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.clone()));
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.clone()));
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;
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;
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,
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_in_leading_whitespace_auto_indents_lines(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_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
1629 let mut cx = EditorTestContext::new(cx);
1630 let language = Arc::new(
1631 Language::new(
1632 LanguageConfig::default(),
1633 Some(tree_sitter_rust::language()),
1634 )
1635 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
1636 .unwrap(),
1637 );
1638 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1639
1640 cx.update(|cx| {
1641 cx.update_global::<Settings, _, _>(|settings, _| {
1642 settings.editor_overrides.tab_size = Some(4.try_into().unwrap());
1643 });
1644 });
1645
1646 cx.set_state(indoc! {"
1647 fn a() {
1648 if b {
1649 \t ˇc
1650 }
1651 }
1652 "});
1653
1654 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1655 cx.assert_editor_state(indoc! {"
1656 fn a() {
1657 if b {
1658 ˇc
1659 }
1660 }
1661 "});
1662}
1663
1664#[gpui::test]
1665async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
1666 let mut cx = EditorTestContext::new(cx);
1667
1668 cx.set_state(indoc! {"
1669 «oneˇ» «twoˇ»
1670 three
1671 four
1672 "});
1673 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1674 cx.assert_editor_state(indoc! {"
1675 «oneˇ» «twoˇ»
1676 three
1677 four
1678 "});
1679
1680 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1681 cx.assert_editor_state(indoc! {"
1682 «oneˇ» «twoˇ»
1683 three
1684 four
1685 "});
1686
1687 // select across line ending
1688 cx.set_state(indoc! {"
1689 one two
1690 t«hree
1691 ˇ» four
1692 "});
1693 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1694 cx.assert_editor_state(indoc! {"
1695 one two
1696 t«hree
1697 ˇ» four
1698 "});
1699
1700 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1701 cx.assert_editor_state(indoc! {"
1702 one two
1703 t«hree
1704 ˇ» four
1705 "});
1706
1707 // Ensure that indenting/outdenting works when the cursor is at column 0.
1708 cx.set_state(indoc! {"
1709 one two
1710 ˇthree
1711 four
1712 "});
1713 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1714 cx.assert_editor_state(indoc! {"
1715 one two
1716 ˇthree
1717 four
1718 "});
1719
1720 cx.set_state(indoc! {"
1721 one two
1722 ˇ three
1723 four
1724 "});
1725 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1726 cx.assert_editor_state(indoc! {"
1727 one two
1728 ˇthree
1729 four
1730 "});
1731}
1732
1733#[gpui::test]
1734async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
1735 let mut cx = EditorTestContext::new(cx);
1736 cx.update(|cx| {
1737 cx.update_global::<Settings, _, _>(|settings, _| {
1738 settings.editor_overrides.hard_tabs = Some(true);
1739 });
1740 });
1741
1742 // select two ranges on one line
1743 cx.set_state(indoc! {"
1744 «oneˇ» «twoˇ»
1745 three
1746 four
1747 "});
1748 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1749 cx.assert_editor_state(indoc! {"
1750 \t«oneˇ» «twoˇ»
1751 three
1752 four
1753 "});
1754 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1755 cx.assert_editor_state(indoc! {"
1756 \t\t«oneˇ» «twoˇ»
1757 three
1758 four
1759 "});
1760 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1761 cx.assert_editor_state(indoc! {"
1762 \t«oneˇ» «twoˇ»
1763 three
1764 four
1765 "});
1766 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1767 cx.assert_editor_state(indoc! {"
1768 «oneˇ» «twoˇ»
1769 three
1770 four
1771 "});
1772
1773 // select across a line ending
1774 cx.set_state(indoc! {"
1775 one two
1776 t«hree
1777 ˇ»four
1778 "});
1779 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1780 cx.assert_editor_state(indoc! {"
1781 one two
1782 \tt«hree
1783 ˇ»four
1784 "});
1785 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1786 cx.assert_editor_state(indoc! {"
1787 one two
1788 \t\tt«hree
1789 ˇ»four
1790 "});
1791 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1792 cx.assert_editor_state(indoc! {"
1793 one two
1794 \tt«hree
1795 ˇ»four
1796 "});
1797 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1798 cx.assert_editor_state(indoc! {"
1799 one two
1800 t«hree
1801 ˇ»four
1802 "});
1803
1804 // Ensure that indenting/outdenting works when the cursor is at column 0.
1805 cx.set_state(indoc! {"
1806 one two
1807 ˇthree
1808 four
1809 "});
1810 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1811 cx.assert_editor_state(indoc! {"
1812 one two
1813 ˇthree
1814 four
1815 "});
1816 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1817 cx.assert_editor_state(indoc! {"
1818 one two
1819 \tˇthree
1820 four
1821 "});
1822 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1823 cx.assert_editor_state(indoc! {"
1824 one two
1825 ˇthree
1826 four
1827 "});
1828}
1829
1830#[gpui::test]
1831fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
1832 cx.set_global(
1833 Settings::test(cx)
1834 .with_language_defaults(
1835 "TOML",
1836 EditorSettings {
1837 tab_size: Some(2.try_into().unwrap()),
1838 ..Default::default()
1839 },
1840 )
1841 .with_language_defaults(
1842 "Rust",
1843 EditorSettings {
1844 tab_size: Some(4.try_into().unwrap()),
1845 ..Default::default()
1846 },
1847 ),
1848 );
1849 let toml_language = Arc::new(Language::new(
1850 LanguageConfig {
1851 name: "TOML".into(),
1852 ..Default::default()
1853 },
1854 None,
1855 ));
1856 let rust_language = Arc::new(Language::new(
1857 LanguageConfig {
1858 name: "Rust".into(),
1859 ..Default::default()
1860 },
1861 None,
1862 ));
1863
1864 let toml_buffer =
1865 cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
1866 let rust_buffer = cx.add_model(|cx| {
1867 Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
1868 });
1869 let multibuffer = cx.add_model(|cx| {
1870 let mut multibuffer = MultiBuffer::new(0);
1871 multibuffer.push_excerpts(
1872 toml_buffer.clone(),
1873 [ExcerptRange {
1874 context: Point::new(0, 0)..Point::new(2, 0),
1875 primary: None,
1876 }],
1877 cx,
1878 );
1879 multibuffer.push_excerpts(
1880 rust_buffer.clone(),
1881 [ExcerptRange {
1882 context: Point::new(0, 0)..Point::new(1, 0),
1883 primary: None,
1884 }],
1885 cx,
1886 );
1887 multibuffer
1888 });
1889
1890 cx.add_window(Default::default(), |cx| {
1891 let mut editor = build_editor(multibuffer, cx);
1892
1893 assert_eq!(
1894 editor.text(cx),
1895 indoc! {"
1896 a = 1
1897 b = 2
1898
1899 const c: usize = 3;
1900 "}
1901 );
1902
1903 select_ranges(
1904 &mut editor,
1905 indoc! {"
1906 «aˇ» = 1
1907 b = 2
1908
1909 «const c:ˇ» usize = 3;
1910 "},
1911 cx,
1912 );
1913
1914 editor.tab(&Tab, cx);
1915 assert_text_with_selections(
1916 &mut editor,
1917 indoc! {"
1918 «aˇ» = 1
1919 b = 2
1920
1921 «const c:ˇ» usize = 3;
1922 "},
1923 cx,
1924 );
1925 editor.tab_prev(&TabPrev, cx);
1926 assert_text_with_selections(
1927 &mut editor,
1928 indoc! {"
1929 «aˇ» = 1
1930 b = 2
1931
1932 «const c:ˇ» usize = 3;
1933 "},
1934 cx,
1935 );
1936
1937 editor
1938 });
1939}
1940
1941#[gpui::test]
1942async fn test_backspace(cx: &mut gpui::TestAppContext) {
1943 let mut cx = EditorTestContext::new(cx);
1944
1945 // Basic backspace
1946 cx.set_state(indoc! {"
1947 onˇe two three
1948 fou«rˇ» five six
1949 seven «ˇeight nine
1950 »ten
1951 "});
1952 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1953 cx.assert_editor_state(indoc! {"
1954 oˇe two three
1955 fouˇ five six
1956 seven ˇten
1957 "});
1958
1959 // Test backspace inside and around indents
1960 cx.set_state(indoc! {"
1961 zero
1962 ˇone
1963 ˇtwo
1964 ˇ ˇ ˇ three
1965 ˇ ˇ four
1966 "});
1967 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1968 cx.assert_editor_state(indoc! {"
1969 zero
1970 ˇone
1971 ˇtwo
1972 ˇ threeˇ four
1973 "});
1974
1975 // Test backspace with line_mode set to true
1976 cx.update_editor(|e, _| e.selections.line_mode = true);
1977 cx.set_state(indoc! {"
1978 The ˇquick ˇbrown
1979 fox jumps over
1980 the lazy dog
1981 ˇThe qu«ick bˇ»rown"});
1982 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
1983 cx.assert_editor_state(indoc! {"
1984 ˇfox jumps over
1985 the lazy dogˇ"});
1986}
1987
1988#[gpui::test]
1989async fn test_delete(cx: &mut gpui::TestAppContext) {
1990 let mut cx = EditorTestContext::new(cx);
1991
1992 cx.set_state(indoc! {"
1993 onˇe two three
1994 fou«rˇ» five six
1995 seven «ˇeight nine
1996 »ten
1997 "});
1998 cx.update_editor(|e, cx| e.delete(&Delete, cx));
1999 cx.assert_editor_state(indoc! {"
2000 onˇ two three
2001 fouˇ five six
2002 seven ˇten
2003 "});
2004
2005 // Test backspace with line_mode set to true
2006 cx.update_editor(|e, _| e.selections.line_mode = true);
2007 cx.set_state(indoc! {"
2008 The ˇquick ˇbrown
2009 fox «ˇjum»ps over
2010 the lazy dog
2011 ˇThe qu«ick bˇ»rown"});
2012 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2013 cx.assert_editor_state("ˇthe lazy dogˇ");
2014}
2015
2016#[gpui::test]
2017fn test_delete_line(cx: &mut gpui::MutableAppContext) {
2018 cx.set_global(Settings::test(cx));
2019 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2020 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2021 view.update(cx, |view, cx| {
2022 view.change_selections(None, cx, |s| {
2023 s.select_display_ranges([
2024 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2025 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2026 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2027 ])
2028 });
2029 view.delete_line(&DeleteLine, cx);
2030 assert_eq!(view.display_text(cx), "ghi");
2031 assert_eq!(
2032 view.selections.display_ranges(cx),
2033 vec![
2034 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2035 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2036 ]
2037 );
2038 });
2039
2040 cx.set_global(Settings::test(cx));
2041 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2042 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2043 view.update(cx, |view, cx| {
2044 view.change_selections(None, cx, |s| {
2045 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2046 });
2047 view.delete_line(&DeleteLine, cx);
2048 assert_eq!(view.display_text(cx), "ghi\n");
2049 assert_eq!(
2050 view.selections.display_ranges(cx),
2051 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2052 );
2053 });
2054}
2055
2056#[gpui::test]
2057fn test_duplicate_line(cx: &mut gpui::MutableAppContext) {
2058 cx.set_global(Settings::test(cx));
2059 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2060 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2061 view.update(cx, |view, cx| {
2062 view.change_selections(None, cx, |s| {
2063 s.select_display_ranges([
2064 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2065 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2066 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2067 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2068 ])
2069 });
2070 view.duplicate_line(&DuplicateLine, cx);
2071 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2072 assert_eq!(
2073 view.selections.display_ranges(cx),
2074 vec![
2075 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2076 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2077 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2078 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2079 ]
2080 );
2081 });
2082
2083 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2084 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2085 view.update(cx, |view, cx| {
2086 view.change_selections(None, cx, |s| {
2087 s.select_display_ranges([
2088 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2089 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2090 ])
2091 });
2092 view.duplicate_line(&DuplicateLine, cx);
2093 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2094 assert_eq!(
2095 view.selections.display_ranges(cx),
2096 vec![
2097 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2098 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2099 ]
2100 );
2101 });
2102}
2103
2104#[gpui::test]
2105fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
2106 cx.set_global(Settings::test(cx));
2107 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2108 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2109 view.update(cx, |view, cx| {
2110 view.fold_ranges(
2111 vec![
2112 Point::new(0, 2)..Point::new(1, 2),
2113 Point::new(2, 3)..Point::new(4, 1),
2114 Point::new(7, 0)..Point::new(8, 4),
2115 ],
2116 cx,
2117 );
2118 view.change_selections(None, cx, |s| {
2119 s.select_display_ranges([
2120 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2121 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2122 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2123 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2124 ])
2125 });
2126 assert_eq!(
2127 view.display_text(cx),
2128 "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj"
2129 );
2130
2131 view.move_line_up(&MoveLineUp, cx);
2132 assert_eq!(
2133 view.display_text(cx),
2134 "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff"
2135 );
2136 assert_eq!(
2137 view.selections.display_ranges(cx),
2138 vec![
2139 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2140 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2141 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2142 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2143 ]
2144 );
2145 });
2146
2147 view.update(cx, |view, cx| {
2148 view.move_line_down(&MoveLineDown, cx);
2149 assert_eq!(
2150 view.display_text(cx),
2151 "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj"
2152 );
2153 assert_eq!(
2154 view.selections.display_ranges(cx),
2155 vec![
2156 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2157 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2158 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2159 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2160 ]
2161 );
2162 });
2163
2164 view.update(cx, |view, cx| {
2165 view.move_line_down(&MoveLineDown, cx);
2166 assert_eq!(
2167 view.display_text(cx),
2168 "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj"
2169 );
2170 assert_eq!(
2171 view.selections.display_ranges(cx),
2172 vec![
2173 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2174 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2175 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2176 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2177 ]
2178 );
2179 });
2180
2181 view.update(cx, |view, cx| {
2182 view.move_line_up(&MoveLineUp, cx);
2183 assert_eq!(
2184 view.display_text(cx),
2185 "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff"
2186 );
2187 assert_eq!(
2188 view.selections.display_ranges(cx),
2189 vec![
2190 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2191 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2192 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2193 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2194 ]
2195 );
2196 });
2197}
2198
2199#[gpui::test]
2200fn test_move_line_up_down_with_blocks(cx: &mut gpui::MutableAppContext) {
2201 cx.set_global(Settings::test(cx));
2202 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2203 let snapshot = buffer.read(cx).snapshot(cx);
2204 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2205 editor.update(cx, |editor, cx| {
2206 editor.insert_blocks(
2207 [BlockProperties {
2208 style: BlockStyle::Fixed,
2209 position: snapshot.anchor_after(Point::new(2, 0)),
2210 disposition: BlockDisposition::Below,
2211 height: 1,
2212 render: Arc::new(|_| Empty::new().boxed()),
2213 }],
2214 cx,
2215 );
2216 editor.change_selections(None, cx, |s| {
2217 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2218 });
2219 editor.move_line_down(&MoveLineDown, cx);
2220 });
2221}
2222
2223#[gpui::test]
2224fn test_transpose(cx: &mut gpui::MutableAppContext) {
2225 cx.set_global(Settings::test(cx));
2226
2227 _ = cx
2228 .add_window(Default::default(), |cx| {
2229 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2230
2231 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2232 editor.transpose(&Default::default(), cx);
2233 assert_eq!(editor.text(cx), "bac");
2234 assert_eq!(editor.selections.ranges(cx), [2..2]);
2235
2236 editor.transpose(&Default::default(), cx);
2237 assert_eq!(editor.text(cx), "bca");
2238 assert_eq!(editor.selections.ranges(cx), [3..3]);
2239
2240 editor.transpose(&Default::default(), cx);
2241 assert_eq!(editor.text(cx), "bac");
2242 assert_eq!(editor.selections.ranges(cx), [3..3]);
2243
2244 editor
2245 })
2246 .1;
2247
2248 _ = cx
2249 .add_window(Default::default(), |cx| {
2250 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2251
2252 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2253 editor.transpose(&Default::default(), cx);
2254 assert_eq!(editor.text(cx), "acb\nde");
2255 assert_eq!(editor.selections.ranges(cx), [3..3]);
2256
2257 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2258 editor.transpose(&Default::default(), cx);
2259 assert_eq!(editor.text(cx), "acbd\ne");
2260 assert_eq!(editor.selections.ranges(cx), [5..5]);
2261
2262 editor.transpose(&Default::default(), cx);
2263 assert_eq!(editor.text(cx), "acbde\n");
2264 assert_eq!(editor.selections.ranges(cx), [6..6]);
2265
2266 editor.transpose(&Default::default(), cx);
2267 assert_eq!(editor.text(cx), "acbd\ne");
2268 assert_eq!(editor.selections.ranges(cx), [6..6]);
2269
2270 editor
2271 })
2272 .1;
2273
2274 _ = cx
2275 .add_window(Default::default(), |cx| {
2276 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2277
2278 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2279 editor.transpose(&Default::default(), cx);
2280 assert_eq!(editor.text(cx), "bacd\ne");
2281 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2282
2283 editor.transpose(&Default::default(), cx);
2284 assert_eq!(editor.text(cx), "bcade\n");
2285 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2286
2287 editor.transpose(&Default::default(), cx);
2288 assert_eq!(editor.text(cx), "bcda\ne");
2289 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2290
2291 editor.transpose(&Default::default(), cx);
2292 assert_eq!(editor.text(cx), "bcade\n");
2293 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2294
2295 editor.transpose(&Default::default(), cx);
2296 assert_eq!(editor.text(cx), "bcaed\n");
2297 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2298
2299 editor
2300 })
2301 .1;
2302
2303 _ = cx
2304 .add_window(Default::default(), |cx| {
2305 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2306
2307 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2308 editor.transpose(&Default::default(), cx);
2309 assert_eq!(editor.text(cx), "🏀🍐✋");
2310 assert_eq!(editor.selections.ranges(cx), [8..8]);
2311
2312 editor.transpose(&Default::default(), cx);
2313 assert_eq!(editor.text(cx), "🏀✋🍐");
2314 assert_eq!(editor.selections.ranges(cx), [11..11]);
2315
2316 editor.transpose(&Default::default(), cx);
2317 assert_eq!(editor.text(cx), "🏀🍐✋");
2318 assert_eq!(editor.selections.ranges(cx), [11..11]);
2319
2320 editor
2321 })
2322 .1;
2323}
2324
2325#[gpui::test]
2326async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2327 let mut cx = EditorTestContext::new(cx);
2328
2329 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2330 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2331 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2332
2333 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2334 cx.set_state("two ˇfour ˇsix ˇ");
2335 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2336 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2337
2338 // Paste again but with only two cursors. Since the number of cursors doesn't
2339 // match the number of slices in the clipboard, the entire clipboard text
2340 // is pasted at each cursor.
2341 cx.set_state("ˇtwo one✅ four three six five ˇ");
2342 cx.update_editor(|e, cx| {
2343 e.handle_input("( ", cx);
2344 e.paste(&Paste, cx);
2345 e.handle_input(") ", cx);
2346 });
2347 cx.assert_editor_state(indoc! {"
2348 ( one✅
2349 three
2350 five ) ˇtwo one✅ four three six five ( one✅
2351 three
2352 five ) ˇ"});
2353
2354 // Cut with three selections, one of which is full-line.
2355 cx.set_state(indoc! {"
2356 1«2ˇ»3
2357 4ˇ567
2358 «8ˇ»9"});
2359 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2360 cx.assert_editor_state(indoc! {"
2361 1ˇ3
2362 ˇ9"});
2363
2364 // Paste with three selections, noticing how the copied selection that was full-line
2365 // gets inserted before the second cursor.
2366 cx.set_state(indoc! {"
2367 1ˇ3
2368 9ˇ
2369 «oˇ»ne"});
2370 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2371 cx.assert_editor_state(indoc! {"
2372 12ˇ3
2373 4567
2374 9ˇ
2375 8ˇne"});
2376
2377 // Copy with a single cursor only, which writes the whole line into the clipboard.
2378 cx.set_state(indoc! {"
2379 The quick brown
2380 fox juˇmps over
2381 the lazy dog"});
2382 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2383 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2384
2385 // Paste with three selections, noticing how the copied full-line selection is inserted
2386 // before the empty selections but replaces the selection that is non-empty.
2387 cx.set_state(indoc! {"
2388 Tˇhe quick brown
2389 «foˇ»x jumps over
2390 tˇhe lazy dog"});
2391 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2392 cx.assert_editor_state(indoc! {"
2393 fox jumps over
2394 Tˇhe quick brown
2395 fox jumps over
2396 ˇx jumps over
2397 fox jumps over
2398 tˇhe lazy dog"});
2399}
2400
2401#[gpui::test]
2402async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2403 let mut cx = EditorTestContext::new(cx);
2404 let language = Arc::new(Language::new(
2405 LanguageConfig::default(),
2406 Some(tree_sitter_rust::language()),
2407 ));
2408 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2409
2410 // Cut an indented block, without the leading whitespace.
2411 cx.set_state(indoc! {"
2412 const a: B = (
2413 c(),
2414 «d(
2415 e,
2416 f
2417 )ˇ»
2418 );
2419 "});
2420 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2421 cx.assert_editor_state(indoc! {"
2422 const a: B = (
2423 c(),
2424 ˇ
2425 );
2426 "});
2427
2428 // Paste it at the same position.
2429 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2430 cx.assert_editor_state(indoc! {"
2431 const a: B = (
2432 c(),
2433 d(
2434 e,
2435 f
2436 )ˇ
2437 );
2438 "});
2439
2440 // Paste it at a line with a lower indent level.
2441 cx.set_state(indoc! {"
2442 ˇ
2443 const a: B = (
2444 c(),
2445 );
2446 "});
2447 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2448 cx.assert_editor_state(indoc! {"
2449 d(
2450 e,
2451 f
2452 )ˇ
2453 const a: B = (
2454 c(),
2455 );
2456 "});
2457
2458 // Cut an indented block, with the leading whitespace.
2459 cx.set_state(indoc! {"
2460 const a: B = (
2461 c(),
2462 « d(
2463 e,
2464 f
2465 )
2466 ˇ»);
2467 "});
2468 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2469 cx.assert_editor_state(indoc! {"
2470 const a: B = (
2471 c(),
2472 ˇ);
2473 "});
2474
2475 // Paste it at the same position.
2476 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2477 cx.assert_editor_state(indoc! {"
2478 const a: B = (
2479 c(),
2480 d(
2481 e,
2482 f
2483 )
2484 ˇ);
2485 "});
2486
2487 // Paste it at a line with a higher indent level.
2488 cx.set_state(indoc! {"
2489 const a: B = (
2490 c(),
2491 d(
2492 e,
2493 fˇ
2494 )
2495 );
2496 "});
2497 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2498 cx.assert_editor_state(indoc! {"
2499 const a: B = (
2500 c(),
2501 d(
2502 e,
2503 f d(
2504 e,
2505 f
2506 )
2507 ˇ
2508 )
2509 );
2510 "});
2511}
2512
2513#[gpui::test]
2514fn test_select_all(cx: &mut gpui::MutableAppContext) {
2515 cx.set_global(Settings::test(cx));
2516 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2517 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2518 view.update(cx, |view, cx| {
2519 view.select_all(&SelectAll, cx);
2520 assert_eq!(
2521 view.selections.display_ranges(cx),
2522 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2523 );
2524 });
2525}
2526
2527#[gpui::test]
2528fn test_select_line(cx: &mut gpui::MutableAppContext) {
2529 cx.set_global(Settings::test(cx));
2530 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
2531 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2532 view.update(cx, |view, cx| {
2533 view.change_selections(None, cx, |s| {
2534 s.select_display_ranges([
2535 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2536 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2537 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2538 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
2539 ])
2540 });
2541 view.select_line(&SelectLine, cx);
2542 assert_eq!(
2543 view.selections.display_ranges(cx),
2544 vec![
2545 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
2546 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
2547 ]
2548 );
2549 });
2550
2551 view.update(cx, |view, cx| {
2552 view.select_line(&SelectLine, cx);
2553 assert_eq!(
2554 view.selections.display_ranges(cx),
2555 vec![
2556 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
2557 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
2558 ]
2559 );
2560 });
2561
2562 view.update(cx, |view, cx| {
2563 view.select_line(&SelectLine, cx);
2564 assert_eq!(
2565 view.selections.display_ranges(cx),
2566 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
2567 );
2568 });
2569}
2570
2571#[gpui::test]
2572fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
2573 cx.set_global(Settings::test(cx));
2574 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
2575 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2576 view.update(cx, |view, cx| {
2577 view.fold_ranges(
2578 vec![
2579 Point::new(0, 2)..Point::new(1, 2),
2580 Point::new(2, 3)..Point::new(4, 1),
2581 Point::new(7, 0)..Point::new(8, 4),
2582 ],
2583 cx,
2584 );
2585 view.change_selections(None, cx, |s| {
2586 s.select_display_ranges([
2587 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2588 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2589 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2590 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
2591 ])
2592 });
2593 assert_eq!(view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i");
2594 });
2595
2596 view.update(cx, |view, cx| {
2597 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2598 assert_eq!(
2599 view.display_text(cx),
2600 "aaaaa\nbbbbb\nccc…eeee\nfffff\nggggg\n…i"
2601 );
2602 assert_eq!(
2603 view.selections.display_ranges(cx),
2604 [
2605 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2606 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2607 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
2608 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
2609 ]
2610 );
2611 });
2612
2613 view.update(cx, |view, cx| {
2614 view.change_selections(None, cx, |s| {
2615 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
2616 });
2617 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2618 assert_eq!(
2619 view.display_text(cx),
2620 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
2621 );
2622 assert_eq!(
2623 view.selections.display_ranges(cx),
2624 [
2625 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
2626 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
2627 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
2628 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
2629 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
2630 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
2631 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
2632 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
2633 ]
2634 );
2635 });
2636}
2637
2638#[gpui::test]
2639fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) {
2640 cx.set_global(Settings::test(cx));
2641 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
2642 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
2643
2644 view.update(cx, |view, cx| {
2645 view.change_selections(None, cx, |s| {
2646 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
2647 });
2648 });
2649 view.update(cx, |view, cx| {
2650 view.add_selection_above(&AddSelectionAbove, cx);
2651 assert_eq!(
2652 view.selections.display_ranges(cx),
2653 vec![
2654 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2655 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2656 ]
2657 );
2658 });
2659
2660 view.update(cx, |view, cx| {
2661 view.add_selection_above(&AddSelectionAbove, cx);
2662 assert_eq!(
2663 view.selections.display_ranges(cx),
2664 vec![
2665 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2666 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2667 ]
2668 );
2669 });
2670
2671 view.update(cx, |view, cx| {
2672 view.add_selection_below(&AddSelectionBelow, cx);
2673 assert_eq!(
2674 view.selections.display_ranges(cx),
2675 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2676 );
2677
2678 view.undo_selection(&UndoSelection, cx);
2679 assert_eq!(
2680 view.selections.display_ranges(cx),
2681 vec![
2682 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2683 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2684 ]
2685 );
2686
2687 view.redo_selection(&RedoSelection, cx);
2688 assert_eq!(
2689 view.selections.display_ranges(cx),
2690 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2691 );
2692 });
2693
2694 view.update(cx, |view, cx| {
2695 view.add_selection_below(&AddSelectionBelow, cx);
2696 assert_eq!(
2697 view.selections.display_ranges(cx),
2698 vec![
2699 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
2700 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
2701 ]
2702 );
2703 });
2704
2705 view.update(cx, |view, cx| {
2706 view.add_selection_below(&AddSelectionBelow, cx);
2707 assert_eq!(
2708 view.selections.display_ranges(cx),
2709 vec![
2710 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
2711 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
2712 ]
2713 );
2714 });
2715
2716 view.update(cx, |view, cx| {
2717 view.change_selections(None, cx, |s| {
2718 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
2719 });
2720 });
2721 view.update(cx, |view, cx| {
2722 view.add_selection_below(&AddSelectionBelow, cx);
2723 assert_eq!(
2724 view.selections.display_ranges(cx),
2725 vec![
2726 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2727 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2728 ]
2729 );
2730 });
2731
2732 view.update(cx, |view, cx| {
2733 view.add_selection_below(&AddSelectionBelow, cx);
2734 assert_eq!(
2735 view.selections.display_ranges(cx),
2736 vec![
2737 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
2738 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
2739 ]
2740 );
2741 });
2742
2743 view.update(cx, |view, cx| {
2744 view.add_selection_above(&AddSelectionAbove, cx);
2745 assert_eq!(
2746 view.selections.display_ranges(cx),
2747 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
2748 );
2749 });
2750
2751 view.update(cx, |view, cx| {
2752 view.add_selection_above(&AddSelectionAbove, cx);
2753 assert_eq!(
2754 view.selections.display_ranges(cx),
2755 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
2756 );
2757 });
2758
2759 view.update(cx, |view, cx| {
2760 view.change_selections(None, cx, |s| {
2761 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
2762 });
2763 view.add_selection_below(&AddSelectionBelow, cx);
2764 assert_eq!(
2765 view.selections.display_ranges(cx),
2766 vec![
2767 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2768 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2769 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2770 ]
2771 );
2772 });
2773
2774 view.update(cx, |view, cx| {
2775 view.add_selection_below(&AddSelectionBelow, cx);
2776 assert_eq!(
2777 view.selections.display_ranges(cx),
2778 vec![
2779 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2780 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2781 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2782 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
2783 ]
2784 );
2785 });
2786
2787 view.update(cx, |view, cx| {
2788 view.add_selection_above(&AddSelectionAbove, cx);
2789 assert_eq!(
2790 view.selections.display_ranges(cx),
2791 vec![
2792 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
2793 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
2794 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
2795 ]
2796 );
2797 });
2798
2799 view.update(cx, |view, cx| {
2800 view.change_selections(None, cx, |s| {
2801 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
2802 });
2803 });
2804 view.update(cx, |view, cx| {
2805 view.add_selection_above(&AddSelectionAbove, cx);
2806 assert_eq!(
2807 view.selections.display_ranges(cx),
2808 vec![
2809 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
2810 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
2811 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
2812 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
2813 ]
2814 );
2815 });
2816
2817 view.update(cx, |view, cx| {
2818 view.add_selection_below(&AddSelectionBelow, cx);
2819 assert_eq!(
2820 view.selections.display_ranges(cx),
2821 vec![
2822 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
2823 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
2824 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
2825 ]
2826 );
2827 });
2828}
2829
2830#[gpui::test]
2831async fn test_select_next(cx: &mut gpui::TestAppContext) {
2832 let mut cx = EditorTestContext::new(cx);
2833 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
2834
2835 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2836 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
2837
2838 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2839 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
2840
2841 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
2842 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
2843
2844 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
2845 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
2846
2847 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2848 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
2849
2850 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
2851 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
2852}
2853
2854#[gpui::test]
2855async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
2856 cx.update(|cx| cx.set_global(Settings::test(cx)));
2857 let language = Arc::new(Language::new(
2858 LanguageConfig::default(),
2859 Some(tree_sitter_rust::language()),
2860 ));
2861
2862 let text = r#"
2863 use mod1::mod2::{mod3, mod4};
2864
2865 fn fn_1(param1: bool, param2: &str) {
2866 let var1 = "text";
2867 }
2868 "#
2869 .unindent();
2870
2871 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
2872 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
2873 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
2874 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
2875 .await;
2876
2877 view.update(cx, |view, cx| {
2878 view.change_selections(None, cx, |s| {
2879 s.select_display_ranges([
2880 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2881 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2882 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2883 ]);
2884 });
2885 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2886 });
2887 assert_eq!(
2888 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
2889 &[
2890 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
2891 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2892 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
2893 ]
2894 );
2895
2896 view.update(cx, |view, cx| {
2897 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2898 });
2899 assert_eq!(
2900 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2901 &[
2902 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2903 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
2904 ]
2905 );
2906
2907 view.update(cx, |view, cx| {
2908 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2909 });
2910 assert_eq!(
2911 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2912 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
2913 );
2914
2915 // Trying to expand the selected syntax node one more time has no effect.
2916 view.update(cx, |view, cx| {
2917 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2918 });
2919 assert_eq!(
2920 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2921 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
2922 );
2923
2924 view.update(cx, |view, cx| {
2925 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2926 });
2927 assert_eq!(
2928 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2929 &[
2930 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2931 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
2932 ]
2933 );
2934
2935 view.update(cx, |view, cx| {
2936 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2937 });
2938 assert_eq!(
2939 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2940 &[
2941 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
2942 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2943 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
2944 ]
2945 );
2946
2947 view.update(cx, |view, cx| {
2948 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2949 });
2950 assert_eq!(
2951 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2952 &[
2953 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2954 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2955 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2956 ]
2957 );
2958
2959 // Trying to shrink the selected syntax node one more time has no effect.
2960 view.update(cx, |view, cx| {
2961 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
2962 });
2963 assert_eq!(
2964 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2965 &[
2966 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
2967 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
2968 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
2969 ]
2970 );
2971
2972 // Ensure that we keep expanding the selection if the larger selection starts or ends within
2973 // a fold.
2974 view.update(cx, |view, cx| {
2975 view.fold_ranges(
2976 vec![
2977 Point::new(0, 21)..Point::new(0, 24),
2978 Point::new(3, 20)..Point::new(3, 22),
2979 ],
2980 cx,
2981 );
2982 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
2983 });
2984 assert_eq!(
2985 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
2986 &[
2987 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
2988 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
2989 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
2990 ]
2991 );
2992}
2993
2994#[gpui::test]
2995async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
2996 cx.update(|cx| cx.set_global(Settings::test(cx)));
2997 let language = Arc::new(
2998 Language::new(
2999 LanguageConfig {
3000 brackets: vec![
3001 BracketPair {
3002 start: "{".to_string(),
3003 end: "}".to_string(),
3004 close: false,
3005 newline: true,
3006 },
3007 BracketPair {
3008 start: "(".to_string(),
3009 end: ")".to_string(),
3010 close: false,
3011 newline: true,
3012 },
3013 ],
3014 ..Default::default()
3015 },
3016 Some(tree_sitter_rust::language()),
3017 )
3018 .with_indents_query(
3019 r#"
3020 (_ "(" ")" @end) @indent
3021 (_ "{" "}" @end) @indent
3022 "#,
3023 )
3024 .unwrap(),
3025 );
3026
3027 let text = "fn a() {}";
3028
3029 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3030 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3031 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3032 editor
3033 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3034 .await;
3035
3036 editor.update(cx, |editor, cx| {
3037 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3038 editor.newline(&Newline, cx);
3039 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3040 assert_eq!(
3041 editor.selections.ranges(cx),
3042 &[
3043 Point::new(1, 4)..Point::new(1, 4),
3044 Point::new(3, 4)..Point::new(3, 4),
3045 Point::new(5, 0)..Point::new(5, 0)
3046 ]
3047 );
3048 });
3049}
3050
3051#[gpui::test]
3052async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3053 let mut cx = EditorTestContext::new(cx);
3054
3055 let language = Arc::new(Language::new(
3056 LanguageConfig {
3057 brackets: vec![
3058 BracketPair {
3059 start: "{".to_string(),
3060 end: "}".to_string(),
3061 close: true,
3062 newline: true,
3063 },
3064 BracketPair {
3065 start: "(".to_string(),
3066 end: ")".to_string(),
3067 close: true,
3068 newline: true,
3069 },
3070 BracketPair {
3071 start: "/*".to_string(),
3072 end: " */".to_string(),
3073 close: true,
3074 newline: true,
3075 },
3076 BracketPair {
3077 start: "[".to_string(),
3078 end: "]".to_string(),
3079 close: false,
3080 newline: true,
3081 },
3082 BracketPair {
3083 start: "\"".to_string(),
3084 end: "\"".to_string(),
3085 close: true,
3086 newline: false,
3087 },
3088 ],
3089 autoclose_before: "})]".to_string(),
3090 ..Default::default()
3091 },
3092 Some(tree_sitter_rust::language()),
3093 ));
3094
3095 let registry = Arc::new(LanguageRegistry::test());
3096 registry.add(language.clone());
3097 cx.update_buffer(|buffer, cx| {
3098 buffer.set_language_registry(registry);
3099 buffer.set_language(Some(language), cx);
3100 });
3101
3102 cx.set_state(
3103 &r#"
3104 🏀ˇ
3105 εˇ
3106 ❤️ˇ
3107 "#
3108 .unindent(),
3109 );
3110
3111 // autoclose multiple nested brackets at multiple cursors
3112 cx.update_editor(|view, cx| {
3113 view.handle_input("{", cx);
3114 view.handle_input("{", cx);
3115 view.handle_input("{", cx);
3116 });
3117 cx.assert_editor_state(
3118 &"
3119 🏀{{{ˇ}}}
3120 ε{{{ˇ}}}
3121 ❤️{{{ˇ}}}
3122 "
3123 .unindent(),
3124 );
3125
3126 // insert a different closing bracket
3127 cx.update_editor(|view, cx| {
3128 view.handle_input(")", cx);
3129 });
3130 cx.assert_editor_state(
3131 &"
3132 🏀{{{)ˇ}}}
3133 ε{{{)ˇ}}}
3134 ❤️{{{)ˇ}}}
3135 "
3136 .unindent(),
3137 );
3138
3139 // skip over the auto-closed brackets when typing a closing bracket
3140 cx.update_editor(|view, cx| {
3141 view.move_right(&MoveRight, cx);
3142 view.handle_input("}", cx);
3143 view.handle_input("}", cx);
3144 view.handle_input("}", cx);
3145 });
3146 cx.assert_editor_state(
3147 &"
3148 🏀{{{)}}}}ˇ
3149 ε{{{)}}}}ˇ
3150 ❤️{{{)}}}}ˇ
3151 "
3152 .unindent(),
3153 );
3154
3155 // autoclose multi-character pairs
3156 cx.set_state(
3157 &"
3158 ˇ
3159 ˇ
3160 "
3161 .unindent(),
3162 );
3163 cx.update_editor(|view, cx| {
3164 view.handle_input("/", cx);
3165 view.handle_input("*", cx);
3166 });
3167 cx.assert_editor_state(
3168 &"
3169 /*ˇ */
3170 /*ˇ */
3171 "
3172 .unindent(),
3173 );
3174
3175 // one cursor autocloses a multi-character pair, one cursor
3176 // does not autoclose.
3177 cx.set_state(
3178 &"
3179 /ˇ
3180 ˇ
3181 "
3182 .unindent(),
3183 );
3184 cx.update_editor(|view, cx| view.handle_input("*", cx));
3185 cx.assert_editor_state(
3186 &"
3187 /*ˇ */
3188 *ˇ
3189 "
3190 .unindent(),
3191 );
3192
3193 // Don't autoclose if the next character isn't whitespace and isn't
3194 // listed in the language's "autoclose_before" section.
3195 cx.set_state("ˇa b");
3196 cx.update_editor(|view, cx| view.handle_input("{", cx));
3197 cx.assert_editor_state("{ˇa b");
3198
3199 // Don't autoclose if `close` is false for the bracket pair
3200 cx.set_state("ˇ");
3201 cx.update_editor(|view, cx| view.handle_input("[", cx));
3202 cx.assert_editor_state("[ˇ");
3203
3204 // Surround with brackets if text is selected
3205 cx.set_state("«aˇ» b");
3206 cx.update_editor(|view, cx| view.handle_input("{", cx));
3207 cx.assert_editor_state("{«aˇ»} b");
3208
3209 // Autclose pair where the start and end characters are the same
3210 cx.set_state("aˇ");
3211 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3212 cx.assert_editor_state("a\"ˇ\"");
3213 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3214 cx.assert_editor_state("a\"\"ˇ");
3215}
3216
3217#[gpui::test]
3218async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3219 let mut cx = EditorTestContext::new(cx);
3220
3221 let html_language = Arc::new(
3222 Language::new(
3223 LanguageConfig {
3224 name: "HTML".into(),
3225 brackets: vec![
3226 BracketPair {
3227 start: "<".into(),
3228 end: ">".into(),
3229 close: true,
3230 ..Default::default()
3231 },
3232 BracketPair {
3233 start: "{".into(),
3234 end: "}".into(),
3235 close: true,
3236 ..Default::default()
3237 },
3238 BracketPair {
3239 start: "(".into(),
3240 end: ")".into(),
3241 close: true,
3242 ..Default::default()
3243 },
3244 ],
3245 autoclose_before: "})]>".into(),
3246 ..Default::default()
3247 },
3248 Some(tree_sitter_html::language()),
3249 )
3250 .with_injection_query(
3251 r#"
3252 (script_element
3253 (raw_text) @content
3254 (#set! "language" "javascript"))
3255 "#,
3256 )
3257 .unwrap(),
3258 );
3259
3260 let javascript_language = Arc::new(Language::new(
3261 LanguageConfig {
3262 name: "JavaScript".into(),
3263 brackets: vec![
3264 BracketPair {
3265 start: "/*".into(),
3266 end: " */".into(),
3267 close: true,
3268 ..Default::default()
3269 },
3270 BracketPair {
3271 start: "{".into(),
3272 end: "}".into(),
3273 close: true,
3274 ..Default::default()
3275 },
3276 BracketPair {
3277 start: "(".into(),
3278 end: ")".into(),
3279 close: true,
3280 ..Default::default()
3281 },
3282 ],
3283 autoclose_before: "})]>".into(),
3284 ..Default::default()
3285 },
3286 Some(tree_sitter_javascript::language()),
3287 ));
3288
3289 let registry = Arc::new(LanguageRegistry::test());
3290 registry.add(html_language.clone());
3291 registry.add(javascript_language.clone());
3292
3293 cx.update_buffer(|buffer, cx| {
3294 buffer.set_language_registry(registry);
3295 buffer.set_language(Some(html_language), cx);
3296 });
3297
3298 cx.set_state(
3299 &r#"
3300 <body>ˇ
3301 <script>
3302 var x = 1;ˇ
3303 </script>
3304 </body>ˇ
3305 "#
3306 .unindent(),
3307 );
3308
3309 // Precondition: different languages are active at different locations.
3310 cx.update_editor(|editor, cx| {
3311 let snapshot = editor.snapshot(cx);
3312 let cursors = editor.selections.ranges::<usize>(cx);
3313 let languages = cursors
3314 .iter()
3315 .map(|c| snapshot.language_at(c.start).unwrap().name())
3316 .collect::<Vec<_>>();
3317 assert_eq!(
3318 languages,
3319 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3320 );
3321 });
3322
3323 // Angle brackets autoclose in HTML, but not JavaScript.
3324 cx.update_editor(|editor, cx| {
3325 editor.handle_input("<", cx);
3326 editor.handle_input("a", cx);
3327 });
3328 cx.assert_editor_state(
3329 &r#"
3330 <body><aˇ>
3331 <script>
3332 var x = 1;<aˇ
3333 </script>
3334 </body><aˇ>
3335 "#
3336 .unindent(),
3337 );
3338
3339 // Curly braces and parens autoclose in both HTML and JavaScript.
3340 cx.update_editor(|editor, cx| {
3341 editor.handle_input(" b=", cx);
3342 editor.handle_input("{", cx);
3343 editor.handle_input("c", cx);
3344 editor.handle_input("(", cx);
3345 });
3346 cx.assert_editor_state(
3347 &r#"
3348 <body><a b={c(ˇ)}>
3349 <script>
3350 var x = 1;<a b={c(ˇ)}
3351 </script>
3352 </body><a b={c(ˇ)}>
3353 "#
3354 .unindent(),
3355 );
3356
3357 // Brackets that were already autoclosed are skipped.
3358 cx.update_editor(|editor, cx| {
3359 editor.handle_input(")", cx);
3360 editor.handle_input("d", cx);
3361 editor.handle_input("}", cx);
3362 });
3363 cx.assert_editor_state(
3364 &r#"
3365 <body><a b={c()d}ˇ>
3366 <script>
3367 var x = 1;<a b={c()d}ˇ
3368 </script>
3369 </body><a b={c()d}ˇ>
3370 "#
3371 .unindent(),
3372 );
3373 cx.update_editor(|editor, cx| {
3374 editor.handle_input(">", cx);
3375 });
3376 cx.assert_editor_state(
3377 &r#"
3378 <body><a b={c()d}>ˇ
3379 <script>
3380 var x = 1;<a b={c()d}>ˇ
3381 </script>
3382 </body><a b={c()d}>ˇ
3383 "#
3384 .unindent(),
3385 );
3386
3387 // Reset
3388 cx.set_state(
3389 &r#"
3390 <body>ˇ
3391 <script>
3392 var x = 1;ˇ
3393 </script>
3394 </body>ˇ
3395 "#
3396 .unindent(),
3397 );
3398
3399 cx.update_editor(|editor, cx| {
3400 editor.handle_input("<", cx);
3401 });
3402 cx.assert_editor_state(
3403 &r#"
3404 <body><ˇ>
3405 <script>
3406 var x = 1;<ˇ
3407 </script>
3408 </body><ˇ>
3409 "#
3410 .unindent(),
3411 );
3412
3413 // When backspacing, the closing angle brackets are removed.
3414 cx.update_editor(|editor, cx| {
3415 editor.backspace(&Backspace, cx);
3416 });
3417 cx.assert_editor_state(
3418 &r#"
3419 <body>ˇ
3420 <script>
3421 var x = 1;ˇ
3422 </script>
3423 </body>ˇ
3424 "#
3425 .unindent(),
3426 );
3427
3428 // Block comments autoclose in JavaScript, but not HTML.
3429 cx.update_editor(|editor, cx| {
3430 editor.handle_input("/", cx);
3431 editor.handle_input("*", cx);
3432 });
3433 cx.assert_editor_state(
3434 &r#"
3435 <body>/*ˇ
3436 <script>
3437 var x = 1;/*ˇ */
3438 </script>
3439 </body>/*ˇ
3440 "#
3441 .unindent(),
3442 );
3443}
3444
3445#[gpui::test]
3446async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3447 cx.update(|cx| cx.set_global(Settings::test(cx)));
3448 let language = Arc::new(Language::new(
3449 LanguageConfig {
3450 brackets: vec![BracketPair {
3451 start: "{".to_string(),
3452 end: "}".to_string(),
3453 close: true,
3454 newline: true,
3455 }],
3456 ..Default::default()
3457 },
3458 Some(tree_sitter_rust::language()),
3459 ));
3460
3461 let text = r#"
3462 a
3463 b
3464 c
3465 "#
3466 .unindent();
3467
3468 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3469 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3470 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3471 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3472 .await;
3473
3474 view.update(cx, |view, cx| {
3475 view.change_selections(None, cx, |s| {
3476 s.select_display_ranges([
3477 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3478 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3479 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3480 ])
3481 });
3482
3483 view.handle_input("{", cx);
3484 view.handle_input("{", cx);
3485 view.handle_input("{", cx);
3486 assert_eq!(
3487 view.text(cx),
3488 "
3489 {{{a}}}
3490 {{{b}}}
3491 {{{c}}}
3492 "
3493 .unindent()
3494 );
3495 assert_eq!(
3496 view.selections.display_ranges(cx),
3497 [
3498 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
3499 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
3500 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
3501 ]
3502 );
3503
3504 view.undo(&Undo, cx);
3505 assert_eq!(
3506 view.text(cx),
3507 "
3508 a
3509 b
3510 c
3511 "
3512 .unindent()
3513 );
3514 assert_eq!(
3515 view.selections.display_ranges(cx),
3516 [
3517 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3518 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3519 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
3520 ]
3521 );
3522 });
3523}
3524
3525#[gpui::test]
3526async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
3527 cx.update(|cx| cx.set_global(Settings::test(cx)));
3528 let language = Arc::new(Language::new(
3529 LanguageConfig {
3530 brackets: vec![BracketPair {
3531 start: "{".to_string(),
3532 end: "}".to_string(),
3533 close: true,
3534 newline: true,
3535 }],
3536 autoclose_before: "}".to_string(),
3537 ..Default::default()
3538 },
3539 Some(tree_sitter_rust::language()),
3540 ));
3541
3542 let text = r#"
3543 a
3544 b
3545 c
3546 "#
3547 .unindent();
3548
3549 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3550 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3551 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3552 editor
3553 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3554 .await;
3555
3556 editor.update(cx, |editor, cx| {
3557 editor.change_selections(None, cx, |s| {
3558 s.select_ranges([
3559 Point::new(0, 1)..Point::new(0, 1),
3560 Point::new(1, 1)..Point::new(1, 1),
3561 Point::new(2, 1)..Point::new(2, 1),
3562 ])
3563 });
3564
3565 editor.handle_input("{", cx);
3566 editor.handle_input("{", cx);
3567 editor.handle_input("_", cx);
3568 assert_eq!(
3569 editor.text(cx),
3570 "
3571 a{{_}}
3572 b{{_}}
3573 c{{_}}
3574 "
3575 .unindent()
3576 );
3577 assert_eq!(
3578 editor.selections.ranges::<Point>(cx),
3579 [
3580 Point::new(0, 4)..Point::new(0, 4),
3581 Point::new(1, 4)..Point::new(1, 4),
3582 Point::new(2, 4)..Point::new(2, 4)
3583 ]
3584 );
3585
3586 editor.backspace(&Default::default(), cx);
3587 editor.backspace(&Default::default(), cx);
3588 assert_eq!(
3589 editor.text(cx),
3590 "
3591 a{}
3592 b{}
3593 c{}
3594 "
3595 .unindent()
3596 );
3597 assert_eq!(
3598 editor.selections.ranges::<Point>(cx),
3599 [
3600 Point::new(0, 2)..Point::new(0, 2),
3601 Point::new(1, 2)..Point::new(1, 2),
3602 Point::new(2, 2)..Point::new(2, 2)
3603 ]
3604 );
3605
3606 editor.delete_to_previous_word_start(&Default::default(), cx);
3607 assert_eq!(
3608 editor.text(cx),
3609 "
3610 a
3611 b
3612 c
3613 "
3614 .unindent()
3615 );
3616 assert_eq!(
3617 editor.selections.ranges::<Point>(cx),
3618 [
3619 Point::new(0, 1)..Point::new(0, 1),
3620 Point::new(1, 1)..Point::new(1, 1),
3621 Point::new(2, 1)..Point::new(2, 1)
3622 ]
3623 );
3624 });
3625}
3626
3627#[gpui::test]
3628async fn test_snippets(cx: &mut gpui::TestAppContext) {
3629 cx.update(|cx| cx.set_global(Settings::test(cx)));
3630
3631 let (text, insertion_ranges) = marked_text_ranges(
3632 indoc! {"
3633 a.ˇ b
3634 a.ˇ b
3635 a.ˇ b
3636 "},
3637 false,
3638 );
3639
3640 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
3641 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3642
3643 editor.update(cx, |editor, cx| {
3644 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
3645
3646 editor
3647 .insert_snippet(&insertion_ranges, snippet, cx)
3648 .unwrap();
3649
3650 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
3651 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
3652 assert_eq!(editor.text(cx), expected_text);
3653 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
3654 }
3655
3656 assert(
3657 editor,
3658 cx,
3659 indoc! {"
3660 a.f(«one», two, «three») b
3661 a.f(«one», two, «three») b
3662 a.f(«one», two, «three») b
3663 "},
3664 );
3665
3666 // Can't move earlier than the first tab stop
3667 assert!(!editor.move_to_prev_snippet_tabstop(cx));
3668 assert(
3669 editor,
3670 cx,
3671 indoc! {"
3672 a.f(«one», two, «three») b
3673 a.f(«one», two, «three») b
3674 a.f(«one», two, «three») b
3675 "},
3676 );
3677
3678 assert!(editor.move_to_next_snippet_tabstop(cx));
3679 assert(
3680 editor,
3681 cx,
3682 indoc! {"
3683 a.f(one, «two», three) b
3684 a.f(one, «two», three) b
3685 a.f(one, «two», three) b
3686 "},
3687 );
3688
3689 editor.move_to_prev_snippet_tabstop(cx);
3690 assert(
3691 editor,
3692 cx,
3693 indoc! {"
3694 a.f(«one», two, «three») b
3695 a.f(«one», two, «three») b
3696 a.f(«one», two, «three») b
3697 "},
3698 );
3699
3700 assert!(editor.move_to_next_snippet_tabstop(cx));
3701 assert(
3702 editor,
3703 cx,
3704 indoc! {"
3705 a.f(one, «two», three) b
3706 a.f(one, «two», three) b
3707 a.f(one, «two», three) b
3708 "},
3709 );
3710 assert!(editor.move_to_next_snippet_tabstop(cx));
3711 assert(
3712 editor,
3713 cx,
3714 indoc! {"
3715 a.f(one, two, three)ˇ b
3716 a.f(one, two, three)ˇ b
3717 a.f(one, two, three)ˇ b
3718 "},
3719 );
3720
3721 // As soon as the last tab stop is reached, snippet state is gone
3722 editor.move_to_prev_snippet_tabstop(cx);
3723 assert(
3724 editor,
3725 cx,
3726 indoc! {"
3727 a.f(one, two, three)ˇ b
3728 a.f(one, two, three)ˇ b
3729 a.f(one, two, three)ˇ b
3730 "},
3731 );
3732 });
3733}
3734
3735#[gpui::test]
3736async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
3737 cx.foreground().forbid_parking();
3738
3739 let mut language = Language::new(
3740 LanguageConfig {
3741 name: "Rust".into(),
3742 path_suffixes: vec!["rs".to_string()],
3743 ..Default::default()
3744 },
3745 Some(tree_sitter_rust::language()),
3746 );
3747 let mut fake_servers = language
3748 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3749 capabilities: lsp::ServerCapabilities {
3750 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3751 ..Default::default()
3752 },
3753 ..Default::default()
3754 }))
3755 .await;
3756
3757 let fs = FakeFs::new(cx.background());
3758 fs.insert_file("/file.rs", Default::default()).await;
3759
3760 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3761 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3762 let buffer = project
3763 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3764 .await
3765 .unwrap();
3766
3767 cx.foreground().start_waiting();
3768 let fake_server = fake_servers.next().await.unwrap();
3769
3770 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3771 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3772 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3773 assert!(cx.read(|cx| editor.is_dirty(cx)));
3774
3775 let save = cx.update(|cx| editor.save(project.clone(), cx));
3776 fake_server
3777 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3778 assert_eq!(
3779 params.text_document.uri,
3780 lsp::Url::from_file_path("/file.rs").unwrap()
3781 );
3782 assert_eq!(params.options.tab_size, 4);
3783 Ok(Some(vec![lsp::TextEdit::new(
3784 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3785 ", ".to_string(),
3786 )]))
3787 })
3788 .next()
3789 .await;
3790 cx.foreground().start_waiting();
3791 save.await.unwrap();
3792 assert_eq!(
3793 editor.read_with(cx, |editor, cx| editor.text(cx)),
3794 "one, two\nthree\n"
3795 );
3796 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3797
3798 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3799 assert!(cx.read(|cx| editor.is_dirty(cx)));
3800
3801 // Ensure we can still save even if formatting hangs.
3802 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3803 assert_eq!(
3804 params.text_document.uri,
3805 lsp::Url::from_file_path("/file.rs").unwrap()
3806 );
3807 futures::future::pending::<()>().await;
3808 unreachable!()
3809 });
3810 let save = cx.update(|cx| editor.save(project.clone(), cx));
3811 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3812 cx.foreground().start_waiting();
3813 save.await.unwrap();
3814 assert_eq!(
3815 editor.read_with(cx, |editor, cx| editor.text(cx)),
3816 "one\ntwo\nthree\n"
3817 );
3818 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3819
3820 // Set rust language override and assert overriden tabsize is sent to language server
3821 cx.update(|cx| {
3822 cx.update_global::<Settings, _, _>(|settings, _| {
3823 settings.language_overrides.insert(
3824 "Rust".into(),
3825 EditorSettings {
3826 tab_size: Some(8.try_into().unwrap()),
3827 ..Default::default()
3828 },
3829 );
3830 })
3831 });
3832
3833 let save = cx.update(|cx| editor.save(project.clone(), cx));
3834 fake_server
3835 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
3836 assert_eq!(
3837 params.text_document.uri,
3838 lsp::Url::from_file_path("/file.rs").unwrap()
3839 );
3840 assert_eq!(params.options.tab_size, 8);
3841 Ok(Some(vec![]))
3842 })
3843 .next()
3844 .await;
3845 cx.foreground().start_waiting();
3846 save.await.unwrap();
3847}
3848
3849#[gpui::test]
3850async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
3851 cx.foreground().forbid_parking();
3852
3853 let mut language = Language::new(
3854 LanguageConfig {
3855 name: "Rust".into(),
3856 path_suffixes: vec!["rs".to_string()],
3857 ..Default::default()
3858 },
3859 Some(tree_sitter_rust::language()),
3860 );
3861 let mut fake_servers = language
3862 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3863 capabilities: lsp::ServerCapabilities {
3864 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
3865 ..Default::default()
3866 },
3867 ..Default::default()
3868 }))
3869 .await;
3870
3871 let fs = FakeFs::new(cx.background());
3872 fs.insert_file("/file.rs", Default::default()).await;
3873
3874 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3875 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3876 let buffer = project
3877 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3878 .await
3879 .unwrap();
3880
3881 cx.foreground().start_waiting();
3882 let fake_server = fake_servers.next().await.unwrap();
3883
3884 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3885 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3886 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3887 assert!(cx.read(|cx| editor.is_dirty(cx)));
3888
3889 let save = cx.update(|cx| editor.save(project.clone(), cx));
3890 fake_server
3891 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3892 assert_eq!(
3893 params.text_document.uri,
3894 lsp::Url::from_file_path("/file.rs").unwrap()
3895 );
3896 assert_eq!(params.options.tab_size, 4);
3897 Ok(Some(vec![lsp::TextEdit::new(
3898 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
3899 ", ".to_string(),
3900 )]))
3901 })
3902 .next()
3903 .await;
3904 cx.foreground().start_waiting();
3905 save.await.unwrap();
3906 assert_eq!(
3907 editor.read_with(cx, |editor, cx| editor.text(cx)),
3908 "one, two\nthree\n"
3909 );
3910 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3911
3912 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
3913 assert!(cx.read(|cx| editor.is_dirty(cx)));
3914
3915 // Ensure we can still save even if formatting hangs.
3916 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
3917 move |params, _| async move {
3918 assert_eq!(
3919 params.text_document.uri,
3920 lsp::Url::from_file_path("/file.rs").unwrap()
3921 );
3922 futures::future::pending::<()>().await;
3923 unreachable!()
3924 },
3925 );
3926 let save = cx.update(|cx| editor.save(project.clone(), cx));
3927 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
3928 cx.foreground().start_waiting();
3929 save.await.unwrap();
3930 assert_eq!(
3931 editor.read_with(cx, |editor, cx| editor.text(cx)),
3932 "one\ntwo\nthree\n"
3933 );
3934 assert!(!cx.read(|cx| editor.is_dirty(cx)));
3935
3936 // Set rust language override and assert overriden tabsize is sent to language server
3937 cx.update(|cx| {
3938 cx.update_global::<Settings, _, _>(|settings, _| {
3939 settings.language_overrides.insert(
3940 "Rust".into(),
3941 EditorSettings {
3942 tab_size: Some(8.try_into().unwrap()),
3943 ..Default::default()
3944 },
3945 );
3946 })
3947 });
3948
3949 let save = cx.update(|cx| editor.save(project.clone(), cx));
3950 fake_server
3951 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
3952 assert_eq!(
3953 params.text_document.uri,
3954 lsp::Url::from_file_path("/file.rs").unwrap()
3955 );
3956 assert_eq!(params.options.tab_size, 8);
3957 Ok(Some(vec![]))
3958 })
3959 .next()
3960 .await;
3961 cx.foreground().start_waiting();
3962 save.await.unwrap();
3963}
3964
3965#[gpui::test]
3966async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
3967 cx.foreground().forbid_parking();
3968
3969 let mut language = Language::new(
3970 LanguageConfig {
3971 name: "Rust".into(),
3972 path_suffixes: vec!["rs".to_string()],
3973 ..Default::default()
3974 },
3975 Some(tree_sitter_rust::language()),
3976 );
3977 let mut fake_servers = language
3978 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3979 capabilities: lsp::ServerCapabilities {
3980 document_formatting_provider: Some(lsp::OneOf::Left(true)),
3981 ..Default::default()
3982 },
3983 ..Default::default()
3984 }))
3985 .await;
3986
3987 let fs = FakeFs::new(cx.background());
3988 fs.insert_file("/file.rs", Default::default()).await;
3989
3990 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
3991 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3992 let buffer = project
3993 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
3994 .await
3995 .unwrap();
3996
3997 cx.foreground().start_waiting();
3998 let fake_server = fake_servers.next().await.unwrap();
3999
4000 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4001 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4002 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4003
4004 let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
4005 fake_server
4006 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4007 assert_eq!(
4008 params.text_document.uri,
4009 lsp::Url::from_file_path("/file.rs").unwrap()
4010 );
4011 assert_eq!(params.options.tab_size, 4);
4012 Ok(Some(vec![lsp::TextEdit::new(
4013 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4014 ", ".to_string(),
4015 )]))
4016 })
4017 .next()
4018 .await;
4019 cx.foreground().start_waiting();
4020 format.await.unwrap();
4021 assert_eq!(
4022 editor.read_with(cx, |editor, cx| editor.text(cx)),
4023 "one, two\nthree\n"
4024 );
4025
4026 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4027 // Ensure we don't lock if formatting hangs.
4028 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4029 assert_eq!(
4030 params.text_document.uri,
4031 lsp::Url::from_file_path("/file.rs").unwrap()
4032 );
4033 futures::future::pending::<()>().await;
4034 unreachable!()
4035 });
4036 let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
4037 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4038 cx.foreground().start_waiting();
4039 format.await.unwrap();
4040 assert_eq!(
4041 editor.read_with(cx, |editor, cx| editor.text(cx)),
4042 "one\ntwo\nthree\n"
4043 );
4044}
4045
4046#[gpui::test]
4047async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4048 cx.foreground().forbid_parking();
4049
4050 let mut cx = EditorLspTestContext::new_rust(
4051 lsp::ServerCapabilities {
4052 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4053 ..Default::default()
4054 },
4055 cx,
4056 )
4057 .await;
4058
4059 cx.set_state(indoc! {"
4060 one.twoˇ
4061 "});
4062
4063 // The format request takes a long time. When it completes, it inserts
4064 // a newline and an indent before the `.`
4065 cx.lsp
4066 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4067 let executor = cx.background();
4068 async move {
4069 executor.timer(Duration::from_millis(100)).await;
4070 Ok(Some(vec![lsp::TextEdit {
4071 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4072 new_text: "\n ".into(),
4073 }]))
4074 }
4075 });
4076
4077 // Submit a format request.
4078 let format_1 = cx
4079 .update_editor(|editor, cx| editor.format(&Format, cx))
4080 .unwrap();
4081 cx.foreground().run_until_parked();
4082
4083 // Submit a second format request.
4084 let format_2 = cx
4085 .update_editor(|editor, cx| editor.format(&Format, cx))
4086 .unwrap();
4087 cx.foreground().run_until_parked();
4088
4089 // Wait for both format requests to complete
4090 cx.foreground().advance_clock(Duration::from_millis(200));
4091 cx.foreground().start_waiting();
4092 format_1.await.unwrap();
4093 cx.foreground().start_waiting();
4094 format_2.await.unwrap();
4095
4096 // The formatting edits only happens once.
4097 cx.assert_editor_state(indoc! {"
4098 one
4099 .twoˇ
4100 "});
4101}
4102
4103#[gpui::test]
4104async fn test_completion(cx: &mut gpui::TestAppContext) {
4105 let mut cx = EditorLspTestContext::new_rust(
4106 lsp::ServerCapabilities {
4107 completion_provider: Some(lsp::CompletionOptions {
4108 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4109 ..Default::default()
4110 }),
4111 ..Default::default()
4112 },
4113 cx,
4114 )
4115 .await;
4116
4117 cx.set_state(indoc! {"
4118 oneˇ
4119 two
4120 three
4121 "});
4122 cx.simulate_keystroke(".");
4123 handle_completion_request(
4124 &mut cx,
4125 indoc! {"
4126 one.|<>
4127 two
4128 three
4129 "},
4130 vec!["first_completion", "second_completion"],
4131 )
4132 .await;
4133 cx.condition(|editor, _| editor.context_menu_visible())
4134 .await;
4135 let apply_additional_edits = cx.update_editor(|editor, cx| {
4136 editor.move_down(&MoveDown, cx);
4137 editor
4138 .confirm_completion(&ConfirmCompletion::default(), cx)
4139 .unwrap()
4140 });
4141 cx.assert_editor_state(indoc! {"
4142 one.second_completionˇ
4143 two
4144 three
4145 "});
4146
4147 handle_resolve_completion_request(
4148 &mut cx,
4149 Some(vec![
4150 (
4151 //This overlaps with the primary completion edit which is
4152 //misbehavior from the LSP spec, test that we filter it out
4153 indoc! {"
4154 one.second_ˇcompletion
4155 two
4156 threeˇ
4157 "},
4158 "overlapping aditional edit",
4159 ),
4160 (
4161 indoc! {"
4162 one.second_completion
4163 two
4164 threeˇ
4165 "},
4166 "\nadditional edit",
4167 ),
4168 ]),
4169 )
4170 .await;
4171 apply_additional_edits.await.unwrap();
4172 cx.assert_editor_state(indoc! {"
4173 one.second_completionˇ
4174 two
4175 three
4176 additional edit
4177 "});
4178
4179 cx.set_state(indoc! {"
4180 one.second_completion
4181 twoˇ
4182 threeˇ
4183 additional edit
4184 "});
4185 cx.simulate_keystroke(" ");
4186 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4187 cx.simulate_keystroke("s");
4188 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4189
4190 cx.assert_editor_state(indoc! {"
4191 one.second_completion
4192 two sˇ
4193 three sˇ
4194 additional edit
4195 "});
4196 handle_completion_request(
4197 &mut cx,
4198 indoc! {"
4199 one.second_completion
4200 two s
4201 three <s|>
4202 additional edit
4203 "},
4204 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4205 )
4206 .await;
4207 cx.condition(|editor, _| editor.context_menu_visible())
4208 .await;
4209
4210 cx.simulate_keystroke("i");
4211
4212 handle_completion_request(
4213 &mut cx,
4214 indoc! {"
4215 one.second_completion
4216 two si
4217 three <si|>
4218 additional edit
4219 "},
4220 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4221 )
4222 .await;
4223 cx.condition(|editor, _| editor.context_menu_visible())
4224 .await;
4225
4226 let apply_additional_edits = cx.update_editor(|editor, cx| {
4227 editor
4228 .confirm_completion(&ConfirmCompletion::default(), cx)
4229 .unwrap()
4230 });
4231 cx.assert_editor_state(indoc! {"
4232 one.second_completion
4233 two sixth_completionˇ
4234 three sixth_completionˇ
4235 additional edit
4236 "});
4237
4238 handle_resolve_completion_request(&mut cx, None).await;
4239 apply_additional_edits.await.unwrap();
4240
4241 cx.update(|cx| {
4242 cx.update_global::<Settings, _, _>(|settings, _| {
4243 settings.show_completions_on_input = false;
4244 })
4245 });
4246 cx.set_state("editorˇ");
4247 cx.simulate_keystroke(".");
4248 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4249 cx.simulate_keystroke("c");
4250 cx.simulate_keystroke("l");
4251 cx.simulate_keystroke("o");
4252 cx.assert_editor_state("editor.cloˇ");
4253 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4254 cx.update_editor(|editor, cx| {
4255 editor.show_completions(&ShowCompletions, cx);
4256 });
4257 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4258 cx.condition(|editor, _| editor.context_menu_visible())
4259 .await;
4260 let apply_additional_edits = cx.update_editor(|editor, cx| {
4261 editor
4262 .confirm_completion(&ConfirmCompletion::default(), cx)
4263 .unwrap()
4264 });
4265 cx.assert_editor_state("editor.closeˇ");
4266 handle_resolve_completion_request(&mut cx, None).await;
4267 apply_additional_edits.await.unwrap();
4268
4269 // Handle completion request passing a marked string specifying where the completion
4270 // should be triggered from using '|' character, what range should be replaced, and what completions
4271 // should be returned using '<' and '>' to delimit the range
4272 async fn handle_completion_request<'a>(
4273 cx: &mut EditorLspTestContext<'a>,
4274 marked_string: &str,
4275 completions: Vec<&'static str>,
4276 ) {
4277 let complete_from_marker: TextRangeMarker = '|'.into();
4278 let replace_range_marker: TextRangeMarker = ('<', '>').into();
4279 let (_, mut marked_ranges) = marked_text_ranges_by(
4280 marked_string,
4281 vec![complete_from_marker.clone(), replace_range_marker.clone()],
4282 );
4283
4284 let complete_from_position =
4285 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
4286 let replace_range =
4287 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
4288
4289 cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
4290 let completions = completions.clone();
4291 async move {
4292 assert_eq!(params.text_document_position.text_document.uri, url.clone());
4293 assert_eq!(
4294 params.text_document_position.position,
4295 complete_from_position
4296 );
4297 Ok(Some(lsp::CompletionResponse::Array(
4298 completions
4299 .iter()
4300 .map(|completion_text| lsp::CompletionItem {
4301 label: completion_text.to_string(),
4302 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4303 range: replace_range,
4304 new_text: completion_text.to_string(),
4305 })),
4306 ..Default::default()
4307 })
4308 .collect(),
4309 )))
4310 }
4311 })
4312 .next()
4313 .await;
4314 }
4315
4316 async fn handle_resolve_completion_request<'a>(
4317 cx: &mut EditorLspTestContext<'a>,
4318 edits: Option<Vec<(&'static str, &'static str)>>,
4319 ) {
4320 let edits = edits.map(|edits| {
4321 edits
4322 .iter()
4323 .map(|(marked_string, new_text)| {
4324 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
4325 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
4326 lsp::TextEdit::new(replace_range, new_text.to_string())
4327 })
4328 .collect::<Vec<_>>()
4329 });
4330
4331 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
4332 let edits = edits.clone();
4333 async move {
4334 Ok(lsp::CompletionItem {
4335 additional_text_edits: edits,
4336 ..Default::default()
4337 })
4338 }
4339 })
4340 .next()
4341 .await;
4342 }
4343}
4344
4345#[gpui::test]
4346async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4347 cx.update(|cx| cx.set_global(Settings::test(cx)));
4348 let language = Arc::new(Language::new(
4349 LanguageConfig {
4350 line_comment: Some("// ".into()),
4351 ..Default::default()
4352 },
4353 Some(tree_sitter_rust::language()),
4354 ));
4355
4356 let text = "
4357 fn a() {
4358 //b();
4359 // c();
4360 // d();
4361 }
4362 "
4363 .unindent();
4364
4365 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4366 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4367 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4368
4369 view.update(cx, |editor, cx| {
4370 // If multiple selections intersect a line, the line is only
4371 // toggled once.
4372 editor.change_selections(None, cx, |s| {
4373 s.select_display_ranges([
4374 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4375 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4376 ])
4377 });
4378 editor.toggle_comments(&ToggleComments, cx);
4379 assert_eq!(
4380 editor.text(cx),
4381 "
4382 fn a() {
4383 b();
4384 c();
4385 d();
4386 }
4387 "
4388 .unindent()
4389 );
4390
4391 // The comment prefix is inserted at the same column for every line
4392 // in a selection.
4393 editor.change_selections(None, cx, |s| {
4394 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4395 });
4396 editor.toggle_comments(&ToggleComments, cx);
4397 assert_eq!(
4398 editor.text(cx),
4399 "
4400 fn a() {
4401 // b();
4402 // c();
4403 // d();
4404 }
4405 "
4406 .unindent()
4407 );
4408
4409 // If a selection ends at the beginning of a line, that line is not toggled.
4410 editor.change_selections(None, cx, |s| {
4411 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
4412 });
4413 editor.toggle_comments(&ToggleComments, cx);
4414 assert_eq!(
4415 editor.text(cx),
4416 "
4417 fn a() {
4418 // b();
4419 c();
4420 // d();
4421 }
4422 "
4423 .unindent()
4424 );
4425 });
4426}
4427
4428#[gpui::test]
4429async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
4430 let mut cx = EditorTestContext::new(cx);
4431
4432 let html_language = Arc::new(
4433 Language::new(
4434 LanguageConfig {
4435 name: "HTML".into(),
4436 block_comment: Some(("<!-- ".into(), " -->".into())),
4437 ..Default::default()
4438 },
4439 Some(tree_sitter_html::language()),
4440 )
4441 .with_injection_query(
4442 r#"
4443 (script_element
4444 (raw_text) @content
4445 (#set! "language" "javascript"))
4446 "#,
4447 )
4448 .unwrap(),
4449 );
4450
4451 let javascript_language = Arc::new(Language::new(
4452 LanguageConfig {
4453 name: "JavaScript".into(),
4454 line_comment: Some("// ".into()),
4455 ..Default::default()
4456 },
4457 Some(tree_sitter_javascript::language()),
4458 ));
4459
4460 let registry = Arc::new(LanguageRegistry::test());
4461 registry.add(html_language.clone());
4462 registry.add(javascript_language.clone());
4463
4464 cx.update_buffer(|buffer, cx| {
4465 buffer.set_language_registry(registry);
4466 buffer.set_language(Some(html_language), cx);
4467 });
4468
4469 // Toggle comments for empty selections
4470 cx.set_state(
4471 &r#"
4472 <p>A</p>ˇ
4473 <p>B</p>ˇ
4474 <p>C</p>ˇ
4475 "#
4476 .unindent(),
4477 );
4478 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4479 cx.assert_editor_state(
4480 &r#"
4481 <!-- <p>A</p>ˇ -->
4482 <!-- <p>B</p>ˇ -->
4483 <!-- <p>C</p>ˇ -->
4484 "#
4485 .unindent(),
4486 );
4487 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4488 cx.assert_editor_state(
4489 &r#"
4490 <p>A</p>ˇ
4491 <p>B</p>ˇ
4492 <p>C</p>ˇ
4493 "#
4494 .unindent(),
4495 );
4496
4497 // Toggle comments for mixture of empty and non-empty selections, where
4498 // multiple selections occupy a given line.
4499 cx.set_state(
4500 &r#"
4501 <p>A«</p>
4502 <p>ˇ»B</p>ˇ
4503 <p>C«</p>
4504 <p>ˇ»D</p>ˇ
4505 "#
4506 .unindent(),
4507 );
4508
4509 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4510 cx.assert_editor_state(
4511 &r#"
4512 <!-- <p>A«</p>
4513 <p>ˇ»B</p>ˇ -->
4514 <!-- <p>C«</p>
4515 <p>ˇ»D</p>ˇ -->
4516 "#
4517 .unindent(),
4518 );
4519 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4520 cx.assert_editor_state(
4521 &r#"
4522 <p>A«</p>
4523 <p>ˇ»B</p>ˇ
4524 <p>C«</p>
4525 <p>ˇ»D</p>ˇ
4526 "#
4527 .unindent(),
4528 );
4529
4530 // Toggle comments when different languages are active for different
4531 // selections.
4532 cx.set_state(
4533 &r#"
4534 ˇ<script>
4535 ˇvar x = new Y();
4536 ˇ</script>
4537 "#
4538 .unindent(),
4539 );
4540 cx.foreground().run_until_parked();
4541 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments, cx));
4542 cx.assert_editor_state(
4543 &r#"
4544 <!-- ˇ<script> -->
4545 // ˇvar x = new Y();
4546 <!-- ˇ</script> -->
4547 "#
4548 .unindent(),
4549 );
4550}
4551
4552#[gpui::test]
4553fn test_editing_disjoint_excerpts(cx: &mut gpui::MutableAppContext) {
4554 cx.set_global(Settings::test(cx));
4555 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4556 let multibuffer = cx.add_model(|cx| {
4557 let mut multibuffer = MultiBuffer::new(0);
4558 multibuffer.push_excerpts(
4559 buffer.clone(),
4560 [
4561 ExcerptRange {
4562 context: Point::new(0, 0)..Point::new(0, 4),
4563 primary: None,
4564 },
4565 ExcerptRange {
4566 context: Point::new(1, 0)..Point::new(1, 4),
4567 primary: None,
4568 },
4569 ],
4570 cx,
4571 );
4572 multibuffer
4573 });
4574
4575 assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
4576
4577 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4578 view.update(cx, |view, cx| {
4579 assert_eq!(view.text(cx), "aaaa\nbbbb");
4580 view.change_selections(None, cx, |s| {
4581 s.select_ranges([
4582 Point::new(0, 0)..Point::new(0, 0),
4583 Point::new(1, 0)..Point::new(1, 0),
4584 ])
4585 });
4586
4587 view.handle_input("X", cx);
4588 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
4589 assert_eq!(
4590 view.selections.ranges(cx),
4591 [
4592 Point::new(0, 1)..Point::new(0, 1),
4593 Point::new(1, 1)..Point::new(1, 1),
4594 ]
4595 )
4596 });
4597}
4598
4599#[gpui::test]
4600fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
4601 cx.set_global(Settings::test(cx));
4602 let markers = vec![('[', ']').into(), ('(', ')').into()];
4603 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
4604 indoc! {"
4605 [aaaa
4606 (bbbb]
4607 cccc)",
4608 },
4609 markers.clone(),
4610 );
4611 let excerpt_ranges = markers.into_iter().map(|marker| {
4612 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
4613 ExcerptRange {
4614 context,
4615 primary: None,
4616 }
4617 });
4618 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
4619 let multibuffer = cx.add_model(|cx| {
4620 let mut multibuffer = MultiBuffer::new(0);
4621 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
4622 multibuffer
4623 });
4624
4625 let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
4626 view.update(cx, |view, cx| {
4627 let (expected_text, selection_ranges) = marked_text_ranges(
4628 indoc! {"
4629 aaaa
4630 bˇbbb
4631 bˇbbˇb
4632 cccc"
4633 },
4634 true,
4635 );
4636 assert_eq!(view.text(cx), expected_text);
4637 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
4638
4639 view.handle_input("X", cx);
4640
4641 let (expected_text, expected_selections) = marked_text_ranges(
4642 indoc! {"
4643 aaaa
4644 bXˇbbXb
4645 bXˇbbXˇb
4646 cccc"
4647 },
4648 false,
4649 );
4650 assert_eq!(view.text(cx), expected_text);
4651 assert_eq!(view.selections.ranges(cx), expected_selections);
4652
4653 view.newline(&Newline, cx);
4654 let (expected_text, expected_selections) = marked_text_ranges(
4655 indoc! {"
4656 aaaa
4657 bX
4658 ˇbbX
4659 b
4660 bX
4661 ˇbbX
4662 ˇb
4663 cccc"
4664 },
4665 false,
4666 );
4667 assert_eq!(view.text(cx), expected_text);
4668 assert_eq!(view.selections.ranges(cx), expected_selections);
4669 });
4670}
4671
4672#[gpui::test]
4673fn test_refresh_selections(cx: &mut gpui::MutableAppContext) {
4674 cx.set_global(Settings::test(cx));
4675 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4676 let mut excerpt1_id = None;
4677 let multibuffer = cx.add_model(|cx| {
4678 let mut multibuffer = MultiBuffer::new(0);
4679 excerpt1_id = multibuffer
4680 .push_excerpts(
4681 buffer.clone(),
4682 [
4683 ExcerptRange {
4684 context: Point::new(0, 0)..Point::new(1, 4),
4685 primary: None,
4686 },
4687 ExcerptRange {
4688 context: Point::new(1, 0)..Point::new(2, 4),
4689 primary: None,
4690 },
4691 ],
4692 cx,
4693 )
4694 .into_iter()
4695 .next();
4696 multibuffer
4697 });
4698 assert_eq!(
4699 multibuffer.read(cx).read(cx).text(),
4700 "aaaa\nbbbb\nbbbb\ncccc"
4701 );
4702 let (_, editor) = cx.add_window(Default::default(), |cx| {
4703 let mut editor = build_editor(multibuffer.clone(), cx);
4704 let snapshot = editor.snapshot(cx);
4705 editor.change_selections(None, cx, |s| {
4706 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
4707 });
4708 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
4709 assert_eq!(
4710 editor.selections.ranges(cx),
4711 [
4712 Point::new(1, 3)..Point::new(1, 3),
4713 Point::new(2, 1)..Point::new(2, 1),
4714 ]
4715 );
4716 editor
4717 });
4718
4719 // Refreshing selections is a no-op when excerpts haven't changed.
4720 editor.update(cx, |editor, cx| {
4721 editor.change_selections(None, cx, |s| s.refresh());
4722 assert_eq!(
4723 editor.selections.ranges(cx),
4724 [
4725 Point::new(1, 3)..Point::new(1, 3),
4726 Point::new(2, 1)..Point::new(2, 1),
4727 ]
4728 );
4729 });
4730
4731 multibuffer.update(cx, |multibuffer, cx| {
4732 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
4733 });
4734 editor.update(cx, |editor, cx| {
4735 // Removing an excerpt causes the first selection to become degenerate.
4736 assert_eq!(
4737 editor.selections.ranges(cx),
4738 [
4739 Point::new(0, 0)..Point::new(0, 0),
4740 Point::new(0, 1)..Point::new(0, 1)
4741 ]
4742 );
4743
4744 // Refreshing selections will relocate the first selection to the original buffer
4745 // location.
4746 editor.change_selections(None, cx, |s| s.refresh());
4747 assert_eq!(
4748 editor.selections.ranges(cx),
4749 [
4750 Point::new(0, 1)..Point::new(0, 1),
4751 Point::new(0, 3)..Point::new(0, 3)
4752 ]
4753 );
4754 assert!(editor.selections.pending_anchor().is_some());
4755 });
4756}
4757
4758#[gpui::test]
4759fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppContext) {
4760 cx.set_global(Settings::test(cx));
4761 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
4762 let mut excerpt1_id = None;
4763 let multibuffer = cx.add_model(|cx| {
4764 let mut multibuffer = MultiBuffer::new(0);
4765 excerpt1_id = multibuffer
4766 .push_excerpts(
4767 buffer.clone(),
4768 [
4769 ExcerptRange {
4770 context: Point::new(0, 0)..Point::new(1, 4),
4771 primary: None,
4772 },
4773 ExcerptRange {
4774 context: Point::new(1, 0)..Point::new(2, 4),
4775 primary: None,
4776 },
4777 ],
4778 cx,
4779 )
4780 .into_iter()
4781 .next();
4782 multibuffer
4783 });
4784 assert_eq!(
4785 multibuffer.read(cx).read(cx).text(),
4786 "aaaa\nbbbb\nbbbb\ncccc"
4787 );
4788 let (_, editor) = cx.add_window(Default::default(), |cx| {
4789 let mut editor = build_editor(multibuffer.clone(), cx);
4790 let snapshot = editor.snapshot(cx);
4791 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
4792 assert_eq!(
4793 editor.selections.ranges(cx),
4794 [Point::new(1, 3)..Point::new(1, 3)]
4795 );
4796 editor
4797 });
4798
4799 multibuffer.update(cx, |multibuffer, cx| {
4800 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
4801 });
4802 editor.update(cx, |editor, cx| {
4803 assert_eq!(
4804 editor.selections.ranges(cx),
4805 [Point::new(0, 0)..Point::new(0, 0)]
4806 );
4807
4808 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
4809 editor.change_selections(None, cx, |s| s.refresh());
4810 assert_eq!(
4811 editor.selections.ranges(cx),
4812 [Point::new(0, 3)..Point::new(0, 3)]
4813 );
4814 assert!(editor.selections.pending_anchor().is_some());
4815 });
4816}
4817
4818#[gpui::test]
4819async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
4820 cx.update(|cx| cx.set_global(Settings::test(cx)));
4821 let language = Arc::new(
4822 Language::new(
4823 LanguageConfig {
4824 brackets: vec![
4825 BracketPair {
4826 start: "{".to_string(),
4827 end: "}".to_string(),
4828 close: true,
4829 newline: true,
4830 },
4831 BracketPair {
4832 start: "/* ".to_string(),
4833 end: " */".to_string(),
4834 close: true,
4835 newline: true,
4836 },
4837 ],
4838 ..Default::default()
4839 },
4840 Some(tree_sitter_rust::language()),
4841 )
4842 .with_indents_query("")
4843 .unwrap(),
4844 );
4845
4846 let text = concat!(
4847 "{ }\n", //
4848 " x\n", //
4849 " /* */\n", //
4850 "x\n", //
4851 "{{} }\n", //
4852 );
4853
4854 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4855 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4856 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4857 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4858 .await;
4859
4860 view.update(cx, |view, cx| {
4861 view.change_selections(None, cx, |s| {
4862 s.select_display_ranges([
4863 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
4864 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
4865 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
4866 ])
4867 });
4868 view.newline(&Newline, cx);
4869
4870 assert_eq!(
4871 view.buffer().read(cx).read(cx).text(),
4872 concat!(
4873 "{ \n", // Suppress rustfmt
4874 "\n", //
4875 "}\n", //
4876 " x\n", //
4877 " /* \n", //
4878 " \n", //
4879 " */\n", //
4880 "x\n", //
4881 "{{} \n", //
4882 "}\n", //
4883 )
4884 );
4885 });
4886}
4887
4888#[gpui::test]
4889fn test_highlighted_ranges(cx: &mut gpui::MutableAppContext) {
4890 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4891
4892 cx.set_global(Settings::test(cx));
4893 let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4894
4895 editor.update(cx, |editor, cx| {
4896 struct Type1;
4897 struct Type2;
4898
4899 let buffer = buffer.read(cx).snapshot(cx);
4900
4901 let anchor_range =
4902 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
4903
4904 editor.highlight_background::<Type1>(
4905 vec![
4906 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
4907 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
4908 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
4909 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
4910 ],
4911 |_| Color::red(),
4912 cx,
4913 );
4914 editor.highlight_background::<Type2>(
4915 vec![
4916 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
4917 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
4918 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
4919 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
4920 ],
4921 |_| Color::green(),
4922 cx,
4923 );
4924
4925 let snapshot = editor.snapshot(cx);
4926 let mut highlighted_ranges = editor.background_highlights_in_range(
4927 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
4928 &snapshot,
4929 cx.global::<Settings>().theme.as_ref(),
4930 );
4931 // Enforce a consistent ordering based on color without relying on the ordering of the
4932 // highlight's `TypeId` which is non-deterministic.
4933 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
4934 assert_eq!(
4935 highlighted_ranges,
4936 &[
4937 (
4938 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
4939 Color::green(),
4940 ),
4941 (
4942 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
4943 Color::green(),
4944 ),
4945 (
4946 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
4947 Color::red(),
4948 ),
4949 (
4950 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4951 Color::red(),
4952 ),
4953 ]
4954 );
4955 assert_eq!(
4956 editor.background_highlights_in_range(
4957 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
4958 &snapshot,
4959 cx.global::<Settings>().theme.as_ref(),
4960 ),
4961 &[(
4962 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
4963 Color::red(),
4964 )]
4965 );
4966 });
4967}
4968
4969#[gpui::test]
4970fn test_following(cx: &mut gpui::MutableAppContext) {
4971 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
4972
4973 cx.set_global(Settings::test(cx));
4974
4975 let (_, leader) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
4976 let (_, follower) = cx.add_window(
4977 WindowOptions {
4978 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
4979 ..Default::default()
4980 },
4981 |cx| build_editor(buffer.clone(), cx),
4982 );
4983
4984 let pending_update = Rc::new(RefCell::new(None));
4985 follower.update(cx, {
4986 let update = pending_update.clone();
4987 |_, cx| {
4988 cx.subscribe(&leader, move |_, leader, event, cx| {
4989 leader
4990 .read(cx)
4991 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
4992 })
4993 .detach();
4994 }
4995 });
4996
4997 // Update the selections only
4998 leader.update(cx, |leader, cx| {
4999 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5000 });
5001 follower.update(cx, |follower, cx| {
5002 follower
5003 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5004 .unwrap();
5005 });
5006 assert_eq!(follower.read(cx).selections.ranges(cx), vec![1..1]);
5007
5008 // Update the scroll position only
5009 leader.update(cx, |leader, cx| {
5010 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5011 });
5012 follower.update(cx, |follower, cx| {
5013 follower
5014 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5015 .unwrap();
5016 });
5017 assert_eq!(
5018 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5019 vec2f(1.5, 3.5)
5020 );
5021
5022 // Update the selections and scroll position
5023 leader.update(cx, |leader, cx| {
5024 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5025 leader.request_autoscroll(Autoscroll::newest(), cx);
5026 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5027 });
5028 follower.update(cx, |follower, cx| {
5029 let initial_scroll_position = follower.scroll_position(cx);
5030 follower
5031 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5032 .unwrap();
5033 assert_eq!(follower.scroll_position(cx), initial_scroll_position);
5034 assert!(follower.autoscroll_request.is_some());
5035 });
5036 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]);
5037
5038 // Creating a pending selection that precedes another selection
5039 leader.update(cx, |leader, cx| {
5040 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5041 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5042 });
5043 follower.update(cx, |follower, cx| {
5044 follower
5045 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5046 .unwrap();
5047 });
5048 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0, 1..1]);
5049
5050 // Extend the pending selection so that it surrounds another selection
5051 leader.update(cx, |leader, cx| {
5052 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5053 });
5054 follower.update(cx, |follower, cx| {
5055 follower
5056 .apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
5057 .unwrap();
5058 });
5059 assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..2]);
5060}
5061
5062#[test]
5063fn test_combine_syntax_and_fuzzy_match_highlights() {
5064 let string = "abcdefghijklmnop";
5065 let syntax_ranges = [
5066 (
5067 0..3,
5068 HighlightStyle {
5069 color: Some(Color::red()),
5070 ..Default::default()
5071 },
5072 ),
5073 (
5074 4..8,
5075 HighlightStyle {
5076 color: Some(Color::green()),
5077 ..Default::default()
5078 },
5079 ),
5080 ];
5081 let match_indices = [4, 6, 7, 8];
5082 assert_eq!(
5083 combine_syntax_and_fuzzy_match_highlights(
5084 string,
5085 Default::default(),
5086 syntax_ranges.into_iter(),
5087 &match_indices,
5088 ),
5089 &[
5090 (
5091 0..3,
5092 HighlightStyle {
5093 color: Some(Color::red()),
5094 ..Default::default()
5095 },
5096 ),
5097 (
5098 4..5,
5099 HighlightStyle {
5100 color: Some(Color::green()),
5101 weight: Some(fonts::Weight::BOLD),
5102 ..Default::default()
5103 },
5104 ),
5105 (
5106 5..6,
5107 HighlightStyle {
5108 color: Some(Color::green()),
5109 ..Default::default()
5110 },
5111 ),
5112 (
5113 6..8,
5114 HighlightStyle {
5115 color: Some(Color::green()),
5116 weight: Some(fonts::Weight::BOLD),
5117 ..Default::default()
5118 },
5119 ),
5120 (
5121 8..9,
5122 HighlightStyle {
5123 weight: Some(fonts::Weight::BOLD),
5124 ..Default::default()
5125 },
5126 ),
5127 ]
5128 );
5129}
5130
5131#[gpui::test]
5132async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
5133 let mut cx = EditorTestContext::new(cx);
5134
5135 let diff_base = r#"
5136 use some::mod;
5137
5138 const A: u32 = 42;
5139
5140 fn main() {
5141 println!("hello");
5142
5143 println!("world");
5144 }
5145 "#
5146 .unindent();
5147
5148 // Edits are modified, removed, modified, added
5149 cx.set_state(
5150 &r#"
5151 use some::modified;
5152
5153 ˇ
5154 fn main() {
5155 println!("hello there");
5156
5157 println!("around the");
5158 println!("world");
5159 }
5160 "#
5161 .unindent(),
5162 );
5163
5164 cx.set_diff_base(Some(&diff_base));
5165 deterministic.run_until_parked();
5166
5167 cx.update_editor(|editor, cx| {
5168 //Wrap around the bottom of the buffer
5169 for _ in 0..3 {
5170 editor.go_to_hunk(&GoToHunk, cx);
5171 }
5172 });
5173
5174 cx.assert_editor_state(
5175 &r#"
5176 ˇuse some::modified;
5177
5178
5179 fn main() {
5180 println!("hello there");
5181
5182 println!("around the");
5183 println!("world");
5184 }
5185 "#
5186 .unindent(),
5187 );
5188
5189 cx.update_editor(|editor, cx| {
5190 //Wrap around the top of the buffer
5191 for _ in 0..2 {
5192 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
5193 }
5194 });
5195
5196 cx.assert_editor_state(
5197 &r#"
5198 use some::modified;
5199
5200
5201 fn main() {
5202 ˇ println!("hello there");
5203
5204 println!("around the");
5205 println!("world");
5206 }
5207 "#
5208 .unindent(),
5209 );
5210
5211 cx.update_editor(|editor, cx| {
5212 editor.fold(&Fold, cx);
5213
5214 //Make sure that the fold only gets one hunk
5215 for _ in 0..4 {
5216 editor.go_to_hunk(&GoToHunk, cx);
5217 }
5218 });
5219
5220 cx.assert_editor_state(
5221 &r#"
5222 ˇuse some::modified;
5223
5224
5225 fn main() {
5226 println!("hello there");
5227
5228 println!("around the");
5229 println!("world");
5230 }
5231 "#
5232 .unindent(),
5233 );
5234}
5235
5236fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
5237 let point = DisplayPoint::new(row as u32, column as u32);
5238 point..point
5239}
5240
5241fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
5242 let (text, ranges) = marked_text_ranges(marked_text, true);
5243 assert_eq!(view.text(cx), text);
5244 assert_eq!(
5245 view.selections.ranges(cx),
5246 ranges,
5247 "Assert selections are {}",
5248 marked_text
5249 );
5250}