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