1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10
11use futures::StreamExt;
12use gpui::{
13 div,
14 serde_json::{self, json},
15 Div, TestAppContext, VisualTestContext, WindowBounds, WindowOptions,
16};
17use indoc::indoc;
18use language::{
19 language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
20 BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
21 Override, Point,
22};
23use parking_lot::Mutex;
24use project::project_settings::{LspSettings, ProjectSettings};
25use project::FakeFs;
26use std::sync::atomic;
27use std::sync::atomic::AtomicUsize;
28use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
29use unindent::Unindent;
30use util::{
31 assert_set_eq,
32 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
33};
34use workspace::{
35 item::{FollowEvent, FollowableEvents, FollowableItem, Item, ItemHandle},
36 NavigationEntry, ViewId,
37};
38
39#[gpui::test]
40fn test_edit_events(cx: &mut TestAppContext) {
41 init_test(cx, |_| {});
42
43 let buffer = cx.build_model(|cx| {
44 let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
45 buffer.set_group_interval(Duration::from_secs(1));
46 buffer
47 });
48
49 let events = Rc::new(RefCell::new(Vec::new()));
50 let editor1 = cx.add_window({
51 let events = events.clone();
52 |cx| {
53 let view = cx.view().clone();
54 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
55 if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
56 events.borrow_mut().push(("editor1", event.clone()));
57 }
58 })
59 .detach();
60 Editor::for_buffer(buffer.clone(), None, cx)
61 }
62 });
63
64 let editor2 = cx.add_window({
65 let events = events.clone();
66 |cx| {
67 cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
68 if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
69 events.borrow_mut().push(("editor2", event.clone()));
70 }
71 })
72 .detach();
73 Editor::for_buffer(buffer.clone(), None, cx)
74 }
75 });
76
77 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
78
79 // Mutating editor 1 will emit an `Edited` event only for that editor.
80 editor1.update(cx, |editor, cx| editor.insert("X", cx));
81 assert_eq!(
82 mem::take(&mut *events.borrow_mut()),
83 [
84 ("editor1", EditorEvent::Edited),
85 ("editor1", EditorEvent::BufferEdited),
86 ("editor2", EditorEvent::BufferEdited),
87 ]
88 );
89
90 // Mutating editor 2 will emit an `Edited` event only for that editor.
91 editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
92 assert_eq!(
93 mem::take(&mut *events.borrow_mut()),
94 [
95 ("editor2", EditorEvent::Edited),
96 ("editor1", EditorEvent::BufferEdited),
97 ("editor2", EditorEvent::BufferEdited),
98 ]
99 );
100
101 // Undoing on editor 1 will emit an `Edited` event only for that editor.
102 editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
103 assert_eq!(
104 mem::take(&mut *events.borrow_mut()),
105 [
106 ("editor1", EditorEvent::Edited),
107 ("editor1", EditorEvent::BufferEdited),
108 ("editor2", EditorEvent::BufferEdited),
109 ]
110 );
111
112 // Redoing on editor 1 will emit an `Edited` event only for that editor.
113 editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
114 assert_eq!(
115 mem::take(&mut *events.borrow_mut()),
116 [
117 ("editor1", EditorEvent::Edited),
118 ("editor1", EditorEvent::BufferEdited),
119 ("editor2", EditorEvent::BufferEdited),
120 ]
121 );
122
123 // Undoing on editor 2 will emit an `Edited` event only for that editor.
124 editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
125 assert_eq!(
126 mem::take(&mut *events.borrow_mut()),
127 [
128 ("editor2", EditorEvent::Edited),
129 ("editor1", EditorEvent::BufferEdited),
130 ("editor2", EditorEvent::BufferEdited),
131 ]
132 );
133
134 // Redoing on editor 2 will emit an `Edited` event only for that editor.
135 editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
136 assert_eq!(
137 mem::take(&mut *events.borrow_mut()),
138 [
139 ("editor2", EditorEvent::Edited),
140 ("editor1", EditorEvent::BufferEdited),
141 ("editor2", EditorEvent::BufferEdited),
142 ]
143 );
144
145 // No event is emitted when the mutation is a no-op.
146 editor2.update(cx, |editor, cx| {
147 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
148
149 editor.backspace(&Backspace, cx);
150 });
151 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
152}
153
154#[gpui::test]
155fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
156 init_test(cx, |_| {});
157
158 let mut now = Instant::now();
159 let buffer = cx.build_model(|cx| language::Buffer::new(0, cx.entity_id().as_u64(), "123456"));
160 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
161 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
162 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
163
164 editor.update(cx, |editor, cx| {
165 editor.start_transaction_at(now, cx);
166 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
167
168 editor.insert("cd", cx);
169 editor.end_transaction_at(now, cx);
170 assert_eq!(editor.text(cx), "12cd56");
171 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
172
173 editor.start_transaction_at(now, cx);
174 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
175 editor.insert("e", cx);
176 editor.end_transaction_at(now, cx);
177 assert_eq!(editor.text(cx), "12cde6");
178 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
179
180 now += group_interval + Duration::from_millis(1);
181 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
182
183 // Simulate an edit in another editor
184 buffer.update(cx, |buffer, cx| {
185 buffer.start_transaction_at(now, cx);
186 buffer.edit([(0..1, "a")], None, cx);
187 buffer.edit([(1..1, "b")], None, cx);
188 buffer.end_transaction_at(now, cx);
189 });
190
191 assert_eq!(editor.text(cx), "ab2cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
193
194 // Last transaction happened past the group interval in a different editor.
195 // Undo it individually and don't restore selections.
196 editor.undo(&Undo, cx);
197 assert_eq!(editor.text(cx), "12cde6");
198 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
199
200 // First two transactions happened within the group interval in this editor.
201 // Undo them together and restore selections.
202 editor.undo(&Undo, cx);
203 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
204 assert_eq!(editor.text(cx), "123456");
205 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
206
207 // Redo the first two transactions together.
208 editor.redo(&Redo, cx);
209 assert_eq!(editor.text(cx), "12cde6");
210 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
211
212 // Redo the last transaction on its own.
213 editor.redo(&Redo, cx);
214 assert_eq!(editor.text(cx), "ab2cde6");
215 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
216
217 // Test empty transactions.
218 editor.start_transaction_at(now, cx);
219 editor.end_transaction_at(now, cx);
220 editor.undo(&Undo, cx);
221 assert_eq!(editor.text(cx), "12cde6");
222 });
223}
224
225#[gpui::test]
226fn test_ime_composition(cx: &mut TestAppContext) {
227 init_test(cx, |_| {});
228
229 let buffer = cx.build_model(|cx| {
230 let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "abcde");
231 // Ensure automatic grouping doesn't occur.
232 buffer.set_group_interval(Duration::ZERO);
233 buffer
234 });
235
236 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
237 cx.add_window(|cx| {
238 let mut editor = build_editor(buffer.clone(), cx);
239
240 // Start a new IME composition.
241 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
242 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
243 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
244 assert_eq!(editor.text(cx), "äbcde");
245 assert_eq!(
246 editor.marked_text_ranges(cx),
247 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
248 );
249
250 // Finalize IME composition.
251 editor.replace_text_in_range(None, "ā", cx);
252 assert_eq!(editor.text(cx), "ābcde");
253 assert_eq!(editor.marked_text_ranges(cx), None);
254
255 // IME composition edits are grouped and are undone/redone at once.
256 editor.undo(&Default::default(), cx);
257 assert_eq!(editor.text(cx), "abcde");
258 assert_eq!(editor.marked_text_ranges(cx), None);
259 editor.redo(&Default::default(), cx);
260 assert_eq!(editor.text(cx), "ābcde");
261 assert_eq!(editor.marked_text_ranges(cx), None);
262
263 // Start a new IME composition.
264 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
265 assert_eq!(
266 editor.marked_text_ranges(cx),
267 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
268 );
269
270 // Undoing during an IME composition cancels it.
271 editor.undo(&Default::default(), cx);
272 assert_eq!(editor.text(cx), "ābcde");
273 assert_eq!(editor.marked_text_ranges(cx), None);
274
275 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
276 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
277 assert_eq!(editor.text(cx), "ābcdè");
278 assert_eq!(
279 editor.marked_text_ranges(cx),
280 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
281 );
282
283 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
284 editor.replace_text_in_range(Some(4..999), "ę", cx);
285 assert_eq!(editor.text(cx), "ābcdę");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287
288 // Start a new IME composition with multiple cursors.
289 editor.change_selections(None, cx, |s| {
290 s.select_ranges([
291 OffsetUtf16(1)..OffsetUtf16(1),
292 OffsetUtf16(3)..OffsetUtf16(3),
293 OffsetUtf16(5)..OffsetUtf16(5),
294 ])
295 });
296 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
297 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
298 assert_eq!(
299 editor.marked_text_ranges(cx),
300 Some(vec![
301 OffsetUtf16(0)..OffsetUtf16(3),
302 OffsetUtf16(4)..OffsetUtf16(7),
303 OffsetUtf16(8)..OffsetUtf16(11)
304 ])
305 );
306
307 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
308 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
309 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
310 assert_eq!(
311 editor.marked_text_ranges(cx),
312 Some(vec![
313 OffsetUtf16(1)..OffsetUtf16(2),
314 OffsetUtf16(5)..OffsetUtf16(6),
315 OffsetUtf16(9)..OffsetUtf16(10)
316 ])
317 );
318
319 // Finalize IME composition with multiple cursors.
320 editor.replace_text_in_range(Some(9..10), "2", cx);
321 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
322 assert_eq!(editor.marked_text_ranges(cx), None);
323
324 editor
325 });
326}
327
328#[gpui::test]
329fn test_selection_with_mouse(cx: &mut TestAppContext) {
330 init_test(cx, |_| {});
331
332 let editor = cx.add_window(|cx| {
333 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
334 build_editor(buffer, cx)
335 });
336
337 editor.update(cx, |view, cx| {
338 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
339 });
340 assert_eq!(
341 editor
342 .update(cx, |view, cx| view.selections.display_ranges(cx))
343 .unwrap(),
344 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
345 );
346
347 editor.update(cx, |view, cx| {
348 view.update_selection(DisplayPoint::new(3, 3), 0, gpui::Point::<f32>::zero(), cx);
349 });
350
351 assert_eq!(
352 editor
353 .update(cx, |view, cx| view.selections.display_ranges(cx))
354 .unwrap(),
355 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
356 );
357
358 editor.update(cx, |view, cx| {
359 view.update_selection(DisplayPoint::new(1, 1), 0, gpui::Point::<f32>::zero(), cx);
360 });
361
362 assert_eq!(
363 editor
364 .update(cx, |view, cx| view.selections.display_ranges(cx))
365 .unwrap(),
366 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
367 );
368
369 editor.update(cx, |view, cx| {
370 view.end_selection(cx);
371 view.update_selection(DisplayPoint::new(3, 3), 0, gpui::Point::<f32>::zero(), cx);
372 });
373
374 assert_eq!(
375 editor
376 .update(cx, |view, cx| view.selections.display_ranges(cx))
377 .unwrap(),
378 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
379 );
380
381 editor.update(cx, |view, cx| {
382 view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
383 view.update_selection(DisplayPoint::new(0, 0), 0, gpui::Point::<f32>::zero(), cx);
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [
391 DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
392 DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
393 ]
394 );
395
396 editor.update(cx, |view, cx| {
397 view.end_selection(cx);
398 });
399
400 assert_eq!(
401 editor
402 .update(cx, |view, cx| view.selections.display_ranges(cx))
403 .unwrap(),
404 [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
405 );
406}
407
408#[gpui::test]
409fn test_canceling_pending_selection(cx: &mut TestAppContext) {
410 init_test(cx, |_| {});
411
412 let view = cx.add_window(|cx| {
413 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
414 build_editor(buffer, cx)
415 });
416
417 view.update(cx, |view, cx| {
418 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
419 assert_eq!(
420 view.selections.display_ranges(cx),
421 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
422 );
423 });
424
425 view.update(cx, |view, cx| {
426 view.update_selection(DisplayPoint::new(3, 3), 0, gpui::Point::<f32>::zero(), cx);
427 assert_eq!(
428 view.selections.display_ranges(cx),
429 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
430 );
431 });
432
433 view.update(cx, |view, cx| {
434 view.cancel(&Cancel, cx);
435 view.update_selection(DisplayPoint::new(1, 1), 0, gpui::Point::<f32>::zero(), cx);
436 assert_eq!(
437 view.selections.display_ranges(cx),
438 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
439 );
440 });
441}
442
443#[gpui::test]
444fn test_clone(cx: &mut TestAppContext) {
445 init_test(cx, |_| {});
446
447 let (text, selection_ranges) = marked_text_ranges(
448 indoc! {"
449 one
450 two
451 threeˇ
452 four
453 fiveˇ
454 "},
455 true,
456 );
457
458 let editor = cx.add_window(|cx| {
459 let buffer = MultiBuffer::build_simple(&text, cx);
460 build_editor(buffer, cx)
461 });
462
463 editor.update(cx, |editor, cx| {
464 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
465 editor.fold_ranges(
466 [
467 Point::new(1, 0)..Point::new(2, 0),
468 Point::new(3, 0)..Point::new(4, 0),
469 ],
470 true,
471 cx,
472 );
473 });
474
475 let cloned_editor = editor
476 .update(cx, |editor, cx| {
477 cx.open_window(Default::default(), |cx| {
478 cx.build_view(|cx| editor.clone(cx))
479 })
480 })
481 .unwrap();
482
483 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
484 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
485
486 assert_eq!(
487 cloned_editor
488 .update(cx, |e, cx| e.display_text(cx))
489 .unwrap(),
490 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
491 );
492 assert_eq!(
493 cloned_snapshot
494 .folds_in_range(0..text.len())
495 .collect::<Vec<_>>(),
496 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
497 );
498 assert_set_eq!(
499 cloned_editor
500 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
501 .unwrap(),
502 editor
503 .update(cx, |editor, cx| editor.selections.ranges(cx))
504 .unwrap()
505 );
506 assert_set_eq!(
507 cloned_editor
508 .update(cx, |e, cx| e.selections.display_ranges(cx))
509 .unwrap(),
510 editor
511 .update(cx, |e, cx| e.selections.display_ranges(cx))
512 .unwrap()
513 );
514}
515
516//todo!(editor navigate)
517#[gpui::test]
518async fn test_navigation_history(cx: &mut TestAppContext) {
519 init_test(cx, |_| {});
520
521 use workspace::item::Item;
522
523 let fs = FakeFs::new(cx.executor());
524 let project = Project::test(fs, [], cx).await;
525 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
526 let pane = workspace
527 .update(cx, |workspace, _| workspace.active_pane().clone())
528 .unwrap();
529
530 workspace.update(cx, |v, cx| {
531 cx.build_view(|cx| {
532 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
533 let mut editor = build_editor(buffer.clone(), cx);
534 let handle = cx.view();
535 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
536
537 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
538 editor.nav_history.as_mut().unwrap().pop_backward(cx)
539 }
540
541 // Move the cursor a small distance.
542 // Nothing is added to the navigation history.
543 editor.change_selections(None, cx, |s| {
544 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
545 });
546 editor.change_selections(None, cx, |s| {
547 s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
548 });
549 assert!(pop_history(&mut editor, cx).is_none());
550
551 // Move the cursor a large distance.
552 // The history can jump back to the previous position.
553 editor.change_selections(None, cx, |s| {
554 s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
555 });
556 let nav_entry = pop_history(&mut editor, cx).unwrap();
557 editor.navigate(nav_entry.data.unwrap(), cx);
558 assert_eq!(nav_entry.item.id(), cx.entity_id());
559 assert_eq!(
560 editor.selections.display_ranges(cx),
561 &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
562 );
563 assert!(pop_history(&mut editor, cx).is_none());
564
565 // Move the cursor a small distance via the mouse.
566 // Nothing is added to the navigation history.
567 editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
568 editor.end_selection(cx);
569 assert_eq!(
570 editor.selections.display_ranges(cx),
571 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
572 );
573 assert!(pop_history(&mut editor, cx).is_none());
574
575 // Move the cursor a large distance via the mouse.
576 // The history can jump back to the previous position.
577 editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
578 editor.end_selection(cx);
579 assert_eq!(
580 editor.selections.display_ranges(cx),
581 &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
582 );
583 let nav_entry = pop_history(&mut editor, cx).unwrap();
584 editor.navigate(nav_entry.data.unwrap(), cx);
585 assert_eq!(nav_entry.item.id(), cx.entity_id());
586 assert_eq!(
587 editor.selections.display_ranges(cx),
588 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
589 );
590 assert!(pop_history(&mut editor, cx).is_none());
591
592 // Set scroll position to check later
593 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
594 let original_scroll_position = editor.scroll_manager.anchor();
595
596 // Jump to the end of the document and adjust scroll
597 editor.move_to_end(&MoveToEnd, cx);
598 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
599 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
600
601 let nav_entry = pop_history(&mut editor, cx).unwrap();
602 editor.navigate(nav_entry.data.unwrap(), cx);
603 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
604
605 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
606 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
607 invalid_anchor.text_anchor.buffer_id = Some(999);
608 let invalid_point = Point::new(9999, 0);
609 editor.navigate(
610 Box::new(NavigationData {
611 cursor_anchor: invalid_anchor,
612 cursor_position: invalid_point,
613 scroll_anchor: ScrollAnchor {
614 anchor: invalid_anchor,
615 offset: Default::default(),
616 },
617 scroll_top_row: invalid_point.row,
618 }),
619 cx,
620 );
621 assert_eq!(
622 editor.selections.display_ranges(cx),
623 &[editor.max_point(cx)..editor.max_point(cx)]
624 );
625 assert_eq!(
626 editor.scroll_position(cx),
627 gpui::Point::new(0., editor.max_point(cx).row() as f32)
628 );
629
630 editor
631 })
632 });
633}
634
635#[gpui::test]
636fn test_cancel(cx: &mut TestAppContext) {
637 init_test(cx, |_| {});
638
639 let view = cx.add_window(|cx| {
640 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
641 build_editor(buffer, cx)
642 });
643
644 view.update(cx, |view, cx| {
645 view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
646 view.update_selection(DisplayPoint::new(1, 1), 0, gpui::Point::<f32>::zero(), cx);
647 view.end_selection(cx);
648
649 view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
650 view.update_selection(DisplayPoint::new(0, 3), 0, gpui::Point::<f32>::zero(), cx);
651 view.end_selection(cx);
652 assert_eq!(
653 view.selections.display_ranges(cx),
654 [
655 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
656 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
657 ]
658 );
659 });
660
661 view.update(cx, |view, cx| {
662 view.cancel(&Cancel, cx);
663 assert_eq!(
664 view.selections.display_ranges(cx),
665 [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
666 );
667 });
668
669 view.update(cx, |view, cx| {
670 view.cancel(&Cancel, cx);
671 assert_eq!(
672 view.selections.display_ranges(cx),
673 [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
674 );
675 });
676}
677
678#[gpui::test]
679fn test_fold_action(cx: &mut TestAppContext) {
680 init_test(cx, |_| {});
681
682 let view = cx.add_window(|cx| {
683 let buffer = MultiBuffer::build_simple(
684 &"
685 impl Foo {
686 // Hello!
687
688 fn a() {
689 1
690 }
691
692 fn b() {
693 2
694 }
695
696 fn c() {
697 3
698 }
699 }
700 "
701 .unindent(),
702 cx,
703 );
704 build_editor(buffer.clone(), cx)
705 });
706
707 view.update(cx, |view, cx| {
708 view.change_selections(None, cx, |s| {
709 s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
710 });
711 view.fold(&Fold, cx);
712 assert_eq!(
713 view.display_text(cx),
714 "
715 impl Foo {
716 // Hello!
717
718 fn a() {
719 1
720 }
721
722 fn b() {⋯
723 }
724
725 fn c() {⋯
726 }
727 }
728 "
729 .unindent(),
730 );
731
732 view.fold(&Fold, cx);
733 assert_eq!(
734 view.display_text(cx),
735 "
736 impl Foo {⋯
737 }
738 "
739 .unindent(),
740 );
741
742 view.unfold_lines(&UnfoldLines, cx);
743 assert_eq!(
744 view.display_text(cx),
745 "
746 impl Foo {
747 // Hello!
748
749 fn a() {
750 1
751 }
752
753 fn b() {⋯
754 }
755
756 fn c() {⋯
757 }
758 }
759 "
760 .unindent(),
761 );
762
763 view.unfold_lines(&UnfoldLines, cx);
764 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
765 });
766}
767
768#[gpui::test]
769fn test_move_cursor(cx: &mut TestAppContext) {
770 init_test(cx, |_| {});
771
772 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
773 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
774
775 buffer.update(cx, |buffer, cx| {
776 buffer.edit(
777 vec![
778 (Point::new(1, 0)..Point::new(1, 0), "\t"),
779 (Point::new(1, 1)..Point::new(1, 1), "\t"),
780 ],
781 None,
782 cx,
783 );
784 });
785 view.update(cx, |view, cx| {
786 assert_eq!(
787 view.selections.display_ranges(cx),
788 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
789 );
790
791 view.move_down(&MoveDown, cx);
792 assert_eq!(
793 view.selections.display_ranges(cx),
794 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
795 );
796
797 view.move_right(&MoveRight, cx);
798 assert_eq!(
799 view.selections.display_ranges(cx),
800 &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
801 );
802
803 view.move_left(&MoveLeft, cx);
804 assert_eq!(
805 view.selections.display_ranges(cx),
806 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
807 );
808
809 view.move_up(&MoveUp, cx);
810 assert_eq!(
811 view.selections.display_ranges(cx),
812 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
813 );
814
815 view.move_to_end(&MoveToEnd, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
819 );
820
821 view.move_to_beginning(&MoveToBeginning, cx);
822 assert_eq!(
823 view.selections.display_ranges(cx),
824 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
825 );
826
827 view.change_selections(None, cx, |s| {
828 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
829 });
830 view.select_to_beginning(&SelectToBeginning, cx);
831 assert_eq!(
832 view.selections.display_ranges(cx),
833 &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
834 );
835
836 view.select_to_end(&SelectToEnd, cx);
837 assert_eq!(
838 view.selections.display_ranges(cx),
839 &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
840 );
841 });
842}
843
844#[gpui::test]
845fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
846 init_test(cx, |_| {});
847
848 let view = cx.add_window(|cx| {
849 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
850 build_editor(buffer.clone(), cx)
851 });
852
853 assert_eq!('ⓐ'.len_utf8(), 3);
854 assert_eq!('α'.len_utf8(), 2);
855
856 view.update(cx, |view, cx| {
857 view.fold_ranges(
858 vec![
859 Point::new(0, 6)..Point::new(0, 12),
860 Point::new(1, 2)..Point::new(1, 4),
861 Point::new(2, 4)..Point::new(2, 8),
862 ],
863 true,
864 cx,
865 );
866 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
867
868 view.move_right(&MoveRight, cx);
869 assert_eq!(
870 view.selections.display_ranges(cx),
871 &[empty_range(0, "ⓐ".len())]
872 );
873 view.move_right(&MoveRight, cx);
874 assert_eq!(
875 view.selections.display_ranges(cx),
876 &[empty_range(0, "ⓐⓑ".len())]
877 );
878 view.move_right(&MoveRight, cx);
879 assert_eq!(
880 view.selections.display_ranges(cx),
881 &[empty_range(0, "ⓐⓑ⋯".len())]
882 );
883
884 view.move_down(&MoveDown, cx);
885 assert_eq!(
886 view.selections.display_ranges(cx),
887 &[empty_range(1, "ab⋯e".len())]
888 );
889 view.move_left(&MoveLeft, cx);
890 assert_eq!(
891 view.selections.display_ranges(cx),
892 &[empty_range(1, "ab⋯".len())]
893 );
894 view.move_left(&MoveLeft, cx);
895 assert_eq!(
896 view.selections.display_ranges(cx),
897 &[empty_range(1, "ab".len())]
898 );
899 view.move_left(&MoveLeft, cx);
900 assert_eq!(
901 view.selections.display_ranges(cx),
902 &[empty_range(1, "a".len())]
903 );
904
905 view.move_down(&MoveDown, cx);
906 assert_eq!(
907 view.selections.display_ranges(cx),
908 &[empty_range(2, "α".len())]
909 );
910 view.move_right(&MoveRight, cx);
911 assert_eq!(
912 view.selections.display_ranges(cx),
913 &[empty_range(2, "αβ".len())]
914 );
915 view.move_right(&MoveRight, cx);
916 assert_eq!(
917 view.selections.display_ranges(cx),
918 &[empty_range(2, "αβ⋯".len())]
919 );
920 view.move_right(&MoveRight, cx);
921 assert_eq!(
922 view.selections.display_ranges(cx),
923 &[empty_range(2, "αβ⋯ε".len())]
924 );
925
926 view.move_up(&MoveUp, cx);
927 assert_eq!(
928 view.selections.display_ranges(cx),
929 &[empty_range(1, "ab⋯e".len())]
930 );
931 view.move_down(&MoveDown, cx);
932 assert_eq!(
933 view.selections.display_ranges(cx),
934 &[empty_range(2, "αβ⋯ε".len())]
935 );
936 view.move_up(&MoveUp, cx);
937 assert_eq!(
938 view.selections.display_ranges(cx),
939 &[empty_range(1, "ab⋯e".len())]
940 );
941
942 view.move_up(&MoveUp, cx);
943 assert_eq!(
944 view.selections.display_ranges(cx),
945 &[empty_range(0, "ⓐⓑ".len())]
946 );
947 view.move_left(&MoveLeft, cx);
948 assert_eq!(
949 view.selections.display_ranges(cx),
950 &[empty_range(0, "ⓐ".len())]
951 );
952 view.move_left(&MoveLeft, cx);
953 assert_eq!(
954 view.selections.display_ranges(cx),
955 &[empty_range(0, "".len())]
956 );
957 });
958}
959
960//todo!(finish editor tests)
961// #[gpui::test]
962// fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
963// init_test(cx, |_| {});
964
965// let view = cx.add_window(|cx| {
966// let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
967// build_editor(buffer.clone(), cx)
968// });
969// view.update(cx, |view, cx| {
970// view.change_selections(None, cx, |s| {
971// s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
972// });
973// view.move_down(&MoveDown, cx);
974// assert_eq!(
975// view.selections.display_ranges(cx),
976// &[empty_range(1, "abcd".len())]
977// );
978
979// view.move_down(&MoveDown, cx);
980// assert_eq!(
981// view.selections.display_ranges(cx),
982// &[empty_range(2, "αβγ".len())]
983// );
984
985// view.move_down(&MoveDown, cx);
986// assert_eq!(
987// view.selections.display_ranges(cx),
988// &[empty_range(3, "abcd".len())]
989// );
990
991// view.move_down(&MoveDown, cx);
992// assert_eq!(
993// view.selections.display_ranges(cx),
994// &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
995// );
996
997// view.move_up(&MoveUp, cx);
998// assert_eq!(
999// view.selections.display_ranges(cx),
1000// &[empty_range(3, "abcd".len())]
1001// );
1002
1003// view.move_up(&MoveUp, cx);
1004// assert_eq!(
1005// view.selections.display_ranges(cx),
1006// &[empty_range(2, "αβγ".len())]
1007// );
1008// });
1009// }
1010
1011#[gpui::test]
1012fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1013 init_test(cx, |_| {});
1014
1015 let view = cx.add_window(|cx| {
1016 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1017 build_editor(buffer, cx)
1018 });
1019 view.update(cx, |view, cx| {
1020 view.change_selections(None, cx, |s| {
1021 s.select_display_ranges([
1022 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
1023 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1024 ]);
1025 });
1026 });
1027
1028 view.update(cx, |view, cx| {
1029 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1030 assert_eq!(
1031 view.selections.display_ranges(cx),
1032 &[
1033 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1034 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1035 ]
1036 );
1037 });
1038
1039 view.update(cx, |view, cx| {
1040 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1041 assert_eq!(
1042 view.selections.display_ranges(cx),
1043 &[
1044 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1045 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1046 ]
1047 );
1048 });
1049
1050 view.update(cx, |view, cx| {
1051 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1052 assert_eq!(
1053 view.selections.display_ranges(cx),
1054 &[
1055 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1056 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1057 ]
1058 );
1059 });
1060
1061 view.update(cx, |view, cx| {
1062 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1063 assert_eq!(
1064 view.selections.display_ranges(cx),
1065 &[
1066 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1067 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1068 ]
1069 );
1070 });
1071
1072 // Moving to the end of line again is a no-op.
1073 view.update(cx, |view, cx| {
1074 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1075 assert_eq!(
1076 view.selections.display_ranges(cx),
1077 &[
1078 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1079 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1080 ]
1081 );
1082 });
1083
1084 view.update(cx, |view, cx| {
1085 view.move_left(&MoveLeft, cx);
1086 view.select_to_beginning_of_line(
1087 &SelectToBeginningOfLine {
1088 stop_at_soft_wraps: true,
1089 },
1090 cx,
1091 );
1092 assert_eq!(
1093 view.selections.display_ranges(cx),
1094 &[
1095 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1096 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1097 ]
1098 );
1099 });
1100
1101 view.update(cx, |view, cx| {
1102 view.select_to_beginning_of_line(
1103 &SelectToBeginningOfLine {
1104 stop_at_soft_wraps: true,
1105 },
1106 cx,
1107 );
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[
1111 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1112 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
1113 ]
1114 );
1115 });
1116
1117 view.update(cx, |view, cx| {
1118 view.select_to_beginning_of_line(
1119 &SelectToBeginningOfLine {
1120 stop_at_soft_wraps: true,
1121 },
1122 cx,
1123 );
1124 assert_eq!(
1125 view.selections.display_ranges(cx),
1126 &[
1127 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1128 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1129 ]
1130 );
1131 });
1132
1133 view.update(cx, |view, cx| {
1134 view.select_to_end_of_line(
1135 &SelectToEndOfLine {
1136 stop_at_soft_wraps: true,
1137 },
1138 cx,
1139 );
1140 assert_eq!(
1141 view.selections.display_ranges(cx),
1142 &[
1143 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
1144 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
1145 ]
1146 );
1147 });
1148
1149 view.update(cx, |view, cx| {
1150 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1151 assert_eq!(view.display_text(cx), "ab\n de");
1152 assert_eq!(
1153 view.selections.display_ranges(cx),
1154 &[
1155 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1156 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1157 ]
1158 );
1159 });
1160
1161 view.update(cx, |view, cx| {
1162 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1163 assert_eq!(view.display_text(cx), "\n");
1164 assert_eq!(
1165 view.selections.display_ranges(cx),
1166 &[
1167 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1168 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1169 ]
1170 );
1171 });
1172}
1173
1174#[gpui::test]
1175fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1176 init_test(cx, |_| {});
1177
1178 let view = cx.add_window(|cx| {
1179 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1180 build_editor(buffer, cx)
1181 });
1182 view.update(cx, |view, cx| {
1183 view.change_selections(None, cx, |s| {
1184 s.select_display_ranges([
1185 DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
1186 DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
1187 ])
1188 });
1189
1190 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1191 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1192
1193 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1194 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1195
1196 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1197 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1198
1199 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1200 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1201
1202 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1203 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1204
1205 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1206 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1207
1208 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1209 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1210
1211 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1212 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1213
1214 view.move_right(&MoveRight, cx);
1215 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1216 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1217
1218 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1219 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1220
1221 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1222 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1223 });
1224}
1225
1226//todo!(finish editor tests)
1227// #[gpui::test]
1228// fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1229// init_test(cx, |_| {});
1230
1231// let view = cx.add_window(|cx| {
1232// let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1233// build_editor(buffer, cx)
1234// });
1235
1236// view.update(cx, |view, cx| {
1237// view.set_wrap_width(Some(140.0.into()), cx);
1238// assert_eq!(
1239// view.display_text(cx),
1240// "use one::{\n two::three::\n four::five\n};"
1241// );
1242
1243// view.change_selections(None, cx, |s| {
1244// s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
1245// });
1246
1247// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1248// assert_eq!(
1249// view.selections.display_ranges(cx),
1250// &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
1251// );
1252
1253// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1254// assert_eq!(
1255// view.selections.display_ranges(cx),
1256// &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1257// );
1258
1259// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1260// assert_eq!(
1261// view.selections.display_ranges(cx),
1262// &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1263// );
1264
1265// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1266// assert_eq!(
1267// view.selections.display_ranges(cx),
1268// &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
1269// );
1270
1271// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1272// assert_eq!(
1273// view.selections.display_ranges(cx),
1274// &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1275// );
1276
1277// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1278// assert_eq!(
1279// view.selections.display_ranges(cx),
1280// &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1281// );
1282// });
1283// }
1284
1285//todo!(simulate_resize)
1286// #[gpui::test]
1287// async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1288// init_test(cx, |_| {});
1289// let mut cx = EditorTestContext::new(cx).await;
1290
1291// let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1292// let window = cx.window;
1293// window.simulate_resize(gpui::Point::new(100., 4. * line_height), &mut cx);
1294
1295// cx.set_state(
1296// &r#"ˇone
1297// two
1298
1299// three
1300// fourˇ
1301// five
1302
1303// six"#
1304// .unindent(),
1305// );
1306
1307// cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1308// cx.assert_editor_state(
1309// &r#"one
1310// two
1311// ˇ
1312// three
1313// four
1314// five
1315// ˇ
1316// six"#
1317// .unindent(),
1318// );
1319
1320// cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1321// cx.assert_editor_state(
1322// &r#"one
1323// two
1324
1325// three
1326// four
1327// five
1328// ˇ
1329// sixˇ"#
1330// .unindent(),
1331// );
1332
1333// cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1334// cx.assert_editor_state(
1335// &r#"one
1336// two
1337
1338// three
1339// four
1340// five
1341
1342// sixˇ"#
1343// .unindent(),
1344// );
1345
1346// cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1347// cx.assert_editor_state(
1348// &r#"one
1349// two
1350
1351// three
1352// four
1353// five
1354// ˇ
1355// six"#
1356// .unindent(),
1357// );
1358
1359// cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1360// cx.assert_editor_state(
1361// &r#"one
1362// two
1363// ˇ
1364// three
1365// four
1366// five
1367
1368// six"#
1369// .unindent(),
1370// );
1371
1372// cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1373// cx.assert_editor_state(
1374// &r#"ˇone
1375// two
1376
1377// three
1378// four
1379// five
1380
1381// six"#
1382// .unindent(),
1383// );
1384// }
1385
1386// #[gpui::test]
1387// async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1388// init_test(cx, |_| {});
1389// let mut cx = EditorTestContext::new(cx).await;
1390// let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1391// let window = cx.window;
1392// window.simulate_resize(Point::new(1000., 4. * line_height + 0.5), &mut cx);
1393
1394// cx.set_state(
1395// &r#"ˇone
1396// two
1397// three
1398// four
1399// five
1400// six
1401// seven
1402// eight
1403// nine
1404// ten
1405// "#,
1406// );
1407
1408// cx.update_editor(|editor, cx| {
1409// assert_eq!(
1410// editor.snapshot(cx).scroll_position(),
1411// gpui::Point::new(0., 0.)
1412// );
1413// editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1414// assert_eq!(
1415// editor.snapshot(cx).scroll_position(),
1416// gpui::Point::new(0., 3.)
1417// );
1418// editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1419// assert_eq!(
1420// editor.snapshot(cx).scroll_position(),
1421// gpui::Point::new(0., 6.)
1422// );
1423// editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1424// assert_eq!(
1425// editor.snapshot(cx).scroll_position(),
1426// gpui::Point::new(0., 3.)
1427// );
1428
1429// editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1430// assert_eq!(
1431// editor.snapshot(cx).scroll_position(),
1432// gpui::Point::new(0., 1.)
1433// );
1434// editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1435// assert_eq!(
1436// editor.snapshot(cx).scroll_position(),
1437// gpui::Point::new(0., 3.)
1438// );
1439// });
1440// }
1441
1442// #[gpui::test]
1443// async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1444// init_test(cx, |_| {});
1445// let mut cx = EditorTestContext::new(cx).await;
1446
1447// let line_height = cx.update_editor(|editor, cx| {
1448// editor.set_vertical_scroll_margin(2, cx);
1449// editor.style(cx).text.line_height(cx.font_cache())
1450// });
1451
1452// let window = cx.window;
1453// window.simulate_resize(gpui::Point::new(1000., 6.0 * line_height), &mut cx);
1454
1455// cx.set_state(
1456// &r#"ˇone
1457// two
1458// three
1459// four
1460// five
1461// six
1462// seven
1463// eight
1464// nine
1465// ten
1466// "#,
1467// );
1468// cx.update_editor(|editor, cx| {
1469// assert_eq!(
1470// editor.snapshot(cx).scroll_position(),
1471// gpui::Point::new(0., 0.0)
1472// );
1473// });
1474
1475// // Add a cursor below the visible area. Since both cursors cannot fit
1476// // on screen, the editor autoscrolls to reveal the newest cursor, and
1477// // allows the vertical scroll margin below that cursor.
1478// cx.update_editor(|editor, cx| {
1479// editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1480// selections.select_ranges([
1481// Point::new(0, 0)..Point::new(0, 0),
1482// Point::new(6, 0)..Point::new(6, 0),
1483// ]);
1484// })
1485// });
1486// cx.update_editor(|editor, cx| {
1487// assert_eq!(
1488// editor.snapshot(cx).scroll_position(),
1489// gpui::Point::new(0., 3.0)
1490// );
1491// });
1492
1493// // Move down. The editor cursor scrolls down to track the newest cursor.
1494// cx.update_editor(|editor, cx| {
1495// editor.move_down(&Default::default(), cx);
1496// });
1497// cx.update_editor(|editor, cx| {
1498// assert_eq!(
1499// editor.snapshot(cx).scroll_position(),
1500// gpui::Point::new(0., 4.0)
1501// );
1502// });
1503
1504// // Add a cursor above the visible area. Since both cursors fit on screen,
1505// // the editor scrolls to show both.
1506// cx.update_editor(|editor, cx| {
1507// editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1508// selections.select_ranges([
1509// Point::new(1, 0)..Point::new(1, 0),
1510// Point::new(6, 0)..Point::new(6, 0),
1511// ]);
1512// })
1513// });
1514// cx.update_editor(|editor, cx| {
1515// assert_eq!(
1516// editor.snapshot(cx).scroll_position(),
1517// gpui::Point::new(0., 1.0)
1518// );
1519// });
1520// }
1521
1522// #[gpui::test]
1523// async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1524// init_test(cx, |_| {});
1525// let mut cx = EditorTestContext::new(cx).await;
1526
1527// let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1528// let window = cx.window;
1529// window.simulate_resize(gpui::Point::new(100., 4. * line_height), &mut cx);
1530
1531// cx.set_state(
1532// &r#"
1533// ˇone
1534// two
1535// threeˇ
1536// four
1537// five
1538// six
1539// seven
1540// eight
1541// nine
1542// ten
1543// "#
1544// .unindent(),
1545// );
1546
1547// cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1548// cx.assert_editor_state(
1549// &r#"
1550// one
1551// two
1552// three
1553// ˇfour
1554// five
1555// sixˇ
1556// seven
1557// eight
1558// nine
1559// ten
1560// "#
1561// .unindent(),
1562// );
1563
1564// cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1565// cx.assert_editor_state(
1566// &r#"
1567// one
1568// two
1569// three
1570// four
1571// five
1572// six
1573// ˇseven
1574// eight
1575// nineˇ
1576// ten
1577// "#
1578// .unindent(),
1579// );
1580
1581// cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1582// cx.assert_editor_state(
1583// &r#"
1584// one
1585// two
1586// three
1587// ˇfour
1588// five
1589// sixˇ
1590// seven
1591// eight
1592// nine
1593// ten
1594// "#
1595// .unindent(),
1596// );
1597
1598// cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1599// cx.assert_editor_state(
1600// &r#"
1601// ˇone
1602// two
1603// threeˇ
1604// four
1605// five
1606// six
1607// seven
1608// eight
1609// nine
1610// ten
1611// "#
1612// .unindent(),
1613// );
1614
1615// // Test select collapsing
1616// cx.update_editor(|editor, cx| {
1617// editor.move_page_down(&MovePageDown::default(), cx);
1618// editor.move_page_down(&MovePageDown::default(), cx);
1619// editor.move_page_down(&MovePageDown::default(), cx);
1620// });
1621// cx.assert_editor_state(
1622// &r#"
1623// one
1624// two
1625// three
1626// four
1627// five
1628// six
1629// seven
1630// eight
1631// nine
1632// ˇten
1633// ˇ"#
1634// .unindent(),
1635// );
1636// }
1637
1638#[gpui::test]
1639async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1640 init_test(cx, |_| {});
1641 let mut cx = EditorTestContext::new(cx).await;
1642 cx.set_state("one «two threeˇ» four");
1643 cx.update_editor(|editor, cx| {
1644 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1645 assert_eq!(editor.text(cx), " four");
1646 });
1647}
1648
1649#[gpui::test]
1650fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1651 init_test(cx, |_| {});
1652
1653 let view = cx.add_window(|cx| {
1654 let buffer = MultiBuffer::build_simple("one two three four", cx);
1655 build_editor(buffer.clone(), cx)
1656 });
1657
1658 view.update(cx, |view, cx| {
1659 view.change_selections(None, cx, |s| {
1660 s.select_display_ranges([
1661 // an empty selection - the preceding word fragment is deleted
1662 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1663 // characters selected - they are deleted
1664 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1665 ])
1666 });
1667 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1668 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1669 });
1670
1671 view.update(cx, |view, cx| {
1672 view.change_selections(None, cx, |s| {
1673 s.select_display_ranges([
1674 // an empty selection - the following word fragment is deleted
1675 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1676 // characters selected - they are deleted
1677 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1678 ])
1679 });
1680 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1681 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1682 });
1683}
1684
1685#[gpui::test]
1686fn test_newline(cx: &mut TestAppContext) {
1687 init_test(cx, |_| {});
1688
1689 let view = cx.add_window(|cx| {
1690 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1691 build_editor(buffer.clone(), cx)
1692 });
1693
1694 view.update(cx, |view, cx| {
1695 view.change_selections(None, cx, |s| {
1696 s.select_display_ranges([
1697 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1698 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1699 DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1700 ])
1701 });
1702
1703 view.newline(&Newline, cx);
1704 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1705 });
1706}
1707
1708#[gpui::test]
1709fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1710 init_test(cx, |_| {});
1711
1712 let editor = cx.add_window(|cx| {
1713 let buffer = MultiBuffer::build_simple(
1714 "
1715 a
1716 b(
1717 X
1718 )
1719 c(
1720 X
1721 )
1722 "
1723 .unindent()
1724 .as_str(),
1725 cx,
1726 );
1727 let mut editor = build_editor(buffer.clone(), cx);
1728 editor.change_selections(None, cx, |s| {
1729 s.select_ranges([
1730 Point::new(2, 4)..Point::new(2, 5),
1731 Point::new(5, 4)..Point::new(5, 5),
1732 ])
1733 });
1734 editor
1735 });
1736
1737 editor.update(cx, |editor, cx| {
1738 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1739 editor.buffer.update(cx, |buffer, cx| {
1740 buffer.edit(
1741 [
1742 (Point::new(1, 2)..Point::new(3, 0), ""),
1743 (Point::new(4, 2)..Point::new(6, 0), ""),
1744 ],
1745 None,
1746 cx,
1747 );
1748 assert_eq!(
1749 buffer.read(cx).text(),
1750 "
1751 a
1752 b()
1753 c()
1754 "
1755 .unindent()
1756 );
1757 });
1758 assert_eq!(
1759 editor.selections.ranges(cx),
1760 &[
1761 Point::new(1, 2)..Point::new(1, 2),
1762 Point::new(2, 2)..Point::new(2, 2),
1763 ],
1764 );
1765
1766 editor.newline(&Newline, cx);
1767 assert_eq!(
1768 editor.text(cx),
1769 "
1770 a
1771 b(
1772 )
1773 c(
1774 )
1775 "
1776 .unindent()
1777 );
1778
1779 // The selections are moved after the inserted newlines
1780 assert_eq!(
1781 editor.selections.ranges(cx),
1782 &[
1783 Point::new(2, 0)..Point::new(2, 0),
1784 Point::new(4, 0)..Point::new(4, 0),
1785 ],
1786 );
1787 });
1788}
1789
1790#[gpui::test]
1791async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1792 init_test(cx, |settings| {
1793 settings.defaults.tab_size = NonZeroU32::new(4)
1794 });
1795
1796 let language = Arc::new(
1797 Language::new(
1798 LanguageConfig::default(),
1799 Some(tree_sitter_rust::language()),
1800 )
1801 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1802 .unwrap(),
1803 );
1804
1805 let mut cx = EditorTestContext::new(cx).await;
1806 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1807 cx.set_state(indoc! {"
1808 const a: ˇA = (
1809 (ˇ
1810 «const_functionˇ»(ˇ),
1811 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1812 )ˇ
1813 ˇ);ˇ
1814 "});
1815
1816 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1817 cx.assert_editor_state(indoc! {"
1818 ˇ
1819 const a: A = (
1820 ˇ
1821 (
1822 ˇ
1823 ˇ
1824 const_function(),
1825 ˇ
1826 ˇ
1827 ˇ
1828 ˇ
1829 something_else,
1830 ˇ
1831 )
1832 ˇ
1833 ˇ
1834 );
1835 "});
1836}
1837
1838#[gpui::test]
1839async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1840 init_test(cx, |settings| {
1841 settings.defaults.tab_size = NonZeroU32::new(4)
1842 });
1843
1844 let language = Arc::new(
1845 Language::new(
1846 LanguageConfig::default(),
1847 Some(tree_sitter_rust::language()),
1848 )
1849 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1850 .unwrap(),
1851 );
1852
1853 let mut cx = EditorTestContext::new(cx).await;
1854 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1855 cx.set_state(indoc! {"
1856 const a: ˇA = (
1857 (ˇ
1858 «const_functionˇ»(ˇ),
1859 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1860 )ˇ
1861 ˇ);ˇ
1862 "});
1863
1864 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1865 cx.assert_editor_state(indoc! {"
1866 const a: A = (
1867 ˇ
1868 (
1869 ˇ
1870 const_function(),
1871 ˇ
1872 ˇ
1873 something_else,
1874 ˇ
1875 ˇ
1876 ˇ
1877 ˇ
1878 )
1879 ˇ
1880 );
1881 ˇ
1882 ˇ
1883 "});
1884}
1885
1886#[gpui::test]
1887async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
1888 init_test(cx, |settings| {
1889 settings.defaults.tab_size = NonZeroU32::new(4)
1890 });
1891
1892 let language = Arc::new(Language::new(
1893 LanguageConfig {
1894 line_comment: Some("//".into()),
1895 ..LanguageConfig::default()
1896 },
1897 None,
1898 ));
1899 {
1900 let mut cx = EditorTestContext::new(cx).await;
1901 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1902 cx.set_state(indoc! {"
1903 // Fooˇ
1904 "});
1905
1906 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1907 cx.assert_editor_state(indoc! {"
1908 // Foo
1909 //ˇ
1910 "});
1911 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
1912 cx.set_state(indoc! {"
1913 ˇ// Foo
1914 "});
1915 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1916 cx.assert_editor_state(indoc! {"
1917
1918 ˇ// Foo
1919 "});
1920 }
1921 // Ensure that comment continuations can be disabled.
1922 update_test_language_settings(cx, |settings| {
1923 settings.defaults.extend_comment_on_newline = Some(false);
1924 });
1925 let mut cx = EditorTestContext::new(cx).await;
1926 cx.set_state(indoc! {"
1927 // Fooˇ
1928 "});
1929 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1930 cx.assert_editor_state(indoc! {"
1931 // Foo
1932 ˇ
1933 "});
1934}
1935
1936#[gpui::test]
1937fn test_insert_with_old_selections(cx: &mut TestAppContext) {
1938 init_test(cx, |_| {});
1939
1940 let editor = cx.add_window(|cx| {
1941 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1942 let mut editor = build_editor(buffer.clone(), cx);
1943 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1944 editor
1945 });
1946
1947 editor.update(cx, |editor, cx| {
1948 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1949 editor.buffer.update(cx, |buffer, cx| {
1950 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
1951 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
1952 });
1953 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
1954
1955 editor.insert("Z", cx);
1956 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
1957
1958 // The selections are moved after the inserted characters
1959 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
1960 });
1961}
1962
1963#[gpui::test]
1964async fn test_tab(cx: &mut gpui::TestAppContext) {
1965 init_test(cx, |settings| {
1966 settings.defaults.tab_size = NonZeroU32::new(3)
1967 });
1968
1969 let mut cx = EditorTestContext::new(cx).await;
1970 cx.set_state(indoc! {"
1971 ˇabˇc
1972 ˇ🏀ˇ🏀ˇefg
1973 dˇ
1974 "});
1975 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1976 cx.assert_editor_state(indoc! {"
1977 ˇab ˇc
1978 ˇ🏀 ˇ🏀 ˇefg
1979 d ˇ
1980 "});
1981
1982 cx.set_state(indoc! {"
1983 a
1984 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1985 "});
1986 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1987 cx.assert_editor_state(indoc! {"
1988 a
1989 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1990 "});
1991}
1992
1993#[gpui::test]
1994async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
1995 init_test(cx, |_| {});
1996
1997 let mut cx = EditorTestContext::new(cx).await;
1998 let language = Arc::new(
1999 Language::new(
2000 LanguageConfig::default(),
2001 Some(tree_sitter_rust::language()),
2002 )
2003 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2004 .unwrap(),
2005 );
2006 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2007
2008 // cursors that are already at the suggested indent level insert
2009 // a soft tab. cursors that are to the left of the suggested indent
2010 // auto-indent their line.
2011 cx.set_state(indoc! {"
2012 ˇ
2013 const a: B = (
2014 c(
2015 d(
2016 ˇ
2017 )
2018 ˇ
2019 ˇ )
2020 );
2021 "});
2022 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2023 cx.assert_editor_state(indoc! {"
2024 ˇ
2025 const a: B = (
2026 c(
2027 d(
2028 ˇ
2029 )
2030 ˇ
2031 ˇ)
2032 );
2033 "});
2034
2035 // handle auto-indent when there are multiple cursors on the same line
2036 cx.set_state(indoc! {"
2037 const a: B = (
2038 c(
2039 ˇ ˇ
2040 ˇ )
2041 );
2042 "});
2043 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2044 cx.assert_editor_state(indoc! {"
2045 const a: B = (
2046 c(
2047 ˇ
2048 ˇ)
2049 );
2050 "});
2051}
2052
2053#[gpui::test]
2054async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2055 init_test(cx, |settings| {
2056 settings.defaults.tab_size = NonZeroU32::new(4)
2057 });
2058
2059 let language = Arc::new(
2060 Language::new(
2061 LanguageConfig::default(),
2062 Some(tree_sitter_rust::language()),
2063 )
2064 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2065 .unwrap(),
2066 );
2067
2068 let mut cx = EditorTestContext::new(cx).await;
2069 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2070 cx.set_state(indoc! {"
2071 fn a() {
2072 if b {
2073 \t ˇc
2074 }
2075 }
2076 "});
2077
2078 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2079 cx.assert_editor_state(indoc! {"
2080 fn a() {
2081 if b {
2082 ˇc
2083 }
2084 }
2085 "});
2086}
2087
2088#[gpui::test]
2089async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2090 init_test(cx, |settings| {
2091 settings.defaults.tab_size = NonZeroU32::new(4);
2092 });
2093
2094 let mut cx = EditorTestContext::new(cx).await;
2095
2096 cx.set_state(indoc! {"
2097 «oneˇ» «twoˇ»
2098 three
2099 four
2100 "});
2101 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2102 cx.assert_editor_state(indoc! {"
2103 «oneˇ» «twoˇ»
2104 three
2105 four
2106 "});
2107
2108 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2109 cx.assert_editor_state(indoc! {"
2110 «oneˇ» «twoˇ»
2111 three
2112 four
2113 "});
2114
2115 // select across line ending
2116 cx.set_state(indoc! {"
2117 one two
2118 t«hree
2119 ˇ» four
2120 "});
2121 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2122 cx.assert_editor_state(indoc! {"
2123 one two
2124 t«hree
2125 ˇ» four
2126 "});
2127
2128 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2129 cx.assert_editor_state(indoc! {"
2130 one two
2131 t«hree
2132 ˇ» four
2133 "});
2134
2135 // Ensure that indenting/outdenting works when the cursor is at column 0.
2136 cx.set_state(indoc! {"
2137 one two
2138 ˇthree
2139 four
2140 "});
2141 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2142 cx.assert_editor_state(indoc! {"
2143 one two
2144 ˇthree
2145 four
2146 "});
2147
2148 cx.set_state(indoc! {"
2149 one two
2150 ˇ three
2151 four
2152 "});
2153 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2154 cx.assert_editor_state(indoc! {"
2155 one two
2156 ˇthree
2157 four
2158 "});
2159}
2160
2161#[gpui::test]
2162async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2163 init_test(cx, |settings| {
2164 settings.defaults.hard_tabs = Some(true);
2165 });
2166
2167 let mut cx = EditorTestContext::new(cx).await;
2168
2169 // select two ranges on one line
2170 cx.set_state(indoc! {"
2171 «oneˇ» «twoˇ»
2172 three
2173 four
2174 "});
2175 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2176 cx.assert_editor_state(indoc! {"
2177 \t«oneˇ» «twoˇ»
2178 three
2179 four
2180 "});
2181 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2182 cx.assert_editor_state(indoc! {"
2183 \t\t«oneˇ» «twoˇ»
2184 three
2185 four
2186 "});
2187 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2188 cx.assert_editor_state(indoc! {"
2189 \t«oneˇ» «twoˇ»
2190 three
2191 four
2192 "});
2193 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2194 cx.assert_editor_state(indoc! {"
2195 «oneˇ» «twoˇ»
2196 three
2197 four
2198 "});
2199
2200 // select across a line ending
2201 cx.set_state(indoc! {"
2202 one two
2203 t«hree
2204 ˇ»four
2205 "});
2206 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2207 cx.assert_editor_state(indoc! {"
2208 one two
2209 \tt«hree
2210 ˇ»four
2211 "});
2212 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2213 cx.assert_editor_state(indoc! {"
2214 one two
2215 \t\tt«hree
2216 ˇ»four
2217 "});
2218 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2219 cx.assert_editor_state(indoc! {"
2220 one two
2221 \tt«hree
2222 ˇ»four
2223 "});
2224 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2225 cx.assert_editor_state(indoc! {"
2226 one two
2227 t«hree
2228 ˇ»four
2229 "});
2230
2231 // Ensure that indenting/outdenting works when the cursor is at column 0.
2232 cx.set_state(indoc! {"
2233 one two
2234 ˇthree
2235 four
2236 "});
2237 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2238 cx.assert_editor_state(indoc! {"
2239 one two
2240 ˇthree
2241 four
2242 "});
2243 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2244 cx.assert_editor_state(indoc! {"
2245 one two
2246 \tˇthree
2247 four
2248 "});
2249 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2250 cx.assert_editor_state(indoc! {"
2251 one two
2252 ˇthree
2253 four
2254 "});
2255}
2256
2257#[gpui::test]
2258fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2259 init_test(cx, |settings| {
2260 settings.languages.extend([
2261 (
2262 "TOML".into(),
2263 LanguageSettingsContent {
2264 tab_size: NonZeroU32::new(2),
2265 ..Default::default()
2266 },
2267 ),
2268 (
2269 "Rust".into(),
2270 LanguageSettingsContent {
2271 tab_size: NonZeroU32::new(4),
2272 ..Default::default()
2273 },
2274 ),
2275 ]);
2276 });
2277
2278 let toml_language = Arc::new(Language::new(
2279 LanguageConfig {
2280 name: "TOML".into(),
2281 ..Default::default()
2282 },
2283 None,
2284 ));
2285 let rust_language = Arc::new(Language::new(
2286 LanguageConfig {
2287 name: "Rust".into(),
2288 ..Default::default()
2289 },
2290 None,
2291 ));
2292
2293 let toml_buffer = cx.build_model(|cx| {
2294 Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n").with_language(toml_language, cx)
2295 });
2296 let rust_buffer = cx.build_model(|cx| {
2297 Buffer::new(0, cx.entity_id().as_u64(), "const c: usize = 3;\n")
2298 .with_language(rust_language, cx)
2299 });
2300 let multibuffer = cx.build_model(|cx| {
2301 let mut multibuffer = MultiBuffer::new(0);
2302 multibuffer.push_excerpts(
2303 toml_buffer.clone(),
2304 [ExcerptRange {
2305 context: Point::new(0, 0)..Point::new(2, 0),
2306 primary: None,
2307 }],
2308 cx,
2309 );
2310 multibuffer.push_excerpts(
2311 rust_buffer.clone(),
2312 [ExcerptRange {
2313 context: Point::new(0, 0)..Point::new(1, 0),
2314 primary: None,
2315 }],
2316 cx,
2317 );
2318 multibuffer
2319 });
2320
2321 cx.add_window(|cx| {
2322 let mut editor = build_editor(multibuffer, cx);
2323
2324 assert_eq!(
2325 editor.text(cx),
2326 indoc! {"
2327 a = 1
2328 b = 2
2329
2330 const c: usize = 3;
2331 "}
2332 );
2333
2334 select_ranges(
2335 &mut editor,
2336 indoc! {"
2337 «aˇ» = 1
2338 b = 2
2339
2340 «const c:ˇ» usize = 3;
2341 "},
2342 cx,
2343 );
2344
2345 editor.tab(&Tab, cx);
2346 assert_text_with_selections(
2347 &mut editor,
2348 indoc! {"
2349 «aˇ» = 1
2350 b = 2
2351
2352 «const c:ˇ» usize = 3;
2353 "},
2354 cx,
2355 );
2356 editor.tab_prev(&TabPrev, cx);
2357 assert_text_with_selections(
2358 &mut editor,
2359 indoc! {"
2360 «aˇ» = 1
2361 b = 2
2362
2363 «const c:ˇ» usize = 3;
2364 "},
2365 cx,
2366 );
2367
2368 editor
2369 });
2370}
2371
2372#[gpui::test]
2373async fn test_backspace(cx: &mut gpui::TestAppContext) {
2374 init_test(cx, |_| {});
2375
2376 let mut cx = EditorTestContext::new(cx).await;
2377
2378 // Basic backspace
2379 cx.set_state(indoc! {"
2380 onˇe two three
2381 fou«rˇ» five six
2382 seven «ˇeight nine
2383 »ten
2384 "});
2385 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2386 cx.assert_editor_state(indoc! {"
2387 oˇe two three
2388 fouˇ five six
2389 seven ˇten
2390 "});
2391
2392 // Test backspace inside and around indents
2393 cx.set_state(indoc! {"
2394 zero
2395 ˇone
2396 ˇtwo
2397 ˇ ˇ ˇ three
2398 ˇ ˇ four
2399 "});
2400 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2401 cx.assert_editor_state(indoc! {"
2402 zero
2403 ˇone
2404 ˇtwo
2405 ˇ threeˇ four
2406 "});
2407
2408 // Test backspace with line_mode set to true
2409 cx.update_editor(|e, _| e.selections.line_mode = true);
2410 cx.set_state(indoc! {"
2411 The ˇquick ˇbrown
2412 fox jumps over
2413 the lazy dog
2414 ˇThe qu«ick bˇ»rown"});
2415 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2416 cx.assert_editor_state(indoc! {"
2417 ˇfox jumps over
2418 the lazy dogˇ"});
2419}
2420
2421#[gpui::test]
2422async fn test_delete(cx: &mut gpui::TestAppContext) {
2423 init_test(cx, |_| {});
2424
2425 let mut cx = EditorTestContext::new(cx).await;
2426 cx.set_state(indoc! {"
2427 onˇe two three
2428 fou«rˇ» five six
2429 seven «ˇeight nine
2430 »ten
2431 "});
2432 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2433 cx.assert_editor_state(indoc! {"
2434 onˇ two three
2435 fouˇ five six
2436 seven ˇten
2437 "});
2438
2439 // Test backspace with line_mode set to true
2440 cx.update_editor(|e, _| e.selections.line_mode = true);
2441 cx.set_state(indoc! {"
2442 The ˇquick ˇbrown
2443 fox «ˇjum»ps over
2444 the lazy dog
2445 ˇThe qu«ick bˇ»rown"});
2446 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2447 cx.assert_editor_state("ˇthe lazy dogˇ");
2448}
2449
2450#[gpui::test]
2451fn test_delete_line(cx: &mut TestAppContext) {
2452 init_test(cx, |_| {});
2453
2454 let view = cx.add_window(|cx| {
2455 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2456 build_editor(buffer, cx)
2457 });
2458 view.update(cx, |view, cx| {
2459 view.change_selections(None, cx, |s| {
2460 s.select_display_ranges([
2461 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2462 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2463 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2464 ])
2465 });
2466 view.delete_line(&DeleteLine, cx);
2467 assert_eq!(view.display_text(cx), "ghi");
2468 assert_eq!(
2469 view.selections.display_ranges(cx),
2470 vec![
2471 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2472 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2473 ]
2474 );
2475 });
2476
2477 let view = cx.add_window(|cx| {
2478 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2479 build_editor(buffer, cx)
2480 });
2481 view.update(cx, |view, cx| {
2482 view.change_selections(None, cx, |s| {
2483 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2484 });
2485 view.delete_line(&DeleteLine, cx);
2486 assert_eq!(view.display_text(cx), "ghi\n");
2487 assert_eq!(
2488 view.selections.display_ranges(cx),
2489 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2490 );
2491 });
2492}
2493
2494//todo!(select_anchor_ranges)
2495// #[gpui::test]
2496// fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2497// init_test(cx, |_| {});
2498
2499// cx.add_window(|cx| {
2500// let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2501// let mut editor = build_editor(buffer.clone(), cx);
2502// let buffer = buffer.read(cx).as_singleton().unwrap();
2503
2504// assert_eq!(
2505// editor.selections.ranges::<Point>(cx),
2506// &[Point::new(0, 0)..Point::new(0, 0)]
2507// );
2508
2509// // When on single line, replace newline at end by space
2510// editor.join_lines(&JoinLines, cx);
2511// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2512// assert_eq!(
2513// editor.selections.ranges::<Point>(cx),
2514// &[Point::new(0, 3)..Point::new(0, 3)]
2515// );
2516
2517// // When multiple lines are selected, remove newlines that are spanned by the selection
2518// editor.change_selections(None, cx, |s| {
2519// s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2520// });
2521// editor.join_lines(&JoinLines, cx);
2522// assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2523// assert_eq!(
2524// editor.selections.ranges::<Point>(cx),
2525// &[Point::new(0, 11)..Point::new(0, 11)]
2526// );
2527
2528// // Undo should be transactional
2529// editor.undo(&Undo, cx);
2530// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2531// assert_eq!(
2532// editor.selections.ranges::<Point>(cx),
2533// &[Point::new(0, 5)..Point::new(2, 2)]
2534// );
2535
2536// // When joining an empty line don't insert a space
2537// editor.change_selections(None, cx, |s| {
2538// s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2539// });
2540// editor.join_lines(&JoinLines, cx);
2541// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2542// assert_eq!(
2543// editor.selections.ranges::<Point>(cx),
2544// [Point::new(2, 3)..Point::new(2, 3)]
2545// );
2546
2547// // We can remove trailing newlines
2548// editor.join_lines(&JoinLines, cx);
2549// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2550// assert_eq!(
2551// editor.selections.ranges::<Point>(cx),
2552// [Point::new(2, 3)..Point::new(2, 3)]
2553// );
2554
2555// // We don't blow up on the last line
2556// editor.join_lines(&JoinLines, cx);
2557// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2558// assert_eq!(
2559// editor.selections.ranges::<Point>(cx),
2560// [Point::new(2, 3)..Point::new(2, 3)]
2561// );
2562
2563// // reset to test indentation
2564// editor.buffer.update(cx, |buffer, cx| {
2565// buffer.edit(
2566// [
2567// (Point::new(1, 0)..Point::new(1, 2), " "),
2568// (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
2569// ],
2570// None,
2571// cx,
2572// )
2573// });
2574
2575// // We remove any leading spaces
2576// assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
2577// editor.change_selections(None, cx, |s| {
2578// s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
2579// });
2580// editor.join_lines(&JoinLines, cx);
2581// assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
2582
2583// // We don't insert a space for a line containing only spaces
2584// editor.join_lines(&JoinLines, cx);
2585// assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
2586
2587// // We ignore any leading tabs
2588// editor.join_lines(&JoinLines, cx);
2589// assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
2590
2591// editor
2592// });
2593// }
2594
2595// #[gpui::test]
2596// fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
2597// init_test(cx, |_| {});
2598
2599// cx.add_window(|cx| {
2600// let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2601// let mut editor = build_editor(buffer.clone(), cx);
2602// let buffer = buffer.read(cx).as_singleton().unwrap();
2603
2604// editor.change_selections(None, cx, |s| {
2605// s.select_ranges([
2606// Point::new(0, 2)..Point::new(1, 1),
2607// Point::new(1, 2)..Point::new(1, 2),
2608// Point::new(3, 1)..Point::new(3, 2),
2609// ])
2610// });
2611
2612// editor.join_lines(&JoinLines, cx);
2613// assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
2614
2615// assert_eq!(
2616// editor.selections.ranges::<Point>(cx),
2617// [
2618// Point::new(0, 7)..Point::new(0, 7),
2619// Point::new(1, 3)..Point::new(1, 3)
2620// ]
2621// );
2622// editor
2623// });
2624// }
2625
2626#[gpui::test]
2627async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
2628 init_test(cx, |_| {});
2629
2630 let mut cx = EditorTestContext::new(cx).await;
2631
2632 // Test sort_lines_case_insensitive()
2633 cx.set_state(indoc! {"
2634 «z
2635 y
2636 x
2637 Z
2638 Y
2639 Xˇ»
2640 "});
2641 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
2642 cx.assert_editor_state(indoc! {"
2643 «x
2644 X
2645 y
2646 Y
2647 z
2648 Zˇ»
2649 "});
2650
2651 // Test reverse_lines()
2652 cx.set_state(indoc! {"
2653 «5
2654 4
2655 3
2656 2
2657 1ˇ»
2658 "});
2659 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
2660 cx.assert_editor_state(indoc! {"
2661 «1
2662 2
2663 3
2664 4
2665 5ˇ»
2666 "});
2667
2668 // Skip testing shuffle_line()
2669
2670 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
2671 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
2672
2673 // Don't manipulate when cursor is on single line, but expand the selection
2674 cx.set_state(indoc! {"
2675 ddˇdd
2676 ccc
2677 bb
2678 a
2679 "});
2680 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2681 cx.assert_editor_state(indoc! {"
2682 «ddddˇ»
2683 ccc
2684 bb
2685 a
2686 "});
2687
2688 // Basic manipulate case
2689 // Start selection moves to column 0
2690 // End of selection shrinks to fit shorter line
2691 cx.set_state(indoc! {"
2692 dd«d
2693 ccc
2694 bb
2695 aaaaaˇ»
2696 "});
2697 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2698 cx.assert_editor_state(indoc! {"
2699 «aaaaa
2700 bb
2701 ccc
2702 dddˇ»
2703 "});
2704
2705 // Manipulate case with newlines
2706 cx.set_state(indoc! {"
2707 dd«d
2708 ccc
2709
2710 bb
2711 aaaaa
2712
2713 ˇ»
2714 "});
2715 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2716 cx.assert_editor_state(indoc! {"
2717 «
2718
2719 aaaaa
2720 bb
2721 ccc
2722 dddˇ»
2723
2724 "});
2725}
2726
2727#[gpui::test]
2728async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
2729 init_test(cx, |_| {});
2730
2731 let mut cx = EditorTestContext::new(cx).await;
2732
2733 // Manipulate with multiple selections on a single line
2734 cx.set_state(indoc! {"
2735 dd«dd
2736 cˇ»c«c
2737 bb
2738 aaaˇ»aa
2739 "});
2740 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2741 cx.assert_editor_state(indoc! {"
2742 «aaaaa
2743 bb
2744 ccc
2745 ddddˇ»
2746 "});
2747
2748 // Manipulate with multiple disjoin selections
2749 cx.set_state(indoc! {"
2750 5«
2751 4
2752 3
2753 2
2754 1ˇ»
2755
2756 dd«dd
2757 ccc
2758 bb
2759 aaaˇ»aa
2760 "});
2761 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
2762 cx.assert_editor_state(indoc! {"
2763 «1
2764 2
2765 3
2766 4
2767 5ˇ»
2768
2769 «aaaaa
2770 bb
2771 ccc
2772 ddddˇ»
2773 "});
2774}
2775
2776#[gpui::test]
2777async fn test_manipulate_text(cx: &mut TestAppContext) {
2778 init_test(cx, |_| {});
2779
2780 let mut cx = EditorTestContext::new(cx).await;
2781
2782 // Test convert_to_upper_case()
2783 cx.set_state(indoc! {"
2784 «hello worldˇ»
2785 "});
2786 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2787 cx.assert_editor_state(indoc! {"
2788 «HELLO WORLDˇ»
2789 "});
2790
2791 // Test convert_to_lower_case()
2792 cx.set_state(indoc! {"
2793 «HELLO WORLDˇ»
2794 "});
2795 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
2796 cx.assert_editor_state(indoc! {"
2797 «hello worldˇ»
2798 "});
2799
2800 // Test multiple line, single selection case
2801 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
2802 cx.set_state(indoc! {"
2803 «The quick brown
2804 fox jumps over
2805 the lazy dogˇ»
2806 "});
2807 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
2808 cx.assert_editor_state(indoc! {"
2809 «The Quick Brown
2810 Fox Jumps Over
2811 The Lazy Dogˇ»
2812 "});
2813
2814 // Test multiple line, single selection case
2815 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
2816 cx.set_state(indoc! {"
2817 «The quick brown
2818 fox jumps over
2819 the lazy dogˇ»
2820 "});
2821 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
2822 cx.assert_editor_state(indoc! {"
2823 «TheQuickBrown
2824 FoxJumpsOver
2825 TheLazyDogˇ»
2826 "});
2827
2828 // From here on out, test more complex cases of manipulate_text()
2829
2830 // Test no selection case - should affect words cursors are in
2831 // Cursor at beginning, middle, and end of word
2832 cx.set_state(indoc! {"
2833 ˇhello big beauˇtiful worldˇ
2834 "});
2835 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2836 cx.assert_editor_state(indoc! {"
2837 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
2838 "});
2839
2840 // Test multiple selections on a single line and across multiple lines
2841 cx.set_state(indoc! {"
2842 «Theˇ» quick «brown
2843 foxˇ» jumps «overˇ»
2844 the «lazyˇ» dog
2845 "});
2846 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2847 cx.assert_editor_state(indoc! {"
2848 «THEˇ» quick «BROWN
2849 FOXˇ» jumps «OVERˇ»
2850 the «LAZYˇ» dog
2851 "});
2852
2853 // Test case where text length grows
2854 cx.set_state(indoc! {"
2855 «tschüߡ»
2856 "});
2857 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «TSCHÜSSˇ»
2860 "});
2861
2862 // Test to make sure we don't crash when text shrinks
2863 cx.set_state(indoc! {"
2864 aaa_bbbˇ
2865 "});
2866 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
2867 cx.assert_editor_state(indoc! {"
2868 «aaaBbbˇ»
2869 "});
2870
2871 // Test to make sure we all aware of the fact that each word can grow and shrink
2872 // Final selections should be aware of this fact
2873 cx.set_state(indoc! {"
2874 aaa_bˇbb bbˇb_ccc ˇccc_ddd
2875 "});
2876 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
2877 cx.assert_editor_state(indoc! {"
2878 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
2879 "});
2880}
2881
2882#[gpui::test]
2883fn test_duplicate_line(cx: &mut TestAppContext) {
2884 init_test(cx, |_| {});
2885
2886 let view = cx.add_window(|cx| {
2887 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2888 build_editor(buffer, cx)
2889 });
2890 view.update(cx, |view, cx| {
2891 view.change_selections(None, cx, |s| {
2892 s.select_display_ranges([
2893 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2894 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2895 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2896 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2897 ])
2898 });
2899 view.duplicate_line(&DuplicateLine, cx);
2900 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2901 assert_eq!(
2902 view.selections.display_ranges(cx),
2903 vec![
2904 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2905 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2906 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2907 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2908 ]
2909 );
2910 });
2911
2912 let view = cx.add_window(|cx| {
2913 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2914 build_editor(buffer, cx)
2915 });
2916 view.update(cx, |view, cx| {
2917 view.change_selections(None, cx, |s| {
2918 s.select_display_ranges([
2919 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2920 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2921 ])
2922 });
2923 view.duplicate_line(&DuplicateLine, cx);
2924 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2925 assert_eq!(
2926 view.selections.display_ranges(cx),
2927 vec![
2928 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2929 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2930 ]
2931 );
2932 });
2933}
2934
2935#[gpui::test]
2936fn test_move_line_up_down(cx: &mut TestAppContext) {
2937 init_test(cx, |_| {});
2938
2939 let view = cx.add_window(|cx| {
2940 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2941 build_editor(buffer, cx)
2942 });
2943 view.update(cx, |view, cx| {
2944 view.fold_ranges(
2945 vec![
2946 Point::new(0, 2)..Point::new(1, 2),
2947 Point::new(2, 3)..Point::new(4, 1),
2948 Point::new(7, 0)..Point::new(8, 4),
2949 ],
2950 true,
2951 cx,
2952 );
2953 view.change_selections(None, cx, |s| {
2954 s.select_display_ranges([
2955 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2956 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2957 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2958 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2959 ])
2960 });
2961 assert_eq!(
2962 view.display_text(cx),
2963 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
2964 );
2965
2966 view.move_line_up(&MoveLineUp, cx);
2967 assert_eq!(
2968 view.display_text(cx),
2969 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
2970 );
2971 assert_eq!(
2972 view.selections.display_ranges(cx),
2973 vec![
2974 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2975 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2976 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2977 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2978 ]
2979 );
2980 });
2981
2982 view.update(cx, |view, cx| {
2983 view.move_line_down(&MoveLineDown, cx);
2984 assert_eq!(
2985 view.display_text(cx),
2986 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
2987 );
2988 assert_eq!(
2989 view.selections.display_ranges(cx),
2990 vec![
2991 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2992 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2993 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2994 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2995 ]
2996 );
2997 });
2998
2999 view.update(cx, |view, cx| {
3000 view.move_line_down(&MoveLineDown, cx);
3001 assert_eq!(
3002 view.display_text(cx),
3003 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3004 );
3005 assert_eq!(
3006 view.selections.display_ranges(cx),
3007 vec![
3008 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3009 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3010 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3011 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
3012 ]
3013 );
3014 });
3015
3016 view.update(cx, |view, cx| {
3017 view.move_line_up(&MoveLineUp, cx);
3018 assert_eq!(
3019 view.display_text(cx),
3020 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3021 );
3022 assert_eq!(
3023 view.selections.display_ranges(cx),
3024 vec![
3025 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3026 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3027 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
3028 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
3029 ]
3030 );
3031 });
3032}
3033
3034#[gpui::test]
3035fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3036 init_test(cx, |_| {});
3037
3038 let editor = cx.add_window(|cx| {
3039 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3040 build_editor(buffer, cx)
3041 });
3042 editor.update(cx, |editor, cx| {
3043 let snapshot = editor.buffer.read(cx).snapshot(cx);
3044 editor.insert_blocks(
3045 [BlockProperties {
3046 style: BlockStyle::Fixed,
3047 position: snapshot.anchor_after(Point::new(2, 0)),
3048 disposition: BlockDisposition::Below,
3049 height: 1,
3050 render: Arc::new(|_| div().into_any()),
3051 }],
3052 Some(Autoscroll::fit()),
3053 cx,
3054 );
3055 editor.change_selections(None, cx, |s| {
3056 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3057 });
3058 editor.move_line_down(&MoveLineDown, cx);
3059 });
3060}
3061
3062//todo!(test_transpose)
3063// #[gpui::test]
3064// fn test_transpose(cx: &mut TestAppContext) {
3065// init_test(cx, |_| {});
3066
3067// _ = cx.add_window(|cx| {
3068// let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3069
3070// editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3071// editor.transpose(&Default::default(), cx);
3072// assert_eq!(editor.text(cx), "bac");
3073// assert_eq!(editor.selections.ranges(cx), [2..2]);
3074
3075// editor.transpose(&Default::default(), cx);
3076// assert_eq!(editor.text(cx), "bca");
3077// assert_eq!(editor.selections.ranges(cx), [3..3]);
3078
3079// editor.transpose(&Default::default(), cx);
3080// assert_eq!(editor.text(cx), "bac");
3081// assert_eq!(editor.selections.ranges(cx), [3..3]);
3082
3083// editor
3084// });
3085
3086// _ = cx.add_window(|cx| {
3087// let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3088
3089// editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3090// editor.transpose(&Default::default(), cx);
3091// assert_eq!(editor.text(cx), "acb\nde");
3092// assert_eq!(editor.selections.ranges(cx), [3..3]);
3093
3094// editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3095// editor.transpose(&Default::default(), cx);
3096// assert_eq!(editor.text(cx), "acbd\ne");
3097// assert_eq!(editor.selections.ranges(cx), [5..5]);
3098
3099// editor.transpose(&Default::default(), cx);
3100// assert_eq!(editor.text(cx), "acbde\n");
3101// assert_eq!(editor.selections.ranges(cx), [6..6]);
3102
3103// editor.transpose(&Default::default(), cx);
3104// assert_eq!(editor.text(cx), "acbd\ne");
3105// assert_eq!(editor.selections.ranges(cx), [6..6]);
3106
3107// editor
3108// });
3109
3110// _ = cx.add_window(|cx| {
3111// let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3112
3113// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3114// editor.transpose(&Default::default(), cx);
3115// assert_eq!(editor.text(cx), "bacd\ne");
3116// assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3117
3118// editor.transpose(&Default::default(), cx);
3119// assert_eq!(editor.text(cx), "bcade\n");
3120// assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3121
3122// editor.transpose(&Default::default(), cx);
3123// assert_eq!(editor.text(cx), "bcda\ne");
3124// assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3125
3126// editor.transpose(&Default::default(), cx);
3127// assert_eq!(editor.text(cx), "bcade\n");
3128// assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3129
3130// editor.transpose(&Default::default(), cx);
3131// assert_eq!(editor.text(cx), "bcaed\n");
3132// assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3133
3134// editor
3135// });
3136
3137// _ = cx.add_window(|cx| {
3138// let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3139
3140// editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3141// editor.transpose(&Default::default(), cx);
3142// assert_eq!(editor.text(cx), "🏀🍐✋");
3143// assert_eq!(editor.selections.ranges(cx), [8..8]);
3144
3145// editor.transpose(&Default::default(), cx);
3146// assert_eq!(editor.text(cx), "🏀✋🍐");
3147// assert_eq!(editor.selections.ranges(cx), [11..11]);
3148
3149// editor.transpose(&Default::default(), cx);
3150// assert_eq!(editor.text(cx), "🏀🍐✋");
3151// assert_eq!(editor.selections.ranges(cx), [11..11]);
3152
3153// editor
3154// });
3155// }
3156
3157//todo!(clipboard)
3158// #[gpui::test]
3159// async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3160// init_test(cx, |_| {});
3161
3162// let mut cx = EditorTestContext::new(cx).await;
3163
3164// cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3165// cx.update_editor(|e, cx| e.cut(&Cut, cx));
3166// cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3167
3168// // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3169// cx.set_state("two ˇfour ˇsix ˇ");
3170// cx.update_editor(|e, cx| e.paste(&Paste, cx));
3171// cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3172
3173// // Paste again but with only two cursors. Since the number of cursors doesn't
3174// // match the number of slices in the clipboard, the entire clipboard text
3175// // is pasted at each cursor.
3176// cx.set_state("ˇtwo one✅ four three six five ˇ");
3177// cx.update_editor(|e, cx| {
3178// e.handle_input("( ", cx);
3179// e.paste(&Paste, cx);
3180// e.handle_input(") ", cx);
3181// });
3182// cx.assert_editor_state(
3183// &([
3184// "( one✅ ",
3185// "three ",
3186// "five ) ˇtwo one✅ four three six five ( one✅ ",
3187// "three ",
3188// "five ) ˇ",
3189// ]
3190// .join("\n")),
3191// );
3192
3193// // Cut with three selections, one of which is full-line.
3194// cx.set_state(indoc! {"
3195// 1«2ˇ»3
3196// 4ˇ567
3197// «8ˇ»9"});
3198// cx.update_editor(|e, cx| e.cut(&Cut, cx));
3199// cx.assert_editor_state(indoc! {"
3200// 1ˇ3
3201// ˇ9"});
3202
3203// // Paste with three selections, noticing how the copied selection that was full-line
3204// // gets inserted before the second cursor.
3205// cx.set_state(indoc! {"
3206// 1ˇ3
3207// 9ˇ
3208// «oˇ»ne"});
3209// cx.update_editor(|e, cx| e.paste(&Paste, cx));
3210// cx.assert_editor_state(indoc! {"
3211// 12ˇ3
3212// 4567
3213// 9ˇ
3214// 8ˇne"});
3215
3216// // Copy with a single cursor only, which writes the whole line into the clipboard.
3217// cx.set_state(indoc! {"
3218// The quick brown
3219// fox juˇmps over
3220// the lazy dog"});
3221// cx.update_editor(|e, cx| e.copy(&Copy, cx));
3222// cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
3223
3224// // Paste with three selections, noticing how the copied full-line selection is inserted
3225// // before the empty selections but replaces the selection that is non-empty.
3226// cx.set_state(indoc! {"
3227// Tˇhe quick brown
3228// «foˇ»x jumps over
3229// tˇhe lazy dog"});
3230// cx.update_editor(|e, cx| e.paste(&Paste, cx));
3231// cx.assert_editor_state(indoc! {"
3232// fox jumps over
3233// Tˇhe quick brown
3234// fox jumps over
3235// ˇx jumps over
3236// fox jumps over
3237// tˇhe lazy dog"});
3238// }
3239
3240// #[gpui::test]
3241// async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3242// init_test(cx, |_| {});
3243
3244// let mut cx = EditorTestContext::new(cx).await;
3245// let language = Arc::new(Language::new(
3246// LanguageConfig::default(),
3247// Some(tree_sitter_rust::language()),
3248// ));
3249// cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3250
3251// // Cut an indented block, without the leading whitespace.
3252// cx.set_state(indoc! {"
3253// const a: B = (
3254// c(),
3255// «d(
3256// e,
3257// f
3258// )ˇ»
3259// );
3260// "});
3261// cx.update_editor(|e, cx| e.cut(&Cut, cx));
3262// cx.assert_editor_state(indoc! {"
3263// const a: B = (
3264// c(),
3265// ˇ
3266// );
3267// "});
3268
3269// // Paste it at the same position.
3270// cx.update_editor(|e, cx| e.paste(&Paste, cx));
3271// cx.assert_editor_state(indoc! {"
3272// const a: B = (
3273// c(),
3274// d(
3275// e,
3276// f
3277// )ˇ
3278// );
3279// "});
3280
3281// // Paste it at a line with a lower indent level.
3282// cx.set_state(indoc! {"
3283// ˇ
3284// const a: B = (
3285// c(),
3286// );
3287// "});
3288// cx.update_editor(|e, cx| e.paste(&Paste, cx));
3289// cx.assert_editor_state(indoc! {"
3290// d(
3291// e,
3292// f
3293// )ˇ
3294// const a: B = (
3295// c(),
3296// );
3297// "});
3298
3299// // Cut an indented block, with the leading whitespace.
3300// cx.set_state(indoc! {"
3301// const a: B = (
3302// c(),
3303// « d(
3304// e,
3305// f
3306// )
3307// ˇ»);
3308// "});
3309// cx.update_editor(|e, cx| e.cut(&Cut, cx));
3310// cx.assert_editor_state(indoc! {"
3311// const a: B = (
3312// c(),
3313// ˇ);
3314// "});
3315
3316// // Paste it at the same position.
3317// cx.update_editor(|e, cx| e.paste(&Paste, cx));
3318// cx.assert_editor_state(indoc! {"
3319// const a: B = (
3320// c(),
3321// d(
3322// e,
3323// f
3324// )
3325// ˇ);
3326// "});
3327
3328// // Paste it at a line with a higher indent level.
3329// cx.set_state(indoc! {"
3330// const a: B = (
3331// c(),
3332// d(
3333// e,
3334// fˇ
3335// )
3336// );
3337// "});
3338// cx.update_editor(|e, cx| e.paste(&Paste, cx));
3339// cx.assert_editor_state(indoc! {"
3340// const a: B = (
3341// c(),
3342// d(
3343// e,
3344// f d(
3345// e,
3346// f
3347// )
3348// ˇ
3349// )
3350// );
3351// "});
3352// }
3353
3354#[gpui::test]
3355fn test_select_all(cx: &mut TestAppContext) {
3356 init_test(cx, |_| {});
3357
3358 let view = cx.add_window(|cx| {
3359 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
3360 build_editor(buffer, cx)
3361 });
3362 view.update(cx, |view, cx| {
3363 view.select_all(&SelectAll, cx);
3364 assert_eq!(
3365 view.selections.display_ranges(cx),
3366 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
3367 );
3368 });
3369}
3370
3371#[gpui::test]
3372fn test_select_line(cx: &mut TestAppContext) {
3373 init_test(cx, |_| {});
3374
3375 let view = cx.add_window(|cx| {
3376 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3377 build_editor(buffer, cx)
3378 });
3379 view.update(cx, |view, cx| {
3380 view.change_selections(None, cx, |s| {
3381 s.select_display_ranges([
3382 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3383 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3384 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3385 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
3386 ])
3387 });
3388 view.select_line(&SelectLine, cx);
3389 assert_eq!(
3390 view.selections.display_ranges(cx),
3391 vec![
3392 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
3393 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
3394 ]
3395 );
3396 });
3397
3398 view.update(cx, |view, cx| {
3399 view.select_line(&SelectLine, cx);
3400 assert_eq!(
3401 view.selections.display_ranges(cx),
3402 vec![
3403 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
3404 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
3405 ]
3406 );
3407 });
3408
3409 view.update(cx, |view, cx| {
3410 view.select_line(&SelectLine, cx);
3411 assert_eq!(
3412 view.selections.display_ranges(cx),
3413 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
3414 );
3415 });
3416}
3417
3418#[gpui::test]
3419fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3420 init_test(cx, |_| {});
3421
3422 let view = cx.add_window(|cx| {
3423 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3424 build_editor(buffer, cx)
3425 });
3426 view.update(cx, |view, cx| {
3427 view.fold_ranges(
3428 vec![
3429 Point::new(0, 2)..Point::new(1, 2),
3430 Point::new(2, 3)..Point::new(4, 1),
3431 Point::new(7, 0)..Point::new(8, 4),
3432 ],
3433 true,
3434 cx,
3435 );
3436 view.change_selections(None, cx, |s| {
3437 s.select_display_ranges([
3438 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3439 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3440 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3441 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
3442 ])
3443 });
3444 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3445 });
3446
3447 view.update(cx, |view, cx| {
3448 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3449 assert_eq!(
3450 view.display_text(cx),
3451 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3452 );
3453 assert_eq!(
3454 view.selections.display_ranges(cx),
3455 [
3456 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3457 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3458 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3459 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
3460 ]
3461 );
3462 });
3463
3464 view.update(cx, |view, cx| {
3465 view.change_selections(None, cx, |s| {
3466 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
3467 });
3468 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3469 assert_eq!(
3470 view.display_text(cx),
3471 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3472 );
3473 assert_eq!(
3474 view.selections.display_ranges(cx),
3475 [
3476 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
3477 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
3478 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3479 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3480 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3481 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3482 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3483 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3484 ]
3485 );
3486 });
3487}
3488
3489#[gpui::test]
3490async fn test_add_selection_above_below(cx: &mut TestAppContext) {
3491 init_test(cx, |_| {});
3492
3493 let mut cx = EditorTestContext::new(cx).await;
3494
3495 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3496 cx.set_state(indoc!(
3497 r#"abc
3498 defˇghi
3499
3500 jk
3501 nlmo
3502 "#
3503 ));
3504
3505 cx.update_editor(|editor, cx| {
3506 editor.add_selection_above(&Default::default(), cx);
3507 });
3508
3509 cx.assert_editor_state(indoc!(
3510 r#"abcˇ
3511 defˇghi
3512
3513 jk
3514 nlmo
3515 "#
3516 ));
3517
3518 cx.update_editor(|editor, cx| {
3519 editor.add_selection_above(&Default::default(), cx);
3520 });
3521
3522 cx.assert_editor_state(indoc!(
3523 r#"abcˇ
3524 defˇghi
3525
3526 jk
3527 nlmo
3528 "#
3529 ));
3530
3531 cx.update_editor(|view, cx| {
3532 view.add_selection_below(&Default::default(), cx);
3533 });
3534
3535 cx.assert_editor_state(indoc!(
3536 r#"abc
3537 defˇghi
3538
3539 jk
3540 nlmo
3541 "#
3542 ));
3543
3544 cx.update_editor(|view, cx| {
3545 view.undo_selection(&Default::default(), cx);
3546 });
3547
3548 cx.assert_editor_state(indoc!(
3549 r#"abcˇ
3550 defˇghi
3551
3552 jk
3553 nlmo
3554 "#
3555 ));
3556
3557 cx.update_editor(|view, cx| {
3558 view.redo_selection(&Default::default(), cx);
3559 });
3560
3561 cx.assert_editor_state(indoc!(
3562 r#"abc
3563 defˇghi
3564
3565 jk
3566 nlmo
3567 "#
3568 ));
3569
3570 cx.update_editor(|view, cx| {
3571 view.add_selection_below(&Default::default(), cx);
3572 });
3573
3574 cx.assert_editor_state(indoc!(
3575 r#"abc
3576 defˇghi
3577
3578 jk
3579 nlmˇo
3580 "#
3581 ));
3582
3583 cx.update_editor(|view, cx| {
3584 view.add_selection_below(&Default::default(), cx);
3585 });
3586
3587 cx.assert_editor_state(indoc!(
3588 r#"abc
3589 defˇghi
3590
3591 jk
3592 nlmˇo
3593 "#
3594 ));
3595
3596 // change selections
3597 cx.set_state(indoc!(
3598 r#"abc
3599 def«ˇg»hi
3600
3601 jk
3602 nlmo
3603 "#
3604 ));
3605
3606 cx.update_editor(|view, cx| {
3607 view.add_selection_below(&Default::default(), cx);
3608 });
3609
3610 cx.assert_editor_state(indoc!(
3611 r#"abc
3612 def«ˇg»hi
3613
3614 jk
3615 nlm«ˇo»
3616 "#
3617 ));
3618
3619 cx.update_editor(|view, cx| {
3620 view.add_selection_below(&Default::default(), cx);
3621 });
3622
3623 cx.assert_editor_state(indoc!(
3624 r#"abc
3625 def«ˇg»hi
3626
3627 jk
3628 nlm«ˇo»
3629 "#
3630 ));
3631
3632 cx.update_editor(|view, cx| {
3633 view.add_selection_above(&Default::default(), cx);
3634 });
3635
3636 cx.assert_editor_state(indoc!(
3637 r#"abc
3638 def«ˇg»hi
3639
3640 jk
3641 nlmo
3642 "#
3643 ));
3644
3645 cx.update_editor(|view, cx| {
3646 view.add_selection_above(&Default::default(), cx);
3647 });
3648
3649 cx.assert_editor_state(indoc!(
3650 r#"abc
3651 def«ˇg»hi
3652
3653 jk
3654 nlmo
3655 "#
3656 ));
3657
3658 // Change selections again
3659 cx.set_state(indoc!(
3660 r#"a«bc
3661 defgˇ»hi
3662
3663 jk
3664 nlmo
3665 "#
3666 ));
3667
3668 cx.update_editor(|view, cx| {
3669 view.add_selection_below(&Default::default(), cx);
3670 });
3671
3672 cx.assert_editor_state(indoc!(
3673 r#"a«bcˇ»
3674 d«efgˇ»hi
3675
3676 j«kˇ»
3677 nlmo
3678 "#
3679 ));
3680
3681 cx.update_editor(|view, cx| {
3682 view.add_selection_below(&Default::default(), cx);
3683 });
3684 cx.assert_editor_state(indoc!(
3685 r#"a«bcˇ»
3686 d«efgˇ»hi
3687
3688 j«kˇ»
3689 n«lmoˇ»
3690 "#
3691 ));
3692 cx.update_editor(|view, cx| {
3693 view.add_selection_above(&Default::default(), cx);
3694 });
3695
3696 cx.assert_editor_state(indoc!(
3697 r#"a«bcˇ»
3698 d«efgˇ»hi
3699
3700 j«kˇ»
3701 nlmo
3702 "#
3703 ));
3704
3705 // Change selections again
3706 cx.set_state(indoc!(
3707 r#"abc
3708 d«ˇefghi
3709
3710 jk
3711 nlm»o
3712 "#
3713 ));
3714
3715 cx.update_editor(|view, cx| {
3716 view.add_selection_above(&Default::default(), cx);
3717 });
3718
3719 cx.assert_editor_state(indoc!(
3720 r#"a«ˇbc»
3721 d«ˇef»ghi
3722
3723 j«ˇk»
3724 n«ˇlm»o
3725 "#
3726 ));
3727
3728 cx.update_editor(|view, cx| {
3729 view.add_selection_below(&Default::default(), cx);
3730 });
3731
3732 cx.assert_editor_state(indoc!(
3733 r#"abc
3734 d«ˇef»ghi
3735
3736 j«ˇk»
3737 n«ˇlm»o
3738 "#
3739 ));
3740}
3741
3742#[gpui::test]
3743async fn test_select_next(cx: &mut gpui::TestAppContext) {
3744 init_test(cx, |_| {});
3745
3746 let mut cx = EditorTestContext::new(cx).await;
3747 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3748
3749 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3750 .unwrap();
3751 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3752
3753 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3754 .unwrap();
3755 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3756
3757 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3758 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3759
3760 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3761 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3762
3763 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3764 .unwrap();
3765 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3766
3767 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3768 .unwrap();
3769 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3770}
3771
3772#[gpui::test]
3773async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3774 init_test(cx, |_| {});
3775 {
3776 // `Select previous` without a selection (selects wordwise)
3777 let mut cx = EditorTestContext::new(cx).await;
3778 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3779
3780 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3781 .unwrap();
3782 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3783
3784 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3785 .unwrap();
3786 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3787
3788 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3789 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3790
3791 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3792 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3793
3794 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3795 .unwrap();
3796 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3797
3798 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3799 .unwrap();
3800 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3801 }
3802 {
3803 // `Select previous` with a selection
3804 let mut cx = EditorTestContext::new(cx).await;
3805 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3806
3807 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3808 .unwrap();
3809 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3810
3811 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3812 .unwrap();
3813 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3814
3815 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3816 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3817
3818 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3819 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3820
3821 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3822 .unwrap();
3823 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3824
3825 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3826 .unwrap();
3827 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3828 }
3829}
3830
3831#[gpui::test]
3832async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3833 init_test(cx, |_| {});
3834
3835 let language = Arc::new(Language::new(
3836 LanguageConfig::default(),
3837 Some(tree_sitter_rust::language()),
3838 ));
3839
3840 let text = r#"
3841 use mod1::mod2::{mod3, mod4};
3842
3843 fn fn_1(param1: bool, param2: &str) {
3844 let var1 = "text";
3845 }
3846 "#
3847 .unindent();
3848
3849 let buffer = cx.build_model(|cx| {
3850 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
3851 });
3852 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
3853 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
3854
3855 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3856 .await;
3857
3858 view.update(cx, |view, cx| {
3859 view.change_selections(None, cx, |s| {
3860 s.select_display_ranges([
3861 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3862 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3863 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3864 ]);
3865 });
3866 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3867 });
3868 assert_eq!(
3869 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3870 &[
3871 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3872 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3873 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3874 ]
3875 );
3876
3877 view.update(cx, |view, cx| {
3878 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3879 });
3880 assert_eq!(
3881 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3882 &[
3883 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3884 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3885 ]
3886 );
3887
3888 view.update(cx, |view, cx| {
3889 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3890 });
3891 assert_eq!(
3892 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3893 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3894 );
3895
3896 // Trying to expand the selected syntax node one more time has no effect.
3897 view.update(cx, |view, cx| {
3898 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3899 });
3900 assert_eq!(
3901 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3902 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3903 );
3904
3905 view.update(cx, |view, cx| {
3906 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3907 });
3908 assert_eq!(
3909 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3910 &[
3911 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3912 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3913 ]
3914 );
3915
3916 view.update(cx, |view, cx| {
3917 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3918 });
3919 assert_eq!(
3920 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3921 &[
3922 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3923 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3924 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3925 ]
3926 );
3927
3928 view.update(cx, |view, cx| {
3929 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3930 });
3931 assert_eq!(
3932 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3933 &[
3934 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3935 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3936 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3937 ]
3938 );
3939
3940 // Trying to shrink the selected syntax node one more time has no effect.
3941 view.update(cx, |view, cx| {
3942 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3943 });
3944 assert_eq!(
3945 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3946 &[
3947 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3948 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3949 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3950 ]
3951 );
3952
3953 // Ensure that we keep expanding the selection if the larger selection starts or ends within
3954 // a fold.
3955 view.update(cx, |view, cx| {
3956 view.fold_ranges(
3957 vec![
3958 Point::new(0, 21)..Point::new(0, 24),
3959 Point::new(3, 20)..Point::new(3, 22),
3960 ],
3961 true,
3962 cx,
3963 );
3964 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3965 });
3966 assert_eq!(
3967 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3968 &[
3969 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3970 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3971 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3972 ]
3973 );
3974}
3975
3976#[gpui::test]
3977async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3978 init_test(cx, |_| {});
3979
3980 let language = Arc::new(
3981 Language::new(
3982 LanguageConfig {
3983 brackets: BracketPairConfig {
3984 pairs: vec![
3985 BracketPair {
3986 start: "{".to_string(),
3987 end: "}".to_string(),
3988 close: false,
3989 newline: true,
3990 },
3991 BracketPair {
3992 start: "(".to_string(),
3993 end: ")".to_string(),
3994 close: false,
3995 newline: true,
3996 },
3997 ],
3998 ..Default::default()
3999 },
4000 ..Default::default()
4001 },
4002 Some(tree_sitter_rust::language()),
4003 )
4004 .with_indents_query(
4005 r#"
4006 (_ "(" ")" @end) @indent
4007 (_ "{" "}" @end) @indent
4008 "#,
4009 )
4010 .unwrap(),
4011 );
4012
4013 let text = "fn a() {}";
4014
4015 let buffer = cx.build_model(|cx| {
4016 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4017 });
4018 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4019 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4020 editor
4021 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4022 .await;
4023
4024 editor.update(cx, |editor, cx| {
4025 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4026 editor.newline(&Newline, cx);
4027 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4028 assert_eq!(
4029 editor.selections.ranges(cx),
4030 &[
4031 Point::new(1, 4)..Point::new(1, 4),
4032 Point::new(3, 4)..Point::new(3, 4),
4033 Point::new(5, 0)..Point::new(5, 0)
4034 ]
4035 );
4036 });
4037}
4038
4039#[gpui::test]
4040async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4041 init_test(cx, |_| {});
4042
4043 let mut cx = EditorTestContext::new(cx).await;
4044
4045 let language = Arc::new(Language::new(
4046 LanguageConfig {
4047 brackets: BracketPairConfig {
4048 pairs: vec![
4049 BracketPair {
4050 start: "{".to_string(),
4051 end: "}".to_string(),
4052 close: true,
4053 newline: true,
4054 },
4055 BracketPair {
4056 start: "(".to_string(),
4057 end: ")".to_string(),
4058 close: true,
4059 newline: true,
4060 },
4061 BracketPair {
4062 start: "/*".to_string(),
4063 end: " */".to_string(),
4064 close: true,
4065 newline: true,
4066 },
4067 BracketPair {
4068 start: "[".to_string(),
4069 end: "]".to_string(),
4070 close: false,
4071 newline: true,
4072 },
4073 BracketPair {
4074 start: "\"".to_string(),
4075 end: "\"".to_string(),
4076 close: true,
4077 newline: false,
4078 },
4079 ],
4080 ..Default::default()
4081 },
4082 autoclose_before: "})]".to_string(),
4083 ..Default::default()
4084 },
4085 Some(tree_sitter_rust::language()),
4086 ));
4087
4088 let registry = Arc::new(LanguageRegistry::test());
4089 registry.add(language.clone());
4090 cx.update_buffer(|buffer, cx| {
4091 buffer.set_language_registry(registry);
4092 buffer.set_language(Some(language), cx);
4093 });
4094
4095 cx.set_state(
4096 &r#"
4097 🏀ˇ
4098 εˇ
4099 ❤️ˇ
4100 "#
4101 .unindent(),
4102 );
4103
4104 // autoclose multiple nested brackets at multiple cursors
4105 cx.update_editor(|view, cx| {
4106 view.handle_input("{", cx);
4107 view.handle_input("{", cx);
4108 view.handle_input("{", cx);
4109 });
4110 cx.assert_editor_state(
4111 &"
4112 🏀{{{ˇ}}}
4113 ε{{{ˇ}}}
4114 ❤️{{{ˇ}}}
4115 "
4116 .unindent(),
4117 );
4118
4119 // insert a different closing bracket
4120 cx.update_editor(|view, cx| {
4121 view.handle_input(")", cx);
4122 });
4123 cx.assert_editor_state(
4124 &"
4125 🏀{{{)ˇ}}}
4126 ε{{{)ˇ}}}
4127 ❤️{{{)ˇ}}}
4128 "
4129 .unindent(),
4130 );
4131
4132 // skip over the auto-closed brackets when typing a closing bracket
4133 cx.update_editor(|view, cx| {
4134 view.move_right(&MoveRight, cx);
4135 view.handle_input("}", cx);
4136 view.handle_input("}", cx);
4137 view.handle_input("}", cx);
4138 });
4139 cx.assert_editor_state(
4140 &"
4141 🏀{{{)}}}}ˇ
4142 ε{{{)}}}}ˇ
4143 ❤️{{{)}}}}ˇ
4144 "
4145 .unindent(),
4146 );
4147
4148 // autoclose multi-character pairs
4149 cx.set_state(
4150 &"
4151 ˇ
4152 ˇ
4153 "
4154 .unindent(),
4155 );
4156 cx.update_editor(|view, cx| {
4157 view.handle_input("/", cx);
4158 view.handle_input("*", cx);
4159 });
4160 cx.assert_editor_state(
4161 &"
4162 /*ˇ */
4163 /*ˇ */
4164 "
4165 .unindent(),
4166 );
4167
4168 // one cursor autocloses a multi-character pair, one cursor
4169 // does not autoclose.
4170 cx.set_state(
4171 &"
4172 /ˇ
4173 ˇ
4174 "
4175 .unindent(),
4176 );
4177 cx.update_editor(|view, cx| view.handle_input("*", cx));
4178 cx.assert_editor_state(
4179 &"
4180 /*ˇ */
4181 *ˇ
4182 "
4183 .unindent(),
4184 );
4185
4186 // Don't autoclose if the next character isn't whitespace and isn't
4187 // listed in the language's "autoclose_before" section.
4188 cx.set_state("ˇa b");
4189 cx.update_editor(|view, cx| view.handle_input("{", cx));
4190 cx.assert_editor_state("{ˇa b");
4191
4192 // Don't autoclose if `close` is false for the bracket pair
4193 cx.set_state("ˇ");
4194 cx.update_editor(|view, cx| view.handle_input("[", cx));
4195 cx.assert_editor_state("[ˇ");
4196
4197 // Surround with brackets if text is selected
4198 cx.set_state("«aˇ» b");
4199 cx.update_editor(|view, cx| view.handle_input("{", cx));
4200 cx.assert_editor_state("{«aˇ»} b");
4201
4202 // Autclose pair where the start and end characters are the same
4203 cx.set_state("aˇ");
4204 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4205 cx.assert_editor_state("a\"ˇ\"");
4206 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4207 cx.assert_editor_state("a\"\"ˇ");
4208}
4209
4210#[gpui::test]
4211async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4212 init_test(cx, |_| {});
4213
4214 let mut cx = EditorTestContext::new(cx).await;
4215
4216 let html_language = Arc::new(
4217 Language::new(
4218 LanguageConfig {
4219 name: "HTML".into(),
4220 brackets: BracketPairConfig {
4221 pairs: vec![
4222 BracketPair {
4223 start: "<".into(),
4224 end: ">".into(),
4225 close: true,
4226 ..Default::default()
4227 },
4228 BracketPair {
4229 start: "{".into(),
4230 end: "}".into(),
4231 close: true,
4232 ..Default::default()
4233 },
4234 BracketPair {
4235 start: "(".into(),
4236 end: ")".into(),
4237 close: true,
4238 ..Default::default()
4239 },
4240 ],
4241 ..Default::default()
4242 },
4243 autoclose_before: "})]>".into(),
4244 ..Default::default()
4245 },
4246 Some(tree_sitter_html::language()),
4247 )
4248 .with_injection_query(
4249 r#"
4250 (script_element
4251 (raw_text) @content
4252 (#set! "language" "javascript"))
4253 "#,
4254 )
4255 .unwrap(),
4256 );
4257
4258 let javascript_language = Arc::new(Language::new(
4259 LanguageConfig {
4260 name: "JavaScript".into(),
4261 brackets: BracketPairConfig {
4262 pairs: vec![
4263 BracketPair {
4264 start: "/*".into(),
4265 end: " */".into(),
4266 close: true,
4267 ..Default::default()
4268 },
4269 BracketPair {
4270 start: "{".into(),
4271 end: "}".into(),
4272 close: true,
4273 ..Default::default()
4274 },
4275 BracketPair {
4276 start: "(".into(),
4277 end: ")".into(),
4278 close: true,
4279 ..Default::default()
4280 },
4281 ],
4282 ..Default::default()
4283 },
4284 autoclose_before: "})]>".into(),
4285 ..Default::default()
4286 },
4287 Some(tree_sitter_typescript::language_tsx()),
4288 ));
4289
4290 let registry = Arc::new(LanguageRegistry::test());
4291 registry.add(html_language.clone());
4292 registry.add(javascript_language.clone());
4293
4294 cx.update_buffer(|buffer, cx| {
4295 buffer.set_language_registry(registry);
4296 buffer.set_language(Some(html_language), cx);
4297 });
4298
4299 cx.set_state(
4300 &r#"
4301 <body>ˇ
4302 <script>
4303 var x = 1;ˇ
4304 </script>
4305 </body>ˇ
4306 "#
4307 .unindent(),
4308 );
4309
4310 // Precondition: different languages are active at different locations.
4311 cx.update_editor(|editor, cx| {
4312 let snapshot = editor.snapshot(cx);
4313 let cursors = editor.selections.ranges::<usize>(cx);
4314 let languages = cursors
4315 .iter()
4316 .map(|c| snapshot.language_at(c.start).unwrap().name())
4317 .collect::<Vec<_>>();
4318 assert_eq!(
4319 languages,
4320 &["HTML".into(), "JavaScript".into(), "HTML".into()]
4321 );
4322 });
4323
4324 // Angle brackets autoclose in HTML, but not JavaScript.
4325 cx.update_editor(|editor, cx| {
4326 editor.handle_input("<", cx);
4327 editor.handle_input("a", cx);
4328 });
4329 cx.assert_editor_state(
4330 &r#"
4331 <body><aˇ>
4332 <script>
4333 var x = 1;<aˇ
4334 </script>
4335 </body><aˇ>
4336 "#
4337 .unindent(),
4338 );
4339
4340 // Curly braces and parens autoclose in both HTML and JavaScript.
4341 cx.update_editor(|editor, cx| {
4342 editor.handle_input(" b=", cx);
4343 editor.handle_input("{", cx);
4344 editor.handle_input("c", cx);
4345 editor.handle_input("(", cx);
4346 });
4347 cx.assert_editor_state(
4348 &r#"
4349 <body><a b={c(ˇ)}>
4350 <script>
4351 var x = 1;<a b={c(ˇ)}
4352 </script>
4353 </body><a b={c(ˇ)}>
4354 "#
4355 .unindent(),
4356 );
4357
4358 // Brackets that were already autoclosed are skipped.
4359 cx.update_editor(|editor, cx| {
4360 editor.handle_input(")", cx);
4361 editor.handle_input("d", cx);
4362 editor.handle_input("}", cx);
4363 });
4364 cx.assert_editor_state(
4365 &r#"
4366 <body><a b={c()d}ˇ>
4367 <script>
4368 var x = 1;<a b={c()d}ˇ
4369 </script>
4370 </body><a b={c()d}ˇ>
4371 "#
4372 .unindent(),
4373 );
4374 cx.update_editor(|editor, cx| {
4375 editor.handle_input(">", cx);
4376 });
4377 cx.assert_editor_state(
4378 &r#"
4379 <body><a b={c()d}>ˇ
4380 <script>
4381 var x = 1;<a b={c()d}>ˇ
4382 </script>
4383 </body><a b={c()d}>ˇ
4384 "#
4385 .unindent(),
4386 );
4387
4388 // Reset
4389 cx.set_state(
4390 &r#"
4391 <body>ˇ
4392 <script>
4393 var x = 1;ˇ
4394 </script>
4395 </body>ˇ
4396 "#
4397 .unindent(),
4398 );
4399
4400 cx.update_editor(|editor, cx| {
4401 editor.handle_input("<", cx);
4402 });
4403 cx.assert_editor_state(
4404 &r#"
4405 <body><ˇ>
4406 <script>
4407 var x = 1;<ˇ
4408 </script>
4409 </body><ˇ>
4410 "#
4411 .unindent(),
4412 );
4413
4414 // When backspacing, the closing angle brackets are removed.
4415 cx.update_editor(|editor, cx| {
4416 editor.backspace(&Backspace, cx);
4417 });
4418 cx.assert_editor_state(
4419 &r#"
4420 <body>ˇ
4421 <script>
4422 var x = 1;ˇ
4423 </script>
4424 </body>ˇ
4425 "#
4426 .unindent(),
4427 );
4428
4429 // Block comments autoclose in JavaScript, but not HTML.
4430 cx.update_editor(|editor, cx| {
4431 editor.handle_input("/", cx);
4432 editor.handle_input("*", cx);
4433 });
4434 cx.assert_editor_state(
4435 &r#"
4436 <body>/*ˇ
4437 <script>
4438 var x = 1;/*ˇ */
4439 </script>
4440 </body>/*ˇ
4441 "#
4442 .unindent(),
4443 );
4444}
4445
4446#[gpui::test]
4447async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4448 init_test(cx, |_| {});
4449
4450 let mut cx = EditorTestContext::new(cx).await;
4451
4452 let rust_language = Arc::new(
4453 Language::new(
4454 LanguageConfig {
4455 name: "Rust".into(),
4456 brackets: serde_json::from_value(json!([
4457 { "start": "{", "end": "}", "close": true, "newline": true },
4458 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4459 ]))
4460 .unwrap(),
4461 autoclose_before: "})]>".into(),
4462 ..Default::default()
4463 },
4464 Some(tree_sitter_rust::language()),
4465 )
4466 .with_override_query("(string_literal) @string")
4467 .unwrap(),
4468 );
4469
4470 let registry = Arc::new(LanguageRegistry::test());
4471 registry.add(rust_language.clone());
4472
4473 cx.update_buffer(|buffer, cx| {
4474 buffer.set_language_registry(registry);
4475 buffer.set_language(Some(rust_language), cx);
4476 });
4477
4478 cx.set_state(
4479 &r#"
4480 let x = ˇ
4481 "#
4482 .unindent(),
4483 );
4484
4485 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4486 cx.update_editor(|editor, cx| {
4487 editor.handle_input("\"", cx);
4488 });
4489 cx.assert_editor_state(
4490 &r#"
4491 let x = "ˇ"
4492 "#
4493 .unindent(),
4494 );
4495
4496 // Inserting another quotation mark. The cursor moves across the existing
4497 // automatically-inserted quotation mark.
4498 cx.update_editor(|editor, cx| {
4499 editor.handle_input("\"", cx);
4500 });
4501 cx.assert_editor_state(
4502 &r#"
4503 let x = ""ˇ
4504 "#
4505 .unindent(),
4506 );
4507
4508 // Reset
4509 cx.set_state(
4510 &r#"
4511 let x = ˇ
4512 "#
4513 .unindent(),
4514 );
4515
4516 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4517 cx.update_editor(|editor, cx| {
4518 editor.handle_input("\"", cx);
4519 editor.handle_input(" ", cx);
4520 editor.move_left(&Default::default(), cx);
4521 editor.handle_input("\\", cx);
4522 editor.handle_input("\"", cx);
4523 });
4524 cx.assert_editor_state(
4525 &r#"
4526 let x = "\"ˇ "
4527 "#
4528 .unindent(),
4529 );
4530
4531 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4532 // mark. Nothing is inserted.
4533 cx.update_editor(|editor, cx| {
4534 editor.move_right(&Default::default(), cx);
4535 editor.handle_input("\"", cx);
4536 });
4537 cx.assert_editor_state(
4538 &r#"
4539 let x = "\" "ˇ
4540 "#
4541 .unindent(),
4542 );
4543}
4544
4545#[gpui::test]
4546async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4547 init_test(cx, |_| {});
4548
4549 let language = Arc::new(Language::new(
4550 LanguageConfig {
4551 brackets: BracketPairConfig {
4552 pairs: vec![
4553 BracketPair {
4554 start: "{".to_string(),
4555 end: "}".to_string(),
4556 close: true,
4557 newline: true,
4558 },
4559 BracketPair {
4560 start: "/* ".to_string(),
4561 end: "*/".to_string(),
4562 close: true,
4563 ..Default::default()
4564 },
4565 ],
4566 ..Default::default()
4567 },
4568 ..Default::default()
4569 },
4570 Some(tree_sitter_rust::language()),
4571 ));
4572
4573 let text = r#"
4574 a
4575 b
4576 c
4577 "#
4578 .unindent();
4579
4580 let buffer = cx.build_model(|cx| {
4581 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4582 });
4583 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4584 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4585 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4586 .await;
4587
4588 view.update(cx, |view, cx| {
4589 view.change_selections(None, cx, |s| {
4590 s.select_display_ranges([
4591 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4592 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4593 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4594 ])
4595 });
4596
4597 view.handle_input("{", cx);
4598 view.handle_input("{", cx);
4599 view.handle_input("{", cx);
4600 assert_eq!(
4601 view.text(cx),
4602 "
4603 {{{a}}}
4604 {{{b}}}
4605 {{{c}}}
4606 "
4607 .unindent()
4608 );
4609 assert_eq!(
4610 view.selections.display_ranges(cx),
4611 [
4612 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4613 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4614 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4615 ]
4616 );
4617
4618 view.undo(&Undo, cx);
4619 view.undo(&Undo, cx);
4620 view.undo(&Undo, cx);
4621 assert_eq!(
4622 view.text(cx),
4623 "
4624 a
4625 b
4626 c
4627 "
4628 .unindent()
4629 );
4630 assert_eq!(
4631 view.selections.display_ranges(cx),
4632 [
4633 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4634 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4635 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4636 ]
4637 );
4638
4639 // Ensure inserting the first character of a multi-byte bracket pair
4640 // doesn't surround the selections with the bracket.
4641 view.handle_input("/", cx);
4642 assert_eq!(
4643 view.text(cx),
4644 "
4645 /
4646 /
4647 /
4648 "
4649 .unindent()
4650 );
4651 assert_eq!(
4652 view.selections.display_ranges(cx),
4653 [
4654 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4655 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4656 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4657 ]
4658 );
4659
4660 view.undo(&Undo, cx);
4661 assert_eq!(
4662 view.text(cx),
4663 "
4664 a
4665 b
4666 c
4667 "
4668 .unindent()
4669 );
4670 assert_eq!(
4671 view.selections.display_ranges(cx),
4672 [
4673 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4674 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4675 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4676 ]
4677 );
4678
4679 // Ensure inserting the last character of a multi-byte bracket pair
4680 // doesn't surround the selections with the bracket.
4681 view.handle_input("*", cx);
4682 assert_eq!(
4683 view.text(cx),
4684 "
4685 *
4686 *
4687 *
4688 "
4689 .unindent()
4690 );
4691 assert_eq!(
4692 view.selections.display_ranges(cx),
4693 [
4694 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4695 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4696 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4697 ]
4698 );
4699 });
4700}
4701
4702#[gpui::test]
4703async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4704 init_test(cx, |_| {});
4705
4706 let language = Arc::new(Language::new(
4707 LanguageConfig {
4708 brackets: BracketPairConfig {
4709 pairs: vec![BracketPair {
4710 start: "{".to_string(),
4711 end: "}".to_string(),
4712 close: true,
4713 newline: true,
4714 }],
4715 ..Default::default()
4716 },
4717 autoclose_before: "}".to_string(),
4718 ..Default::default()
4719 },
4720 Some(tree_sitter_rust::language()),
4721 ));
4722
4723 let text = r#"
4724 a
4725 b
4726 c
4727 "#
4728 .unindent();
4729
4730 let buffer = cx.build_model(|cx| {
4731 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4732 });
4733 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4734 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4735 editor
4736 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4737 .await;
4738
4739 editor.update(cx, |editor, cx| {
4740 editor.change_selections(None, cx, |s| {
4741 s.select_ranges([
4742 Point::new(0, 1)..Point::new(0, 1),
4743 Point::new(1, 1)..Point::new(1, 1),
4744 Point::new(2, 1)..Point::new(2, 1),
4745 ])
4746 });
4747
4748 editor.handle_input("{", cx);
4749 editor.handle_input("{", cx);
4750 editor.handle_input("_", cx);
4751 assert_eq!(
4752 editor.text(cx),
4753 "
4754 a{{_}}
4755 b{{_}}
4756 c{{_}}
4757 "
4758 .unindent()
4759 );
4760 assert_eq!(
4761 editor.selections.ranges::<Point>(cx),
4762 [
4763 Point::new(0, 4)..Point::new(0, 4),
4764 Point::new(1, 4)..Point::new(1, 4),
4765 Point::new(2, 4)..Point::new(2, 4)
4766 ]
4767 );
4768
4769 editor.backspace(&Default::default(), cx);
4770 editor.backspace(&Default::default(), cx);
4771 assert_eq!(
4772 editor.text(cx),
4773 "
4774 a{}
4775 b{}
4776 c{}
4777 "
4778 .unindent()
4779 );
4780 assert_eq!(
4781 editor.selections.ranges::<Point>(cx),
4782 [
4783 Point::new(0, 2)..Point::new(0, 2),
4784 Point::new(1, 2)..Point::new(1, 2),
4785 Point::new(2, 2)..Point::new(2, 2)
4786 ]
4787 );
4788
4789 editor.delete_to_previous_word_start(&Default::default(), cx);
4790 assert_eq!(
4791 editor.text(cx),
4792 "
4793 a
4794 b
4795 c
4796 "
4797 .unindent()
4798 );
4799 assert_eq!(
4800 editor.selections.ranges::<Point>(cx),
4801 [
4802 Point::new(0, 1)..Point::new(0, 1),
4803 Point::new(1, 1)..Point::new(1, 1),
4804 Point::new(2, 1)..Point::new(2, 1)
4805 ]
4806 );
4807 });
4808}
4809
4810// todo!(select_anchor_ranges)
4811#[gpui::test]
4812async fn test_snippets(cx: &mut gpui::TestAppContext) {
4813 init_test(cx, |_| {});
4814
4815 let (text, insertion_ranges) = marked_text_ranges(
4816 indoc! {"
4817 a.ˇ b
4818 a.ˇ b
4819 a.ˇ b
4820 "},
4821 false,
4822 );
4823
4824 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4825 let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4826
4827 editor.update(cx, |editor, cx| {
4828 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4829
4830 editor
4831 .insert_snippet(&insertion_ranges, snippet, cx)
4832 .unwrap();
4833
4834 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4835 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4836 assert_eq!(editor.text(cx), expected_text);
4837 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4838 }
4839
4840 assert(
4841 editor,
4842 cx,
4843 indoc! {"
4844 a.f(«one», two, «three») b
4845 a.f(«one», two, «three») b
4846 a.f(«one», two, «three») b
4847 "},
4848 );
4849
4850 // Can't move earlier than the first tab stop
4851 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4852 assert(
4853 editor,
4854 cx,
4855 indoc! {"
4856 a.f(«one», two, «three») b
4857 a.f(«one», two, «three») b
4858 a.f(«one», two, «three») b
4859 "},
4860 );
4861
4862 assert!(editor.move_to_next_snippet_tabstop(cx));
4863 assert(
4864 editor,
4865 cx,
4866 indoc! {"
4867 a.f(one, «two», three) b
4868 a.f(one, «two», three) b
4869 a.f(one, «two», three) b
4870 "},
4871 );
4872
4873 editor.move_to_prev_snippet_tabstop(cx);
4874 assert(
4875 editor,
4876 cx,
4877 indoc! {"
4878 a.f(«one», two, «three») b
4879 a.f(«one», two, «three») b
4880 a.f(«one», two, «three») b
4881 "},
4882 );
4883
4884 assert!(editor.move_to_next_snippet_tabstop(cx));
4885 assert(
4886 editor,
4887 cx,
4888 indoc! {"
4889 a.f(one, «two», three) b
4890 a.f(one, «two», three) b
4891 a.f(one, «two», three) b
4892 "},
4893 );
4894 assert!(editor.move_to_next_snippet_tabstop(cx));
4895 assert(
4896 editor,
4897 cx,
4898 indoc! {"
4899 a.f(one, two, three)ˇ b
4900 a.f(one, two, three)ˇ b
4901 a.f(one, two, three)ˇ b
4902 "},
4903 );
4904
4905 // As soon as the last tab stop is reached, snippet state is gone
4906 editor.move_to_prev_snippet_tabstop(cx);
4907 assert(
4908 editor,
4909 cx,
4910 indoc! {"
4911 a.f(one, two, three)ˇ b
4912 a.f(one, two, three)ˇ b
4913 a.f(one, two, three)ˇ b
4914 "},
4915 );
4916 });
4917}
4918
4919#[gpui::test]
4920async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4921 init_test(cx, |_| {});
4922
4923 let mut language = Language::new(
4924 LanguageConfig {
4925 name: "Rust".into(),
4926 path_suffixes: vec!["rs".to_string()],
4927 ..Default::default()
4928 },
4929 Some(tree_sitter_rust::language()),
4930 );
4931 let mut fake_servers = language
4932 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4933 capabilities: lsp::ServerCapabilities {
4934 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4935 ..Default::default()
4936 },
4937 ..Default::default()
4938 }))
4939 .await;
4940
4941 let fs = FakeFs::new(cx.executor());
4942 fs.insert_file("/file.rs", Default::default()).await;
4943
4944 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4945 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4946 let buffer = project
4947 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4948 .await
4949 .unwrap();
4950
4951 cx.executor().start_waiting();
4952 let fake_server = fake_servers.next().await.unwrap();
4953
4954 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4955 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4956 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4957 assert!(cx.read(|cx| editor.is_dirty(cx)));
4958
4959 let save = editor
4960 .update(cx, |editor, cx| editor.save(project.clone(), cx))
4961 .unwrap();
4962 fake_server
4963 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4964 assert_eq!(
4965 params.text_document.uri,
4966 lsp::Url::from_file_path("/file.rs").unwrap()
4967 );
4968 assert_eq!(params.options.tab_size, 4);
4969 Ok(Some(vec![lsp::TextEdit::new(
4970 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4971 ", ".to_string(),
4972 )]))
4973 })
4974 .next()
4975 .await;
4976 cx.executor().start_waiting();
4977 let x = save.await;
4978
4979 assert_eq!(
4980 editor.update(cx, |editor, cx| editor.text(cx)),
4981 "one, two\nthree\n"
4982 );
4983 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4984
4985 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4986 assert!(cx.read(|cx| editor.is_dirty(cx)));
4987
4988 // Ensure we can still save even if formatting hangs.
4989 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4990 assert_eq!(
4991 params.text_document.uri,
4992 lsp::Url::from_file_path("/file.rs").unwrap()
4993 );
4994 futures::future::pending::<()>().await;
4995 unreachable!()
4996 });
4997 let save = editor
4998 .update(cx, |editor, cx| editor.save(project.clone(), cx))
4999 .unwrap();
5000 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5001 cx.executor().start_waiting();
5002 save.await;
5003 assert_eq!(
5004 editor.update(cx, |editor, cx| editor.text(cx)),
5005 "one\ntwo\nthree\n"
5006 );
5007 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5008
5009 // Set rust language override and assert overridden tabsize is sent to language server
5010 update_test_language_settings(cx, |settings| {
5011 settings.languages.insert(
5012 "Rust".into(),
5013 LanguageSettingsContent {
5014 tab_size: NonZeroU32::new(8),
5015 ..Default::default()
5016 },
5017 );
5018 });
5019
5020 let save = editor
5021 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5022 .unwrap();
5023 fake_server
5024 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5025 assert_eq!(
5026 params.text_document.uri,
5027 lsp::Url::from_file_path("/file.rs").unwrap()
5028 );
5029 assert_eq!(params.options.tab_size, 8);
5030 Ok(Some(vec![]))
5031 })
5032 .next()
5033 .await;
5034 cx.executor().start_waiting();
5035 save.await;
5036}
5037
5038#[gpui::test]
5039async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5040 init_test(cx, |_| {});
5041
5042 let mut language = Language::new(
5043 LanguageConfig {
5044 name: "Rust".into(),
5045 path_suffixes: vec!["rs".to_string()],
5046 ..Default::default()
5047 },
5048 Some(tree_sitter_rust::language()),
5049 );
5050 let mut fake_servers = language
5051 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5052 capabilities: lsp::ServerCapabilities {
5053 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5054 ..Default::default()
5055 },
5056 ..Default::default()
5057 }))
5058 .await;
5059
5060 let fs = FakeFs::new(cx.executor());
5061 fs.insert_file("/file.rs", Default::default()).await;
5062
5063 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5064 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5065 let buffer = project
5066 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5067 .await
5068 .unwrap();
5069
5070 cx.executor().start_waiting();
5071 let fake_server = fake_servers.next().await.unwrap();
5072
5073 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
5074 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5075 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5076 assert!(cx.read(|cx| editor.is_dirty(cx)));
5077
5078 let save = editor
5079 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5080 .unwrap();
5081 fake_server
5082 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5083 assert_eq!(
5084 params.text_document.uri,
5085 lsp::Url::from_file_path("/file.rs").unwrap()
5086 );
5087 assert_eq!(params.options.tab_size, 4);
5088 Ok(Some(vec![lsp::TextEdit::new(
5089 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5090 ", ".to_string(),
5091 )]))
5092 })
5093 .next()
5094 .await;
5095 cx.executor().start_waiting();
5096 save.await;
5097 assert_eq!(
5098 editor.update(cx, |editor, cx| editor.text(cx)),
5099 "one, two\nthree\n"
5100 );
5101 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5102
5103 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5104 assert!(cx.read(|cx| editor.is_dirty(cx)));
5105
5106 // Ensure we can still save even if formatting hangs.
5107 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5108 move |params, _| async move {
5109 assert_eq!(
5110 params.text_document.uri,
5111 lsp::Url::from_file_path("/file.rs").unwrap()
5112 );
5113 futures::future::pending::<()>().await;
5114 unreachable!()
5115 },
5116 );
5117 let save = editor
5118 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5119 .unwrap();
5120 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5121 cx.executor().start_waiting();
5122 save.await;
5123 assert_eq!(
5124 editor.update(cx, |editor, cx| editor.text(cx)),
5125 "one\ntwo\nthree\n"
5126 );
5127 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5128
5129 // Set rust language override and assert overridden tabsize is sent to language server
5130 update_test_language_settings(cx, |settings| {
5131 settings.languages.insert(
5132 "Rust".into(),
5133 LanguageSettingsContent {
5134 tab_size: NonZeroU32::new(8),
5135 ..Default::default()
5136 },
5137 );
5138 });
5139
5140 let save = editor
5141 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5142 .unwrap();
5143 fake_server
5144 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5145 assert_eq!(
5146 params.text_document.uri,
5147 lsp::Url::from_file_path("/file.rs").unwrap()
5148 );
5149 assert_eq!(params.options.tab_size, 8);
5150 Ok(Some(vec![]))
5151 })
5152 .next()
5153 .await;
5154 cx.executor().start_waiting();
5155 save.await;
5156}
5157
5158#[gpui::test]
5159async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5160 init_test(cx, |settings| {
5161 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
5162 });
5163
5164 let mut language = Language::new(
5165 LanguageConfig {
5166 name: "Rust".into(),
5167 path_suffixes: vec!["rs".to_string()],
5168 // Enable Prettier formatting for the same buffer, and ensure
5169 // LSP is called instead of Prettier.
5170 prettier_parser_name: Some("test_parser".to_string()),
5171 ..Default::default()
5172 },
5173 Some(tree_sitter_rust::language()),
5174 );
5175 let mut fake_servers = language
5176 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5177 capabilities: lsp::ServerCapabilities {
5178 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5179 ..Default::default()
5180 },
5181 ..Default::default()
5182 }))
5183 .await;
5184
5185 let fs = FakeFs::new(cx.executor());
5186 fs.insert_file("/file.rs", Default::default()).await;
5187
5188 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5189 project.update(cx, |project, _| {
5190 project.languages().add(Arc::new(language));
5191 });
5192 let buffer = project
5193 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5194 .await
5195 .unwrap();
5196
5197 cx.executor().start_waiting();
5198 let fake_server = fake_servers.next().await.unwrap();
5199
5200 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
5201 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5202 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5203
5204 let format = editor
5205 .update(cx, |editor, cx| {
5206 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5207 })
5208 .unwrap();
5209 fake_server
5210 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5211 assert_eq!(
5212 params.text_document.uri,
5213 lsp::Url::from_file_path("/file.rs").unwrap()
5214 );
5215 assert_eq!(params.options.tab_size, 4);
5216 Ok(Some(vec![lsp::TextEdit::new(
5217 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5218 ", ".to_string(),
5219 )]))
5220 })
5221 .next()
5222 .await;
5223 cx.executor().start_waiting();
5224 format.await;
5225 assert_eq!(
5226 editor.update(cx, |editor, cx| editor.text(cx)),
5227 "one, two\nthree\n"
5228 );
5229
5230 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5231 // Ensure we don't lock if formatting hangs.
5232 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5233 assert_eq!(
5234 params.text_document.uri,
5235 lsp::Url::from_file_path("/file.rs").unwrap()
5236 );
5237 futures::future::pending::<()>().await;
5238 unreachable!()
5239 });
5240 let format = editor
5241 .update(cx, |editor, cx| {
5242 editor.perform_format(project, FormatTrigger::Manual, cx)
5243 })
5244 .unwrap();
5245 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5246 cx.executor().start_waiting();
5247 format.await;
5248 assert_eq!(
5249 editor.update(cx, |editor, cx| editor.text(cx)),
5250 "one\ntwo\nthree\n"
5251 );
5252}
5253
5254#[gpui::test]
5255async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5256 init_test(cx, |_| {});
5257
5258 let mut cx = EditorLspTestContext::new_rust(
5259 lsp::ServerCapabilities {
5260 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5261 ..Default::default()
5262 },
5263 cx,
5264 )
5265 .await;
5266
5267 cx.set_state(indoc! {"
5268 one.twoˇ
5269 "});
5270
5271 // The format request takes a long time. When it completes, it inserts
5272 // a newline and an indent before the `.`
5273 cx.lsp
5274 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5275 let executor = cx.background_executor().clone();
5276 async move {
5277 executor.timer(Duration::from_millis(100)).await;
5278 Ok(Some(vec![lsp::TextEdit {
5279 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5280 new_text: "\n ".into(),
5281 }]))
5282 }
5283 });
5284
5285 // Submit a format request.
5286 let format_1 = cx
5287 .update_editor(|editor, cx| editor.format(&Format, cx))
5288 .unwrap();
5289 cx.executor().run_until_parked();
5290
5291 // Submit a second format request.
5292 let format_2 = cx
5293 .update_editor(|editor, cx| editor.format(&Format, cx))
5294 .unwrap();
5295 cx.executor().run_until_parked();
5296
5297 // Wait for both format requests to complete
5298 cx.executor().advance_clock(Duration::from_millis(200));
5299 cx.executor().start_waiting();
5300 format_1.await.unwrap();
5301 cx.executor().start_waiting();
5302 format_2.await.unwrap();
5303
5304 // The formatting edits only happens once.
5305 cx.assert_editor_state(indoc! {"
5306 one
5307 .twoˇ
5308 "});
5309}
5310
5311#[gpui::test]
5312async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5313 init_test(cx, |settings| {
5314 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5315 });
5316
5317 let mut cx = EditorLspTestContext::new_rust(
5318 lsp::ServerCapabilities {
5319 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5320 ..Default::default()
5321 },
5322 cx,
5323 )
5324 .await;
5325
5326 // Set up a buffer white some trailing whitespace and no trailing newline.
5327 cx.set_state(
5328 &[
5329 "one ", //
5330 "twoˇ", //
5331 "three ", //
5332 "four", //
5333 ]
5334 .join("\n"),
5335 );
5336
5337 // Submit a format request.
5338 let format = cx
5339 .update_editor(|editor, cx| editor.format(&Format, cx))
5340 .unwrap();
5341
5342 // Record which buffer changes have been sent to the language server
5343 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5344 cx.lsp
5345 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5346 let buffer_changes = buffer_changes.clone();
5347 move |params, _| {
5348 buffer_changes.lock().extend(
5349 params
5350 .content_changes
5351 .into_iter()
5352 .map(|e| (e.range.unwrap(), e.text)),
5353 );
5354 }
5355 });
5356
5357 // Handle formatting requests to the language server.
5358 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5359 let buffer_changes = buffer_changes.clone();
5360 move |_, _| {
5361 // When formatting is requested, trailing whitespace has already been stripped,
5362 // and the trailing newline has already been added.
5363 assert_eq!(
5364 &buffer_changes.lock()[1..],
5365 &[
5366 (
5367 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5368 "".into()
5369 ),
5370 (
5371 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5372 "".into()
5373 ),
5374 (
5375 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5376 "\n".into()
5377 ),
5378 ]
5379 );
5380
5381 // Insert blank lines between each line of the buffer.
5382 async move {
5383 Ok(Some(vec![
5384 lsp::TextEdit {
5385 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5386 new_text: "\n".into(),
5387 },
5388 lsp::TextEdit {
5389 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5390 new_text: "\n".into(),
5391 },
5392 ]))
5393 }
5394 }
5395 });
5396
5397 // After formatting the buffer, the trailing whitespace is stripped,
5398 // a newline is appended, and the edits provided by the language server
5399 // have been applied.
5400 format.await.unwrap();
5401 cx.assert_editor_state(
5402 &[
5403 "one", //
5404 "", //
5405 "twoˇ", //
5406 "", //
5407 "three", //
5408 "four", //
5409 "", //
5410 ]
5411 .join("\n"),
5412 );
5413
5414 // Undoing the formatting undoes the trailing whitespace removal, the
5415 // trailing newline, and the LSP edits.
5416 cx.update_buffer(|buffer, cx| buffer.undo(cx));
5417 cx.assert_editor_state(
5418 &[
5419 "one ", //
5420 "twoˇ", //
5421 "three ", //
5422 "four", //
5423 ]
5424 .join("\n"),
5425 );
5426}
5427
5428#[gpui::test]
5429async fn test_completion(cx: &mut gpui::TestAppContext) {
5430 init_test(cx, |_| {});
5431
5432 let mut cx = EditorLspTestContext::new_rust(
5433 lsp::ServerCapabilities {
5434 completion_provider: Some(lsp::CompletionOptions {
5435 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5436 resolve_provider: Some(true),
5437 ..Default::default()
5438 }),
5439 ..Default::default()
5440 },
5441 cx,
5442 )
5443 .await;
5444
5445 cx.set_state(indoc! {"
5446 oneˇ
5447 two
5448 three
5449 "});
5450 cx.simulate_keystroke(".");
5451 handle_completion_request(
5452 &mut cx,
5453 indoc! {"
5454 one.|<>
5455 two
5456 three
5457 "},
5458 vec!["first_completion", "second_completion"],
5459 )
5460 .await;
5461 cx.condition(|editor, _| editor.context_menu_visible())
5462 .await;
5463 let apply_additional_edits = cx.update_editor(|editor, cx| {
5464 editor.context_menu_next(&Default::default(), cx);
5465 editor
5466 .confirm_completion(&ConfirmCompletion::default(), cx)
5467 .unwrap()
5468 });
5469 cx.assert_editor_state(indoc! {"
5470 one.second_completionˇ
5471 two
5472 three
5473 "});
5474
5475 handle_resolve_completion_request(
5476 &mut cx,
5477 Some(vec![
5478 (
5479 //This overlaps with the primary completion edit which is
5480 //misbehavior from the LSP spec, test that we filter it out
5481 indoc! {"
5482 one.second_ˇcompletion
5483 two
5484 threeˇ
5485 "},
5486 "overlapping additional edit",
5487 ),
5488 (
5489 indoc! {"
5490 one.second_completion
5491 two
5492 threeˇ
5493 "},
5494 "\nadditional edit",
5495 ),
5496 ]),
5497 )
5498 .await;
5499 apply_additional_edits.await.unwrap();
5500 cx.assert_editor_state(indoc! {"
5501 one.second_completionˇ
5502 two
5503 three
5504 additional edit
5505 "});
5506
5507 cx.set_state(indoc! {"
5508 one.second_completion
5509 twoˇ
5510 threeˇ
5511 additional edit
5512 "});
5513 cx.simulate_keystroke(" ");
5514 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5515 cx.simulate_keystroke("s");
5516 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5517
5518 cx.assert_editor_state(indoc! {"
5519 one.second_completion
5520 two sˇ
5521 three sˇ
5522 additional edit
5523 "});
5524 handle_completion_request(
5525 &mut cx,
5526 indoc! {"
5527 one.second_completion
5528 two s
5529 three <s|>
5530 additional edit
5531 "},
5532 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5533 )
5534 .await;
5535 cx.condition(|editor, _| editor.context_menu_visible())
5536 .await;
5537
5538 cx.simulate_keystroke("i");
5539
5540 handle_completion_request(
5541 &mut cx,
5542 indoc! {"
5543 one.second_completion
5544 two si
5545 three <si|>
5546 additional edit
5547 "},
5548 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5549 )
5550 .await;
5551 cx.condition(|editor, _| editor.context_menu_visible())
5552 .await;
5553
5554 let apply_additional_edits = cx.update_editor(|editor, cx| {
5555 editor
5556 .confirm_completion(&ConfirmCompletion::default(), cx)
5557 .unwrap()
5558 });
5559 cx.assert_editor_state(indoc! {"
5560 one.second_completion
5561 two sixth_completionˇ
5562 three sixth_completionˇ
5563 additional edit
5564 "});
5565
5566 handle_resolve_completion_request(&mut cx, None).await;
5567 apply_additional_edits.await.unwrap();
5568
5569 cx.update(|cx| {
5570 cx.update_global::<SettingsStore, _>(|settings, cx| {
5571 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5572 settings.show_completions_on_input = Some(false);
5573 });
5574 })
5575 });
5576 cx.set_state("editorˇ");
5577 cx.simulate_keystroke(".");
5578 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5579 cx.simulate_keystroke("c");
5580 cx.simulate_keystroke("l");
5581 cx.simulate_keystroke("o");
5582 cx.assert_editor_state("editor.cloˇ");
5583 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5584 cx.update_editor(|editor, cx| {
5585 editor.show_completions(&ShowCompletions, cx);
5586 });
5587 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5588 cx.condition(|editor, _| editor.context_menu_visible())
5589 .await;
5590 let apply_additional_edits = cx.update_editor(|editor, cx| {
5591 editor
5592 .confirm_completion(&ConfirmCompletion::default(), cx)
5593 .unwrap()
5594 });
5595 cx.assert_editor_state("editor.closeˇ");
5596 handle_resolve_completion_request(&mut cx, None).await;
5597 apply_additional_edits.await.unwrap();
5598}
5599
5600#[gpui::test]
5601async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5602 init_test(cx, |_| {});
5603 let mut cx = EditorTestContext::new(cx).await;
5604 let language = Arc::new(Language::new(
5605 LanguageConfig {
5606 line_comment: Some("// ".into()),
5607 ..Default::default()
5608 },
5609 Some(tree_sitter_rust::language()),
5610 ));
5611 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5612
5613 // If multiple selections intersect a line, the line is only toggled once.
5614 cx.set_state(indoc! {"
5615 fn a() {
5616 «//b();
5617 ˇ»// «c();
5618 //ˇ» d();
5619 }
5620 "});
5621
5622 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5623
5624 cx.assert_editor_state(indoc! {"
5625 fn a() {
5626 «b();
5627 c();
5628 ˇ» d();
5629 }
5630 "});
5631
5632 // The comment prefix is inserted at the same column for every line in a
5633 // selection.
5634 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5635
5636 cx.assert_editor_state(indoc! {"
5637 fn a() {
5638 // «b();
5639 // c();
5640 ˇ»// d();
5641 }
5642 "});
5643
5644 // If a selection ends at the beginning of a line, that line is not toggled.
5645 cx.set_selections_state(indoc! {"
5646 fn a() {
5647 // b();
5648 «// c();
5649 ˇ» // d();
5650 }
5651 "});
5652
5653 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5654
5655 cx.assert_editor_state(indoc! {"
5656 fn a() {
5657 // b();
5658 «c();
5659 ˇ» // d();
5660 }
5661 "});
5662
5663 // If a selection span a single line and is empty, the line is toggled.
5664 cx.set_state(indoc! {"
5665 fn a() {
5666 a();
5667 b();
5668 ˇ
5669 }
5670 "});
5671
5672 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5673
5674 cx.assert_editor_state(indoc! {"
5675 fn a() {
5676 a();
5677 b();
5678 //•ˇ
5679 }
5680 "});
5681
5682 // If a selection span multiple lines, empty lines are not toggled.
5683 cx.set_state(indoc! {"
5684 fn a() {
5685 «a();
5686
5687 c();ˇ»
5688 }
5689 "});
5690
5691 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5692
5693 cx.assert_editor_state(indoc! {"
5694 fn a() {
5695 // «a();
5696
5697 // c();ˇ»
5698 }
5699 "});
5700}
5701
5702#[gpui::test]
5703async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5704 init_test(cx, |_| {});
5705
5706 let language = Arc::new(Language::new(
5707 LanguageConfig {
5708 line_comment: Some("// ".into()),
5709 ..Default::default()
5710 },
5711 Some(tree_sitter_rust::language()),
5712 ));
5713
5714 let registry = Arc::new(LanguageRegistry::test());
5715 registry.add(language.clone());
5716
5717 let mut cx = EditorTestContext::new(cx).await;
5718 cx.update_buffer(|buffer, cx| {
5719 buffer.set_language_registry(registry);
5720 buffer.set_language(Some(language), cx);
5721 });
5722
5723 let toggle_comments = &ToggleComments {
5724 advance_downwards: true,
5725 };
5726
5727 // Single cursor on one line -> advance
5728 // Cursor moves horizontally 3 characters as well on non-blank line
5729 cx.set_state(indoc!(
5730 "fn a() {
5731 ˇdog();
5732 cat();
5733 }"
5734 ));
5735 cx.update_editor(|editor, cx| {
5736 editor.toggle_comments(toggle_comments, cx);
5737 });
5738 cx.assert_editor_state(indoc!(
5739 "fn a() {
5740 // dog();
5741 catˇ();
5742 }"
5743 ));
5744
5745 // Single selection on one line -> don't advance
5746 cx.set_state(indoc!(
5747 "fn a() {
5748 «dog()ˇ»;
5749 cat();
5750 }"
5751 ));
5752 cx.update_editor(|editor, cx| {
5753 editor.toggle_comments(toggle_comments, cx);
5754 });
5755 cx.assert_editor_state(indoc!(
5756 "fn a() {
5757 // «dog()ˇ»;
5758 cat();
5759 }"
5760 ));
5761
5762 // Multiple cursors on one line -> advance
5763 cx.set_state(indoc!(
5764 "fn a() {
5765 ˇdˇog();
5766 cat();
5767 }"
5768 ));
5769 cx.update_editor(|editor, cx| {
5770 editor.toggle_comments(toggle_comments, cx);
5771 });
5772 cx.assert_editor_state(indoc!(
5773 "fn a() {
5774 // dog();
5775 catˇ(ˇ);
5776 }"
5777 ));
5778
5779 // Multiple cursors on one line, with selection -> don't advance
5780 cx.set_state(indoc!(
5781 "fn a() {
5782 ˇdˇog«()ˇ»;
5783 cat();
5784 }"
5785 ));
5786 cx.update_editor(|editor, cx| {
5787 editor.toggle_comments(toggle_comments, cx);
5788 });
5789 cx.assert_editor_state(indoc!(
5790 "fn a() {
5791 // ˇdˇog«()ˇ»;
5792 cat();
5793 }"
5794 ));
5795
5796 // Single cursor on one line -> advance
5797 // Cursor moves to column 0 on blank line
5798 cx.set_state(indoc!(
5799 "fn a() {
5800 ˇdog();
5801
5802 cat();
5803 }"
5804 ));
5805 cx.update_editor(|editor, cx| {
5806 editor.toggle_comments(toggle_comments, cx);
5807 });
5808 cx.assert_editor_state(indoc!(
5809 "fn a() {
5810 // dog();
5811 ˇ
5812 cat();
5813 }"
5814 ));
5815
5816 // Single cursor on one line -> advance
5817 // Cursor starts and ends at column 0
5818 cx.set_state(indoc!(
5819 "fn a() {
5820 ˇ dog();
5821 cat();
5822 }"
5823 ));
5824 cx.update_editor(|editor, cx| {
5825 editor.toggle_comments(toggle_comments, cx);
5826 });
5827 cx.assert_editor_state(indoc!(
5828 "fn a() {
5829 // dog();
5830 ˇ cat();
5831 }"
5832 ));
5833}
5834
5835#[gpui::test]
5836async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5837 init_test(cx, |_| {});
5838
5839 let mut cx = EditorTestContext::new(cx).await;
5840
5841 let html_language = Arc::new(
5842 Language::new(
5843 LanguageConfig {
5844 name: "HTML".into(),
5845 block_comment: Some(("<!-- ".into(), " -->".into())),
5846 ..Default::default()
5847 },
5848 Some(tree_sitter_html::language()),
5849 )
5850 .with_injection_query(
5851 r#"
5852 (script_element
5853 (raw_text) @content
5854 (#set! "language" "javascript"))
5855 "#,
5856 )
5857 .unwrap(),
5858 );
5859
5860 let javascript_language = Arc::new(Language::new(
5861 LanguageConfig {
5862 name: "JavaScript".into(),
5863 line_comment: Some("// ".into()),
5864 ..Default::default()
5865 },
5866 Some(tree_sitter_typescript::language_tsx()),
5867 ));
5868
5869 let registry = Arc::new(LanguageRegistry::test());
5870 registry.add(html_language.clone());
5871 registry.add(javascript_language.clone());
5872
5873 cx.update_buffer(|buffer, cx| {
5874 buffer.set_language_registry(registry);
5875 buffer.set_language(Some(html_language), cx);
5876 });
5877
5878 // Toggle comments for empty selections
5879 cx.set_state(
5880 &r#"
5881 <p>A</p>ˇ
5882 <p>B</p>ˇ
5883 <p>C</p>ˇ
5884 "#
5885 .unindent(),
5886 );
5887 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5888 cx.assert_editor_state(
5889 &r#"
5890 <!-- <p>A</p>ˇ -->
5891 <!-- <p>B</p>ˇ -->
5892 <!-- <p>C</p>ˇ -->
5893 "#
5894 .unindent(),
5895 );
5896 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5897 cx.assert_editor_state(
5898 &r#"
5899 <p>A</p>ˇ
5900 <p>B</p>ˇ
5901 <p>C</p>ˇ
5902 "#
5903 .unindent(),
5904 );
5905
5906 // Toggle comments for mixture of empty and non-empty selections, where
5907 // multiple selections occupy a given line.
5908 cx.set_state(
5909 &r#"
5910 <p>A«</p>
5911 <p>ˇ»B</p>ˇ
5912 <p>C«</p>
5913 <p>ˇ»D</p>ˇ
5914 "#
5915 .unindent(),
5916 );
5917
5918 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5919 cx.assert_editor_state(
5920 &r#"
5921 <!-- <p>A«</p>
5922 <p>ˇ»B</p>ˇ -->
5923 <!-- <p>C«</p>
5924 <p>ˇ»D</p>ˇ -->
5925 "#
5926 .unindent(),
5927 );
5928 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5929 cx.assert_editor_state(
5930 &r#"
5931 <p>A«</p>
5932 <p>ˇ»B</p>ˇ
5933 <p>C«</p>
5934 <p>ˇ»D</p>ˇ
5935 "#
5936 .unindent(),
5937 );
5938
5939 // Toggle comments when different languages are active for different
5940 // selections.
5941 cx.set_state(
5942 &r#"
5943 ˇ<script>
5944 ˇvar x = new Y();
5945 ˇ</script>
5946 "#
5947 .unindent(),
5948 );
5949 cx.executor().run_until_parked();
5950 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5951 cx.assert_editor_state(
5952 &r#"
5953 <!-- ˇ<script> -->
5954 // ˇvar x = new Y();
5955 <!-- ˇ</script> -->
5956 "#
5957 .unindent(),
5958 );
5959}
5960
5961#[gpui::test]
5962fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5963 init_test(cx, |_| {});
5964
5965 let buffer =
5966 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
5967 let multibuffer = cx.build_model(|cx| {
5968 let mut multibuffer = MultiBuffer::new(0);
5969 multibuffer.push_excerpts(
5970 buffer.clone(),
5971 [
5972 ExcerptRange {
5973 context: Point::new(0, 0)..Point::new(0, 4),
5974 primary: None,
5975 },
5976 ExcerptRange {
5977 context: Point::new(1, 0)..Point::new(1, 4),
5978 primary: None,
5979 },
5980 ],
5981 cx,
5982 );
5983 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5984 multibuffer
5985 });
5986
5987 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
5988 view.update(cx, |view, cx| {
5989 assert_eq!(view.text(cx), "aaaa\nbbbb");
5990 view.change_selections(None, cx, |s| {
5991 s.select_ranges([
5992 Point::new(0, 0)..Point::new(0, 0),
5993 Point::new(1, 0)..Point::new(1, 0),
5994 ])
5995 });
5996
5997 view.handle_input("X", cx);
5998 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5999 assert_eq!(
6000 view.selections.ranges(cx),
6001 [
6002 Point::new(0, 1)..Point::new(0, 1),
6003 Point::new(1, 1)..Point::new(1, 1),
6004 ]
6005 );
6006
6007 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6008 view.change_selections(None, cx, |s| {
6009 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6010 });
6011 view.backspace(&Default::default(), cx);
6012 assert_eq!(view.text(cx), "Xa\nbbb");
6013 assert_eq!(
6014 view.selections.ranges(cx),
6015 [Point::new(1, 0)..Point::new(1, 0)]
6016 );
6017
6018 view.change_selections(None, cx, |s| {
6019 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6020 });
6021 view.backspace(&Default::default(), cx);
6022 assert_eq!(view.text(cx), "X\nbb");
6023 assert_eq!(
6024 view.selections.ranges(cx),
6025 [Point::new(0, 1)..Point::new(0, 1)]
6026 );
6027 });
6028}
6029
6030#[gpui::test]
6031fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6032 init_test(cx, |_| {});
6033
6034 let markers = vec![('[', ']').into(), ('(', ')').into()];
6035 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6036 indoc! {"
6037 [aaaa
6038 (bbbb]
6039 cccc)",
6040 },
6041 markers.clone(),
6042 );
6043 let excerpt_ranges = markers.into_iter().map(|marker| {
6044 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6045 ExcerptRange {
6046 context,
6047 primary: None,
6048 }
6049 });
6050 let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
6051 let multibuffer = cx.build_model(|cx| {
6052 let mut multibuffer = MultiBuffer::new(0);
6053 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6054 multibuffer
6055 });
6056
6057 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6058 view.update(cx, |view, cx| {
6059 let (expected_text, selection_ranges) = marked_text_ranges(
6060 indoc! {"
6061 aaaa
6062 bˇbbb
6063 bˇbbˇb
6064 cccc"
6065 },
6066 true,
6067 );
6068 assert_eq!(view.text(cx), expected_text);
6069 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6070
6071 view.handle_input("X", cx);
6072
6073 let (expected_text, expected_selections) = marked_text_ranges(
6074 indoc! {"
6075 aaaa
6076 bXˇbbXb
6077 bXˇbbXˇb
6078 cccc"
6079 },
6080 false,
6081 );
6082 assert_eq!(view.text(cx), expected_text);
6083 assert_eq!(view.selections.ranges(cx), expected_selections);
6084
6085 view.newline(&Newline, cx);
6086 let (expected_text, expected_selections) = marked_text_ranges(
6087 indoc! {"
6088 aaaa
6089 bX
6090 ˇbbX
6091 b
6092 bX
6093 ˇbbX
6094 ˇb
6095 cccc"
6096 },
6097 false,
6098 );
6099 assert_eq!(view.text(cx), expected_text);
6100 assert_eq!(view.selections.ranges(cx), expected_selections);
6101 });
6102}
6103
6104#[gpui::test]
6105fn test_refresh_selections(cx: &mut TestAppContext) {
6106 init_test(cx, |_| {});
6107
6108 let buffer =
6109 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6110 let mut excerpt1_id = None;
6111 let multibuffer = cx.build_model(|cx| {
6112 let mut multibuffer = MultiBuffer::new(0);
6113 excerpt1_id = multibuffer
6114 .push_excerpts(
6115 buffer.clone(),
6116 [
6117 ExcerptRange {
6118 context: Point::new(0, 0)..Point::new(1, 4),
6119 primary: None,
6120 },
6121 ExcerptRange {
6122 context: Point::new(1, 0)..Point::new(2, 4),
6123 primary: None,
6124 },
6125 ],
6126 cx,
6127 )
6128 .into_iter()
6129 .next();
6130 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6131 multibuffer
6132 });
6133
6134 let editor = cx.add_window(|cx| {
6135 let mut editor = build_editor(multibuffer.clone(), cx);
6136 let snapshot = editor.snapshot(cx);
6137 editor.change_selections(None, cx, |s| {
6138 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6139 });
6140 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6141 assert_eq!(
6142 editor.selections.ranges(cx),
6143 [
6144 Point::new(1, 3)..Point::new(1, 3),
6145 Point::new(2, 1)..Point::new(2, 1),
6146 ]
6147 );
6148 editor
6149 });
6150
6151 // Refreshing selections is a no-op when excerpts haven't changed.
6152 editor.update(cx, |editor, cx| {
6153 editor.change_selections(None, cx, |s| s.refresh());
6154 assert_eq!(
6155 editor.selections.ranges(cx),
6156 [
6157 Point::new(1, 3)..Point::new(1, 3),
6158 Point::new(2, 1)..Point::new(2, 1),
6159 ]
6160 );
6161 });
6162
6163 multibuffer.update(cx, |multibuffer, cx| {
6164 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6165 });
6166 editor.update(cx, |editor, cx| {
6167 // Removing an excerpt causes the first selection to become degenerate.
6168 assert_eq!(
6169 editor.selections.ranges(cx),
6170 [
6171 Point::new(0, 0)..Point::new(0, 0),
6172 Point::new(0, 1)..Point::new(0, 1)
6173 ]
6174 );
6175
6176 // Refreshing selections will relocate the first selection to the original buffer
6177 // location.
6178 editor.change_selections(None, cx, |s| s.refresh());
6179 assert_eq!(
6180 editor.selections.ranges(cx),
6181 [
6182 Point::new(0, 1)..Point::new(0, 1),
6183 Point::new(0, 3)..Point::new(0, 3)
6184 ]
6185 );
6186 assert!(editor.selections.pending_anchor().is_some());
6187 });
6188}
6189
6190#[gpui::test]
6191fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6192 init_test(cx, |_| {});
6193
6194 let buffer =
6195 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6196 let mut excerpt1_id = None;
6197 let multibuffer = cx.build_model(|cx| {
6198 let mut multibuffer = MultiBuffer::new(0);
6199 excerpt1_id = multibuffer
6200 .push_excerpts(
6201 buffer.clone(),
6202 [
6203 ExcerptRange {
6204 context: Point::new(0, 0)..Point::new(1, 4),
6205 primary: None,
6206 },
6207 ExcerptRange {
6208 context: Point::new(1, 0)..Point::new(2, 4),
6209 primary: None,
6210 },
6211 ],
6212 cx,
6213 )
6214 .into_iter()
6215 .next();
6216 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6217 multibuffer
6218 });
6219
6220 let editor = cx.add_window(|cx| {
6221 let mut editor = build_editor(multibuffer.clone(), cx);
6222 let snapshot = editor.snapshot(cx);
6223 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6224 assert_eq!(
6225 editor.selections.ranges(cx),
6226 [Point::new(1, 3)..Point::new(1, 3)]
6227 );
6228 editor
6229 });
6230
6231 multibuffer.update(cx, |multibuffer, cx| {
6232 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6233 });
6234 editor.update(cx, |editor, cx| {
6235 assert_eq!(
6236 editor.selections.ranges(cx),
6237 [Point::new(0, 0)..Point::new(0, 0)]
6238 );
6239
6240 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6241 editor.change_selections(None, cx, |s| s.refresh());
6242 assert_eq!(
6243 editor.selections.ranges(cx),
6244 [Point::new(0, 3)..Point::new(0, 3)]
6245 );
6246 assert!(editor.selections.pending_anchor().is_some());
6247 });
6248}
6249
6250#[gpui::test]
6251async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6252 init_test(cx, |_| {});
6253
6254 let language = Arc::new(
6255 Language::new(
6256 LanguageConfig {
6257 brackets: BracketPairConfig {
6258 pairs: vec![
6259 BracketPair {
6260 start: "{".to_string(),
6261 end: "}".to_string(),
6262 close: true,
6263 newline: true,
6264 },
6265 BracketPair {
6266 start: "/* ".to_string(),
6267 end: " */".to_string(),
6268 close: true,
6269 newline: true,
6270 },
6271 ],
6272 ..Default::default()
6273 },
6274 ..Default::default()
6275 },
6276 Some(tree_sitter_rust::language()),
6277 )
6278 .with_indents_query("")
6279 .unwrap(),
6280 );
6281
6282 let text = concat!(
6283 "{ }\n", //
6284 " x\n", //
6285 " /* */\n", //
6286 "x\n", //
6287 "{{} }\n", //
6288 );
6289
6290 let buffer = cx.build_model(|cx| {
6291 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
6292 });
6293 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
6294 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6295 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6296 .await;
6297
6298 view.update(cx, |view, cx| {
6299 view.change_selections(None, cx, |s| {
6300 s.select_display_ranges([
6301 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6302 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6303 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6304 ])
6305 });
6306 view.newline(&Newline, cx);
6307
6308 assert_eq!(
6309 view.buffer().read(cx).read(cx).text(),
6310 concat!(
6311 "{ \n", // Suppress rustfmt
6312 "\n", //
6313 "}\n", //
6314 " x\n", //
6315 " /* \n", //
6316 " \n", //
6317 " */\n", //
6318 "x\n", //
6319 "{{} \n", //
6320 "}\n", //
6321 )
6322 );
6323 });
6324}
6325
6326// #[gpui::test]
6327// fn test_highlighted_ranges(cx: &mut TestAppContext) {
6328// init_test(cx, |_| {});
6329
6330// let editor = cx.add_window(|cx| {
6331// let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6332// build_editor(buffer.clone(), cx)
6333// });
6334
6335// editor.update(cx, |editor, cx| {
6336// struct Type1;
6337// struct Type2;
6338
6339// let buffer = editor.buffer.read(cx).snapshot(cx);
6340
6341// let anchor_range =
6342// |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6343
6344// editor.highlight_background::<Type1>(
6345// vec![
6346// anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6347// anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6348// anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6349// anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6350// ],
6351// |_| Hsla::red(),
6352// cx,
6353// );
6354// editor.highlight_background::<Type2>(
6355// vec![
6356// anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6357// anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6358// anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6359// anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6360// ],
6361// |_| Hsla::green(),
6362// cx,
6363// );
6364
6365// let snapshot = editor.snapshot(cx);
6366// let mut highlighted_ranges = editor.background_highlights_in_range(
6367// anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6368// &snapshot,
6369// cx.theme().colors(),
6370// );
6371// // Enforce a consistent ordering based on color without relying on the ordering of the
6372// // highlight's `TypeId` which is non-executor.
6373// highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6374// assert_eq!(
6375// highlighted_ranges,
6376// &[
6377// (
6378// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6379// Hsla::green(),
6380// ),
6381// (
6382// DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6383// Hsla::green(),
6384// ),
6385// (
6386// DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6387// Hsla::red(),
6388// ),
6389// (
6390// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6391// Hsla::red(),
6392// ),
6393// ]
6394// );
6395// assert_eq!(
6396// editor.background_highlights_in_range(
6397// anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6398// &snapshot,
6399// cx.theme().colors(),
6400// ),
6401// &[(
6402// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6403// Hsla::red(),
6404// )]
6405// );
6406// });
6407// }
6408
6409// todo!(following)
6410// #[gpui::test]
6411// async fn test_following(cx: &mut gpui::TestAppContext) {
6412// init_test(cx, |_| {});
6413
6414// let fs = FakeFs::new(cx.executor());
6415// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6416
6417// let buffer = project.update(cx, |project, cx| {
6418// let buffer = project
6419// .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6420// .unwrap();
6421// cx.build_model(|cx| MultiBuffer::singleton(buffer, cx))
6422// });
6423// let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6424// let follower = cx.update(|cx| {
6425// cx.open_window(
6426// WindowOptions {
6427// bounds: WindowBounds::Fixed(Bounds::from_corners(
6428// gpui::Point::new((0. as f64).into(), (0. as f64).into()),
6429// gpui::Point::new((10. as f64).into(), (80. as f64).into()),
6430// )),
6431// ..Default::default()
6432// },
6433// |cx| cx.build_view(|cx| build_editor(buffer.clone(), cx)),
6434// )
6435// });
6436
6437// let is_still_following = Rc::new(RefCell::new(true));
6438// let follower_edit_event_count = Rc::new(RefCell::new(0));
6439// let pending_update = Rc::new(RefCell::new(None));
6440// follower.update(cx, {
6441// let update = pending_update.clone();
6442// let is_still_following = is_still_following.clone();
6443// let follower_edit_event_count = follower_edit_event_count.clone();
6444// |_, cx| {
6445// cx.subscribe(
6446// &leader.root_view(cx).unwrap(),
6447// move |_, leader, event, cx| {
6448// leader
6449// .read(cx)
6450// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6451// },
6452// )
6453// .detach();
6454
6455// cx.subscribe(
6456// &follower.root_view(cx).unwrap(),
6457// move |_, _, event: &Event, cx| {
6458// if matches!(event.to_follow_event(), Some(FollowEvent::Unfollow)) {
6459// *is_still_following.borrow_mut() = false;
6460// }
6461
6462// if let Event::BufferEdited = event {
6463// *follower_edit_event_count.borrow_mut() += 1;
6464// }
6465// },
6466// )
6467// .detach();
6468// }
6469// });
6470
6471// // Update the selections only
6472// leader.update(cx, |leader, cx| {
6473// leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6474// });
6475// follower
6476// .update(cx, |follower, cx| {
6477// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6478// })
6479// .unwrap()
6480// .await
6481// .unwrap();
6482// follower.update(cx, |follower, cx| {
6483// assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6484// });
6485// assert_eq!(*is_still_following.borrow(), true);
6486// assert_eq!(*follower_edit_event_count.borrow(), 0);
6487
6488// // Update the scroll position only
6489// leader.update(cx, |leader, cx| {
6490// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6491// });
6492// follower
6493// .update(cx, |follower, cx| {
6494// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6495// })
6496// .unwrap()
6497// .await
6498// .unwrap();
6499// assert_eq!(
6500// follower
6501// .update(cx, |follower, cx| follower.scroll_position(cx))
6502// .unwrap(),
6503// gpui::Point::new(1.5, 3.5)
6504// );
6505// assert_eq!(*is_still_following.borrow(), true);
6506// assert_eq!(*follower_edit_event_count.borrow(), 0);
6507
6508// // Update the selections and scroll position. The follower's scroll position is updated
6509// // via autoscroll, not via the leader's exact scroll position.
6510// leader.update(cx, |leader, cx| {
6511// leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6512// leader.request_autoscroll(Autoscroll::newest(), cx);
6513// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6514// });
6515// follower
6516// .update(cx, |follower, cx| {
6517// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6518// })
6519// .unwrap()
6520// .await
6521// .unwrap();
6522// follower.update(cx, |follower, cx| {
6523// assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
6524// assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6525// });
6526// assert_eq!(*is_still_following.borrow(), true);
6527
6528// // Creating a pending selection that precedes another selection
6529// leader.update(cx, |leader, cx| {
6530// leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6531// leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6532// });
6533// follower
6534// .update(cx, |follower, cx| {
6535// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6536// })
6537// .unwrap()
6538// .await
6539// .unwrap();
6540// follower.update(cx, |follower, cx| {
6541// assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6542// });
6543// assert_eq!(*is_still_following.borrow(), true);
6544
6545// // Extend the pending selection so that it surrounds another selection
6546// leader.update(cx, |leader, cx| {
6547// leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6548// });
6549// follower
6550// .update(cx, |follower, cx| {
6551// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6552// })
6553// .unwrap()
6554// .await
6555// .unwrap();
6556// follower.update(cx, |follower, cx| {
6557// assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6558// });
6559
6560// // Scrolling locally breaks the follow
6561// follower.update(cx, |follower, cx| {
6562// let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6563// follower.set_scroll_anchor(
6564// ScrollAnchor {
6565// anchor: top_anchor,
6566// offset: gpui::Point::new(0.0, 0.5),
6567// },
6568// cx,
6569// );
6570// });
6571// assert_eq!(*is_still_following.borrow(), false);
6572// }
6573
6574// #[gpui::test]
6575// async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6576// init_test(cx, |_| {});
6577
6578// let fs = FakeFs::new(cx.executor());
6579// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6580// let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6581// let pane = workspace
6582// .update(cx, |workspace, _| workspace.active_pane().clone())
6583// .unwrap();
6584
6585// let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6586
6587// let leader = pane.update(cx, |_, cx| {
6588// let multibuffer = cx.build_model(|_| MultiBuffer::new(0));
6589// cx.build_view(|cx| build_editor(multibuffer.clone(), cx))
6590// });
6591
6592// // Start following the editor when it has no excerpts.
6593// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6594// let follower_1 = cx
6595// .update(|cx| {
6596// Editor::from_state_proto(
6597// pane.clone(),
6598// workspace.root_view(cx).unwrap(),
6599// ViewId {
6600// creator: Default::default(),
6601// id: 0,
6602// },
6603// &mut state_message,
6604// cx,
6605// )
6606// })
6607// .unwrap()
6608// .await
6609// .unwrap();
6610
6611// let update_message = Rc::new(RefCell::new(None));
6612// follower_1.update(cx, {
6613// let update = update_message.clone();
6614// |_, cx| {
6615// cx.subscribe(&leader, move |_, leader, event, cx| {
6616// leader
6617// .read(cx)
6618// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6619// })
6620// .detach();
6621// }
6622// });
6623
6624// let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6625// (
6626// project
6627// .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6628// .unwrap(),
6629// project
6630// .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6631// .unwrap(),
6632// )
6633// });
6634
6635// // Insert some excerpts.
6636// leader.update(cx, |leader, cx| {
6637// leader.buffer.update(cx, |multibuffer, cx| {
6638// let excerpt_ids = multibuffer.push_excerpts(
6639// buffer_1.clone(),
6640// [
6641// ExcerptRange {
6642// context: 1..6,
6643// primary: None,
6644// },
6645// ExcerptRange {
6646// context: 12..15,
6647// primary: None,
6648// },
6649// ExcerptRange {
6650// context: 0..3,
6651// primary: None,
6652// },
6653// ],
6654// cx,
6655// );
6656// multibuffer.insert_excerpts_after(
6657// excerpt_ids[0],
6658// buffer_2.clone(),
6659// [
6660// ExcerptRange {
6661// context: 8..12,
6662// primary: None,
6663// },
6664// ExcerptRange {
6665// context: 0..6,
6666// primary: None,
6667// },
6668// ],
6669// cx,
6670// );
6671// });
6672// });
6673
6674// // Apply the update of adding the excerpts.
6675// follower_1
6676// .update(cx, |follower, cx| {
6677// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6678// })
6679// .await
6680// .unwrap();
6681// assert_eq!(
6682// follower_1.update(cx, |editor, cx| editor.text(cx)),
6683// leader.update(cx, |editor, cx| editor.text(cx))
6684// );
6685// update_message.borrow_mut().take();
6686
6687// // Start following separately after it already has excerpts.
6688// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6689// let follower_2 = cx
6690// .update(|cx| {
6691// Editor::from_state_proto(
6692// pane.clone(),
6693// workspace.clone(),
6694// ViewId {
6695// creator: Default::default(),
6696// id: 0,
6697// },
6698// &mut state_message,
6699// cx,
6700// )
6701// })
6702// .unwrap()
6703// .await
6704// .unwrap();
6705// assert_eq!(
6706// follower_2.update(cx, |editor, cx| editor.text(cx)),
6707// leader.update(cx, |editor, cx| editor.text(cx))
6708// );
6709
6710// // Remove some excerpts.
6711// leader.update(cx, |leader, cx| {
6712// leader.buffer.update(cx, |multibuffer, cx| {
6713// let excerpt_ids = multibuffer.excerpt_ids();
6714// multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6715// multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6716// });
6717// });
6718
6719// // Apply the update of removing the excerpts.
6720// follower_1
6721// .update(cx, |follower, cx| {
6722// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6723// })
6724// .await
6725// .unwrap();
6726// follower_2
6727// .update(cx, |follower, cx| {
6728// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6729// })
6730// .await
6731// .unwrap();
6732// update_message.borrow_mut().take();
6733// assert_eq!(
6734// follower_1.update(cx, |editor, cx| editor.text(cx)),
6735// leader.update(cx, |editor, cx| editor.text(cx))
6736// );
6737// }
6738
6739#[gpui::test]
6740async fn go_to_prev_overlapping_diagnostic(
6741 executor: BackgroundExecutor,
6742 cx: &mut gpui::TestAppContext,
6743) {
6744 init_test(cx, |_| {});
6745
6746 let mut cx = EditorTestContext::new(cx).await;
6747 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
6748
6749 cx.set_state(indoc! {"
6750 ˇfn func(abc def: i32) -> u32 {
6751 }
6752 "});
6753
6754 cx.update(|cx| {
6755 project.update(cx, |project, cx| {
6756 project
6757 .update_diagnostics(
6758 LanguageServerId(0),
6759 lsp::PublishDiagnosticsParams {
6760 uri: lsp::Url::from_file_path("/root/file").unwrap(),
6761 version: None,
6762 diagnostics: vec![
6763 lsp::Diagnostic {
6764 range: lsp::Range::new(
6765 lsp::Position::new(0, 11),
6766 lsp::Position::new(0, 12),
6767 ),
6768 severity: Some(lsp::DiagnosticSeverity::ERROR),
6769 ..Default::default()
6770 },
6771 lsp::Diagnostic {
6772 range: lsp::Range::new(
6773 lsp::Position::new(0, 12),
6774 lsp::Position::new(0, 15),
6775 ),
6776 severity: Some(lsp::DiagnosticSeverity::ERROR),
6777 ..Default::default()
6778 },
6779 lsp::Diagnostic {
6780 range: lsp::Range::new(
6781 lsp::Position::new(0, 25),
6782 lsp::Position::new(0, 28),
6783 ),
6784 severity: Some(lsp::DiagnosticSeverity::ERROR),
6785 ..Default::default()
6786 },
6787 ],
6788 },
6789 &[],
6790 cx,
6791 )
6792 .unwrap()
6793 });
6794 });
6795
6796 executor.run_until_parked();
6797
6798 cx.update_editor(|editor, cx| {
6799 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6800 });
6801
6802 cx.assert_editor_state(indoc! {"
6803 fn func(abc def: i32) -> ˇu32 {
6804 }
6805 "});
6806
6807 cx.update_editor(|editor, cx| {
6808 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6809 });
6810
6811 cx.assert_editor_state(indoc! {"
6812 fn func(abc ˇdef: i32) -> u32 {
6813 }
6814 "});
6815
6816 cx.update_editor(|editor, cx| {
6817 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6818 });
6819
6820 cx.assert_editor_state(indoc! {"
6821 fn func(abcˇ def: i32) -> u32 {
6822 }
6823 "});
6824
6825 cx.update_editor(|editor, cx| {
6826 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6827 });
6828
6829 cx.assert_editor_state(indoc! {"
6830 fn func(abc def: i32) -> ˇu32 {
6831 }
6832 "});
6833}
6834
6835#[gpui::test]
6836async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
6837 init_test(cx, |_| {});
6838
6839 let mut cx = EditorTestContext::new(cx).await;
6840
6841 let diff_base = r#"
6842 use some::mod;
6843
6844 const A: u32 = 42;
6845
6846 fn main() {
6847 println!("hello");
6848
6849 println!("world");
6850 }
6851 "#
6852 .unindent();
6853
6854 // Edits are modified, removed, modified, added
6855 cx.set_state(
6856 &r#"
6857 use some::modified;
6858
6859 ˇ
6860 fn main() {
6861 println!("hello there");
6862
6863 println!("around the");
6864 println!("world");
6865 }
6866 "#
6867 .unindent(),
6868 );
6869
6870 cx.set_diff_base(Some(&diff_base));
6871 executor.run_until_parked();
6872
6873 cx.update_editor(|editor, cx| {
6874 //Wrap around the bottom of the buffer
6875 for _ in 0..3 {
6876 editor.go_to_hunk(&GoToHunk, cx);
6877 }
6878 });
6879
6880 cx.assert_editor_state(
6881 &r#"
6882 ˇuse some::modified;
6883
6884
6885 fn main() {
6886 println!("hello there");
6887
6888 println!("around the");
6889 println!("world");
6890 }
6891 "#
6892 .unindent(),
6893 );
6894
6895 cx.update_editor(|editor, cx| {
6896 //Wrap around the top of the buffer
6897 for _ in 0..2 {
6898 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6899 }
6900 });
6901
6902 cx.assert_editor_state(
6903 &r#"
6904 use some::modified;
6905
6906
6907 fn main() {
6908 ˇ println!("hello there");
6909
6910 println!("around the");
6911 println!("world");
6912 }
6913 "#
6914 .unindent(),
6915 );
6916
6917 cx.update_editor(|editor, cx| {
6918 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6919 });
6920
6921 cx.assert_editor_state(
6922 &r#"
6923 use some::modified;
6924
6925 ˇ
6926 fn main() {
6927 println!("hello there");
6928
6929 println!("around the");
6930 println!("world");
6931 }
6932 "#
6933 .unindent(),
6934 );
6935
6936 cx.update_editor(|editor, cx| {
6937 for _ in 0..3 {
6938 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6939 }
6940 });
6941
6942 cx.assert_editor_state(
6943 &r#"
6944 use some::modified;
6945
6946
6947 fn main() {
6948 ˇ println!("hello there");
6949
6950 println!("around the");
6951 println!("world");
6952 }
6953 "#
6954 .unindent(),
6955 );
6956
6957 cx.update_editor(|editor, cx| {
6958 editor.fold(&Fold, cx);
6959
6960 //Make sure that the fold only gets one hunk
6961 for _ in 0..4 {
6962 editor.go_to_hunk(&GoToHunk, cx);
6963 }
6964 });
6965
6966 cx.assert_editor_state(
6967 &r#"
6968 ˇuse some::modified;
6969
6970
6971 fn main() {
6972 println!("hello there");
6973
6974 println!("around the");
6975 println!("world");
6976 }
6977 "#
6978 .unindent(),
6979 );
6980}
6981
6982#[test]
6983fn test_split_words() {
6984 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6985 split_words(text).collect()
6986 }
6987
6988 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6989 assert_eq!(split("hello_world"), &["hello_", "world"]);
6990 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6991 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6992 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6993 assert_eq!(split("helloworld"), &["helloworld"]);
6994}
6995
6996#[gpui::test]
6997async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6998 init_test(cx, |_| {});
6999
7000 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7001 let mut assert = |before, after| {
7002 let _state_context = cx.set_state(before);
7003 cx.update_editor(|editor, cx| {
7004 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7005 });
7006 cx.assert_editor_state(after);
7007 };
7008
7009 // Outside bracket jumps to outside of matching bracket
7010 assert("console.logˇ(var);", "console.log(var)ˇ;");
7011 assert("console.log(var)ˇ;", "console.logˇ(var);");
7012
7013 // Inside bracket jumps to inside of matching bracket
7014 assert("console.log(ˇvar);", "console.log(varˇ);");
7015 assert("console.log(varˇ);", "console.log(ˇvar);");
7016
7017 // When outside a bracket and inside, favor jumping to the inside bracket
7018 assert(
7019 "console.log('foo', [1, 2, 3]ˇ);",
7020 "console.log(ˇ'foo', [1, 2, 3]);",
7021 );
7022 assert(
7023 "console.log(ˇ'foo', [1, 2, 3]);",
7024 "console.log('foo', [1, 2, 3]ˇ);",
7025 );
7026
7027 // Bias forward if two options are equally likely
7028 assert(
7029 "let result = curried_fun()ˇ();",
7030 "let result = curried_fun()()ˇ;",
7031 );
7032
7033 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7034 assert(
7035 indoc! {"
7036 function test() {
7037 console.log('test')ˇ
7038 }"},
7039 indoc! {"
7040 function test() {
7041 console.logˇ('test')
7042 }"},
7043 );
7044}
7045
7046// todo!(completions)
7047#[gpui::test(iterations = 10)]
7048async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7049 // flaky
7050 init_test(cx, |_| {});
7051
7052 let (copilot, copilot_lsp) = Copilot::fake(cx);
7053 cx.update(|cx| cx.set_global(copilot));
7054 let mut cx = EditorLspTestContext::new_rust(
7055 lsp::ServerCapabilities {
7056 completion_provider: Some(lsp::CompletionOptions {
7057 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7058 ..Default::default()
7059 }),
7060 ..Default::default()
7061 },
7062 cx,
7063 )
7064 .await;
7065
7066 // When inserting, ensure autocompletion is favored over Copilot suggestions.
7067 cx.set_state(indoc! {"
7068 oneˇ
7069 two
7070 three
7071 "});
7072 cx.simulate_keystroke(".");
7073 let _ = handle_completion_request(
7074 &mut cx,
7075 indoc! {"
7076 one.|<>
7077 two
7078 three
7079 "},
7080 vec!["completion_a", "completion_b"],
7081 );
7082 handle_copilot_completion_request(
7083 &copilot_lsp,
7084 vec![copilot::request::Completion {
7085 text: "one.copilot1".into(),
7086 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7087 ..Default::default()
7088 }],
7089 vec![],
7090 );
7091 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7092 cx.update_editor(|editor, cx| {
7093 assert!(editor.context_menu_visible());
7094 assert!(!editor.has_active_copilot_suggestion(cx));
7095
7096 // Confirming a completion inserts it and hides the context menu, without showing
7097 // the copilot suggestion afterwards.
7098 editor
7099 .confirm_completion(&Default::default(), cx)
7100 .unwrap()
7101 .detach();
7102 assert!(!editor.context_menu_visible());
7103 assert!(!editor.has_active_copilot_suggestion(cx));
7104 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
7105 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
7106 });
7107
7108 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
7109 cx.set_state(indoc! {"
7110 oneˇ
7111 two
7112 three
7113 "});
7114 cx.simulate_keystroke(".");
7115 let _ = handle_completion_request(
7116 &mut cx,
7117 indoc! {"
7118 one.|<>
7119 two
7120 three
7121 "},
7122 vec![],
7123 );
7124 handle_copilot_completion_request(
7125 &copilot_lsp,
7126 vec![copilot::request::Completion {
7127 text: "one.copilot1".into(),
7128 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7129 ..Default::default()
7130 }],
7131 vec![],
7132 );
7133 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7134 cx.update_editor(|editor, cx| {
7135 assert!(!editor.context_menu_visible());
7136 assert!(editor.has_active_copilot_suggestion(cx));
7137 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7138 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7139 });
7140
7141 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
7142 cx.set_state(indoc! {"
7143 oneˇ
7144 two
7145 three
7146 "});
7147 cx.simulate_keystroke(".");
7148 let _ = handle_completion_request(
7149 &mut cx,
7150 indoc! {"
7151 one.|<>
7152 two
7153 three
7154 "},
7155 vec!["completion_a", "completion_b"],
7156 );
7157 handle_copilot_completion_request(
7158 &copilot_lsp,
7159 vec![copilot::request::Completion {
7160 text: "one.copilot1".into(),
7161 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7162 ..Default::default()
7163 }],
7164 vec![],
7165 );
7166 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7167 cx.update_editor(|editor, cx| {
7168 assert!(editor.context_menu_visible());
7169 assert!(!editor.has_active_copilot_suggestion(cx));
7170
7171 // When hiding the context menu, the Copilot suggestion becomes visible.
7172 editor.hide_context_menu(cx);
7173 assert!(!editor.context_menu_visible());
7174 assert!(editor.has_active_copilot_suggestion(cx));
7175 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7176 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7177 });
7178
7179 // Ensure existing completion is interpolated when inserting again.
7180 cx.simulate_keystroke("c");
7181 executor.run_until_parked();
7182 cx.update_editor(|editor, cx| {
7183 assert!(!editor.context_menu_visible());
7184 assert!(editor.has_active_copilot_suggestion(cx));
7185 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7186 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7187 });
7188
7189 // After debouncing, new Copilot completions should be requested.
7190 handle_copilot_completion_request(
7191 &copilot_lsp,
7192 vec![copilot::request::Completion {
7193 text: "one.copilot2".into(),
7194 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7195 ..Default::default()
7196 }],
7197 vec![],
7198 );
7199 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7200 cx.update_editor(|editor, cx| {
7201 assert!(!editor.context_menu_visible());
7202 assert!(editor.has_active_copilot_suggestion(cx));
7203 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7204 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7205
7206 // Canceling should remove the active Copilot suggestion.
7207 editor.cancel(&Default::default(), cx);
7208 assert!(!editor.has_active_copilot_suggestion(cx));
7209 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7210 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7211
7212 // After canceling, tabbing shouldn't insert the previously shown suggestion.
7213 editor.tab(&Default::default(), cx);
7214 assert!(!editor.has_active_copilot_suggestion(cx));
7215 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
7216 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
7217
7218 // When undoing the previously active suggestion is shown again.
7219 editor.undo(&Default::default(), cx);
7220 assert!(editor.has_active_copilot_suggestion(cx));
7221 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7222 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7223 });
7224
7225 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7226 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7227 cx.update_editor(|editor, cx| {
7228 assert!(editor.has_active_copilot_suggestion(cx));
7229 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7230 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7231
7232 // Tabbing when there is an active suggestion inserts it.
7233 editor.tab(&Default::default(), cx);
7234 assert!(!editor.has_active_copilot_suggestion(cx));
7235 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7236 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7237
7238 // When undoing the previously active suggestion is shown again.
7239 editor.undo(&Default::default(), cx);
7240 assert!(editor.has_active_copilot_suggestion(cx));
7241 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7242 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7243
7244 // Hide suggestion.
7245 editor.cancel(&Default::default(), cx);
7246 assert!(!editor.has_active_copilot_suggestion(cx));
7247 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7248 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7249 });
7250
7251 // If an edit occurs outside of this editor but no suggestion is being shown,
7252 // we won't make it visible.
7253 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7254 cx.update_editor(|editor, cx| {
7255 assert!(!editor.has_active_copilot_suggestion(cx));
7256 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7257 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7258 });
7259
7260 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7261 cx.update_editor(|editor, cx| {
7262 editor.set_text("fn foo() {\n \n}", cx);
7263 editor.change_selections(None, cx, |s| {
7264 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7265 });
7266 });
7267 handle_copilot_completion_request(
7268 &copilot_lsp,
7269 vec![copilot::request::Completion {
7270 text: " let x = 4;".into(),
7271 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7272 ..Default::default()
7273 }],
7274 vec![],
7275 );
7276
7277 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7278 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7279 cx.update_editor(|editor, cx| {
7280 assert!(editor.has_active_copilot_suggestion(cx));
7281 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7282 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7283
7284 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7285 editor.tab(&Default::default(), cx);
7286 assert!(editor.has_active_copilot_suggestion(cx));
7287 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7288 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7289
7290 // Tabbing again accepts the suggestion.
7291 editor.tab(&Default::default(), cx);
7292 assert!(!editor.has_active_copilot_suggestion(cx));
7293 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7294 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7295 });
7296}
7297
7298#[gpui::test]
7299async fn test_copilot_completion_invalidation(
7300 executor: BackgroundExecutor,
7301 cx: &mut gpui::TestAppContext,
7302) {
7303 init_test(cx, |_| {});
7304
7305 let (copilot, copilot_lsp) = Copilot::fake(cx);
7306 cx.update(|cx| cx.set_global(copilot));
7307 let mut cx = EditorLspTestContext::new_rust(
7308 lsp::ServerCapabilities {
7309 completion_provider: Some(lsp::CompletionOptions {
7310 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7311 ..Default::default()
7312 }),
7313 ..Default::default()
7314 },
7315 cx,
7316 )
7317 .await;
7318
7319 cx.set_state(indoc! {"
7320 one
7321 twˇ
7322 three
7323 "});
7324
7325 handle_copilot_completion_request(
7326 &copilot_lsp,
7327 vec![copilot::request::Completion {
7328 text: "two.foo()".into(),
7329 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7330 ..Default::default()
7331 }],
7332 vec![],
7333 );
7334 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7335 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7336 cx.update_editor(|editor, cx| {
7337 assert!(editor.has_active_copilot_suggestion(cx));
7338 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7339 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7340
7341 editor.backspace(&Default::default(), cx);
7342 assert!(editor.has_active_copilot_suggestion(cx));
7343 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7344 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7345
7346 editor.backspace(&Default::default(), cx);
7347 assert!(editor.has_active_copilot_suggestion(cx));
7348 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7349 assert_eq!(editor.text(cx), "one\n\nthree\n");
7350
7351 // Deleting across the original suggestion range invalidates it.
7352 editor.backspace(&Default::default(), cx);
7353 assert!(!editor.has_active_copilot_suggestion(cx));
7354 assert_eq!(editor.display_text(cx), "one\nthree\n");
7355 assert_eq!(editor.text(cx), "one\nthree\n");
7356
7357 // Undoing the deletion restores the suggestion.
7358 editor.undo(&Default::default(), cx);
7359 assert!(editor.has_active_copilot_suggestion(cx));
7360 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7361 assert_eq!(editor.text(cx), "one\n\nthree\n");
7362 });
7363}
7364
7365//todo!()
7366// #[gpui::test]
7367// async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7368// init_test(cx, |_| {});
7369
7370// let (copilot, copilot_lsp) = Copilot::fake(cx);
7371// cx.update(|cx| cx.set_global(copilot));
7372
7373// let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
7374// let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
7375// let multibuffer = cx.build_model(|cx| {
7376// let mut multibuffer = MultiBuffer::new(0);
7377// multibuffer.push_excerpts(
7378// buffer_1.clone(),
7379// [ExcerptRange {
7380// context: Point::new(0, 0)..Point::new(2, 0),
7381// primary: None,
7382// }],
7383// cx,
7384// );
7385// multibuffer.push_excerpts(
7386// buffer_2.clone(),
7387// [ExcerptRange {
7388// context: Point::new(0, 0)..Point::new(2, 0),
7389// primary: None,
7390// }],
7391// cx,
7392// );
7393// multibuffer
7394// });
7395// let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7396
7397// handle_copilot_completion_request(
7398// &copilot_lsp,
7399// vec![copilot::request::Completion {
7400// text: "b = 2 + a".into(),
7401// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7402// ..Default::default()
7403// }],
7404// vec![],
7405// );
7406// editor.update(cx, |editor, cx| {
7407// // Ensure copilot suggestions are shown for the first excerpt.
7408// editor.change_selections(None, cx, |s| {
7409// s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7410// });
7411// editor.next_copilot_suggestion(&Default::default(), cx);
7412// });
7413// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7414// editor.update(cx, |editor, cx| {
7415// assert!(editor.has_active_copilot_suggestion(cx));
7416// assert_eq!(
7417// editor.display_text(cx),
7418// "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7419// );
7420// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7421// });
7422
7423// handle_copilot_completion_request(
7424// &copilot_lsp,
7425// vec![copilot::request::Completion {
7426// text: "d = 4 + c".into(),
7427// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7428// ..Default::default()
7429// }],
7430// vec![],
7431// );
7432// editor.update(cx, |editor, cx| {
7433// // Move to another excerpt, ensuring the suggestion gets cleared.
7434// editor.change_selections(None, cx, |s| {
7435// s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7436// });
7437// assert!(!editor.has_active_copilot_suggestion(cx));
7438// assert_eq!(
7439// editor.display_text(cx),
7440// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7441// );
7442// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7443
7444// // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7445// editor.handle_input(" ", cx);
7446// assert!(!editor.has_active_copilot_suggestion(cx));
7447// assert_eq!(
7448// editor.display_text(cx),
7449// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7450// );
7451// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7452// });
7453
7454// // Ensure the new suggestion is displayed when the debounce timeout expires.
7455// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7456// editor.update(cx, |editor, cx| {
7457// assert!(editor.has_active_copilot_suggestion(cx));
7458// assert_eq!(
7459// editor.display_text(cx),
7460// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7461// );
7462// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7463// });
7464// }
7465
7466#[gpui::test]
7467async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7468 init_test(cx, |settings| {
7469 settings
7470 .copilot
7471 .get_or_insert(Default::default())
7472 .disabled_globs = Some(vec![".env*".to_string()]);
7473 });
7474
7475 let (copilot, copilot_lsp) = Copilot::fake(cx);
7476 cx.update(|cx| cx.set_global(copilot));
7477
7478 let fs = FakeFs::new(cx.executor());
7479 fs.insert_tree(
7480 "/test",
7481 json!({
7482 ".env": "SECRET=something\n",
7483 "README.md": "hello\n"
7484 }),
7485 )
7486 .await;
7487 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7488
7489 let private_buffer = project
7490 .update(cx, |project, cx| {
7491 project.open_local_buffer("/test/.env", cx)
7492 })
7493 .await
7494 .unwrap();
7495 let public_buffer = project
7496 .update(cx, |project, cx| {
7497 project.open_local_buffer("/test/README.md", cx)
7498 })
7499 .await
7500 .unwrap();
7501
7502 let multibuffer = cx.build_model(|cx| {
7503 let mut multibuffer = MultiBuffer::new(0);
7504 multibuffer.push_excerpts(
7505 private_buffer.clone(),
7506 [ExcerptRange {
7507 context: Point::new(0, 0)..Point::new(1, 0),
7508 primary: None,
7509 }],
7510 cx,
7511 );
7512 multibuffer.push_excerpts(
7513 public_buffer.clone(),
7514 [ExcerptRange {
7515 context: Point::new(0, 0)..Point::new(1, 0),
7516 primary: None,
7517 }],
7518 cx,
7519 );
7520 multibuffer
7521 });
7522 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7523
7524 let mut copilot_requests = copilot_lsp
7525 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7526 Ok(copilot::request::GetCompletionsResult {
7527 completions: vec![copilot::request::Completion {
7528 text: "next line".into(),
7529 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7530 ..Default::default()
7531 }],
7532 })
7533 });
7534
7535 editor.update(cx, |editor, cx| {
7536 editor.change_selections(None, cx, |selections| {
7537 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7538 });
7539 editor.next_copilot_suggestion(&Default::default(), cx);
7540 });
7541
7542 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7543 assert!(copilot_requests.try_next().is_err());
7544
7545 editor.update(cx, |editor, cx| {
7546 editor.change_selections(None, cx, |s| {
7547 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7548 });
7549 editor.next_copilot_suggestion(&Default::default(), cx);
7550 });
7551
7552 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7553 assert!(copilot_requests.try_next().is_ok());
7554}
7555
7556#[gpui::test]
7557async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7558 init_test(cx, |_| {});
7559
7560 let mut language = Language::new(
7561 LanguageConfig {
7562 name: "Rust".into(),
7563 path_suffixes: vec!["rs".to_string()],
7564 brackets: BracketPairConfig {
7565 pairs: vec![BracketPair {
7566 start: "{".to_string(),
7567 end: "}".to_string(),
7568 close: true,
7569 newline: true,
7570 }],
7571 disabled_scopes_by_bracket_ix: Vec::new(),
7572 },
7573 ..Default::default()
7574 },
7575 Some(tree_sitter_rust::language()),
7576 );
7577 let mut fake_servers = language
7578 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7579 capabilities: lsp::ServerCapabilities {
7580 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7581 first_trigger_character: "{".to_string(),
7582 more_trigger_character: None,
7583 }),
7584 ..Default::default()
7585 },
7586 ..Default::default()
7587 }))
7588 .await;
7589
7590 let fs = FakeFs::new(cx.executor());
7591 fs.insert_tree(
7592 "/a",
7593 json!({
7594 "main.rs": "fn main() { let a = 5; }",
7595 "other.rs": "// Test file",
7596 }),
7597 )
7598 .await;
7599 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7600 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7601 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7602
7603 let cx = &mut VisualTestContext::from_window(*workspace, cx);
7604
7605 let worktree_id = workspace
7606 .update(cx, |workspace, cx| {
7607 workspace.project().update(cx, |project, cx| {
7608 project.worktrees().next().unwrap().read(cx).id()
7609 })
7610 })
7611 .unwrap();
7612
7613 let buffer = project
7614 .update(cx, |project, cx| {
7615 project.open_local_buffer("/a/main.rs", cx)
7616 })
7617 .await
7618 .unwrap();
7619 cx.executor().run_until_parked();
7620 cx.executor().start_waiting();
7621 let fake_server = fake_servers.next().await.unwrap();
7622 let editor_handle = workspace
7623 .update(cx, |workspace, cx| {
7624 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7625 })
7626 .unwrap()
7627 .await
7628 .unwrap()
7629 .downcast::<Editor>()
7630 .unwrap();
7631
7632 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7633 assert_eq!(
7634 params.text_document_position.text_document.uri,
7635 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7636 );
7637 assert_eq!(
7638 params.text_document_position.position,
7639 lsp::Position::new(0, 21),
7640 );
7641
7642 Ok(Some(vec![lsp::TextEdit {
7643 new_text: "]".to_string(),
7644 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7645 }]))
7646 });
7647
7648 editor_handle.update(cx, |editor, cx| {
7649 editor.focus(cx);
7650 editor.change_selections(None, cx, |s| {
7651 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7652 });
7653 editor.handle_input("{", cx);
7654 });
7655
7656 cx.executor().run_until_parked();
7657
7658 buffer.update(cx, |buffer, _| {
7659 assert_eq!(
7660 buffer.text(),
7661 "fn main() { let a = {5}; }",
7662 "No extra braces from on type formatting should appear in the buffer"
7663 )
7664 });
7665}
7666
7667#[gpui::test]
7668async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7669 init_test(cx, |_| {});
7670
7671 let language_name: Arc<str> = "Rust".into();
7672 let mut language = Language::new(
7673 LanguageConfig {
7674 name: Arc::clone(&language_name),
7675 path_suffixes: vec!["rs".to_string()],
7676 ..Default::default()
7677 },
7678 Some(tree_sitter_rust::language()),
7679 );
7680
7681 let server_restarts = Arc::new(AtomicUsize::new(0));
7682 let closure_restarts = Arc::clone(&server_restarts);
7683 let language_server_name = "test language server";
7684 let mut fake_servers = language
7685 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7686 name: language_server_name,
7687 initialization_options: Some(json!({
7688 "testOptionValue": true
7689 })),
7690 initializer: Some(Box::new(move |fake_server| {
7691 let task_restarts = Arc::clone(&closure_restarts);
7692 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7693 task_restarts.fetch_add(1, atomic::Ordering::Release);
7694 futures::future::ready(Ok(()))
7695 });
7696 })),
7697 ..Default::default()
7698 }))
7699 .await;
7700
7701 let fs = FakeFs::new(cx.executor());
7702 fs.insert_tree(
7703 "/a",
7704 json!({
7705 "main.rs": "fn main() { let a = 5; }",
7706 "other.rs": "// Test file",
7707 }),
7708 )
7709 .await;
7710 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7711 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7712 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7713 let _buffer = project
7714 .update(cx, |project, cx| {
7715 project.open_local_buffer("/a/main.rs", cx)
7716 })
7717 .await
7718 .unwrap();
7719 let _fake_server = fake_servers.next().await.unwrap();
7720 update_test_language_settings(cx, |language_settings| {
7721 language_settings.languages.insert(
7722 Arc::clone(&language_name),
7723 LanguageSettingsContent {
7724 tab_size: NonZeroU32::new(8),
7725 ..Default::default()
7726 },
7727 );
7728 });
7729 cx.executor().run_until_parked();
7730 assert_eq!(
7731 server_restarts.load(atomic::Ordering::Acquire),
7732 0,
7733 "Should not restart LSP server on an unrelated change"
7734 );
7735
7736 update_test_project_settings(cx, |project_settings| {
7737 project_settings.lsp.insert(
7738 "Some other server name".into(),
7739 LspSettings {
7740 initialization_options: Some(json!({
7741 "some other init value": false
7742 })),
7743 },
7744 );
7745 });
7746 cx.executor().run_until_parked();
7747 assert_eq!(
7748 server_restarts.load(atomic::Ordering::Acquire),
7749 0,
7750 "Should not restart LSP server on an unrelated LSP settings change"
7751 );
7752
7753 update_test_project_settings(cx, |project_settings| {
7754 project_settings.lsp.insert(
7755 language_server_name.into(),
7756 LspSettings {
7757 initialization_options: Some(json!({
7758 "anotherInitValue": false
7759 })),
7760 },
7761 );
7762 });
7763 cx.executor().run_until_parked();
7764 assert_eq!(
7765 server_restarts.load(atomic::Ordering::Acquire),
7766 1,
7767 "Should restart LSP server on a related LSP settings change"
7768 );
7769
7770 update_test_project_settings(cx, |project_settings| {
7771 project_settings.lsp.insert(
7772 language_server_name.into(),
7773 LspSettings {
7774 initialization_options: Some(json!({
7775 "anotherInitValue": false
7776 })),
7777 },
7778 );
7779 });
7780 cx.executor().run_until_parked();
7781 assert_eq!(
7782 server_restarts.load(atomic::Ordering::Acquire),
7783 1,
7784 "Should not restart LSP server on a related LSP settings change that is the same"
7785 );
7786
7787 update_test_project_settings(cx, |project_settings| {
7788 project_settings.lsp.insert(
7789 language_server_name.into(),
7790 LspSettings {
7791 initialization_options: None,
7792 },
7793 );
7794 });
7795 cx.executor().run_until_parked();
7796 assert_eq!(
7797 server_restarts.load(atomic::Ordering::Acquire),
7798 2,
7799 "Should restart LSP server on another related LSP settings change"
7800 );
7801}
7802
7803#[gpui::test]
7804async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7805 init_test(cx, |_| {});
7806
7807 let mut cx = EditorLspTestContext::new_rust(
7808 lsp::ServerCapabilities {
7809 completion_provider: Some(lsp::CompletionOptions {
7810 trigger_characters: Some(vec![".".to_string()]),
7811 resolve_provider: Some(true),
7812 ..Default::default()
7813 }),
7814 ..Default::default()
7815 },
7816 cx,
7817 )
7818 .await;
7819
7820 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7821 cx.simulate_keystroke(".");
7822 let completion_item = lsp::CompletionItem {
7823 label: "some".into(),
7824 kind: Some(lsp::CompletionItemKind::SNIPPET),
7825 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7826 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7827 kind: lsp::MarkupKind::Markdown,
7828 value: "```rust\nSome(2)\n```".to_string(),
7829 })),
7830 deprecated: Some(false),
7831 sort_text: Some("fffffff2".to_string()),
7832 filter_text: Some("some".to_string()),
7833 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7834 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7835 range: lsp::Range {
7836 start: lsp::Position {
7837 line: 0,
7838 character: 22,
7839 },
7840 end: lsp::Position {
7841 line: 0,
7842 character: 22,
7843 },
7844 },
7845 new_text: "Some(2)".to_string(),
7846 })),
7847 additional_text_edits: Some(vec![lsp::TextEdit {
7848 range: lsp::Range {
7849 start: lsp::Position {
7850 line: 0,
7851 character: 20,
7852 },
7853 end: lsp::Position {
7854 line: 0,
7855 character: 22,
7856 },
7857 },
7858 new_text: "".to_string(),
7859 }]),
7860 ..Default::default()
7861 };
7862
7863 let closure_completion_item = completion_item.clone();
7864 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7865 let task_completion_item = closure_completion_item.clone();
7866 async move {
7867 Ok(Some(lsp::CompletionResponse::Array(vec![
7868 task_completion_item,
7869 ])))
7870 }
7871 });
7872
7873 request.next().await;
7874
7875 cx.condition(|editor, _| editor.context_menu_visible())
7876 .await;
7877 let apply_additional_edits = cx.update_editor(|editor, cx| {
7878 editor
7879 .confirm_completion(&ConfirmCompletion::default(), cx)
7880 .unwrap()
7881 });
7882 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7883
7884 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7885 let task_completion_item = completion_item.clone();
7886 async move { Ok(task_completion_item) }
7887 })
7888 .next()
7889 .await
7890 .unwrap();
7891 apply_additional_edits.await.unwrap();
7892 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7893}
7894
7895#[gpui::test]
7896async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
7897 init_test(cx, |_| {});
7898
7899 let mut cx = EditorLspTestContext::new(
7900 Language::new(
7901 LanguageConfig {
7902 path_suffixes: vec!["jsx".into()],
7903 overrides: [(
7904 "element".into(),
7905 LanguageConfigOverride {
7906 word_characters: Override::Set(['-'].into_iter().collect()),
7907 ..Default::default()
7908 },
7909 )]
7910 .into_iter()
7911 .collect(),
7912 ..Default::default()
7913 },
7914 Some(tree_sitter_typescript::language_tsx()),
7915 )
7916 .with_override_query("(jsx_self_closing_element) @element")
7917 .unwrap(),
7918 lsp::ServerCapabilities {
7919 completion_provider: Some(lsp::CompletionOptions {
7920 trigger_characters: Some(vec![":".to_string()]),
7921 ..Default::default()
7922 }),
7923 ..Default::default()
7924 },
7925 cx,
7926 )
7927 .await;
7928
7929 cx.lsp
7930 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7931 Ok(Some(lsp::CompletionResponse::Array(vec![
7932 lsp::CompletionItem {
7933 label: "bg-blue".into(),
7934 ..Default::default()
7935 },
7936 lsp::CompletionItem {
7937 label: "bg-red".into(),
7938 ..Default::default()
7939 },
7940 lsp::CompletionItem {
7941 label: "bg-yellow".into(),
7942 ..Default::default()
7943 },
7944 ])))
7945 });
7946
7947 cx.set_state(r#"<p class="bgˇ" />"#);
7948
7949 // Trigger completion when typing a dash, because the dash is an extra
7950 // word character in the 'element' scope, which contains the cursor.
7951 cx.simulate_keystroke("-");
7952 cx.executor().run_until_parked();
7953 cx.update_editor(|editor, _| {
7954 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7955 assert_eq!(
7956 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7957 &["bg-red", "bg-blue", "bg-yellow"]
7958 );
7959 } else {
7960 panic!("expected completion menu to be open");
7961 }
7962 });
7963
7964 cx.simulate_keystroke("l");
7965 cx.executor().run_until_parked();
7966 cx.update_editor(|editor, _| {
7967 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7968 assert_eq!(
7969 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7970 &["bg-blue", "bg-yellow"]
7971 );
7972 } else {
7973 panic!("expected completion menu to be open");
7974 }
7975 });
7976
7977 // When filtering completions, consider the character after the '-' to
7978 // be the start of a subword.
7979 cx.set_state(r#"<p class="yelˇ" />"#);
7980 cx.simulate_keystroke("l");
7981 cx.executor().run_until_parked();
7982 cx.update_editor(|editor, _| {
7983 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7984 assert_eq!(
7985 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7986 &["bg-yellow"]
7987 );
7988 } else {
7989 panic!("expected completion menu to be open");
7990 }
7991 });
7992}
7993
7994#[gpui::test]
7995async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
7996 init_test(cx, |settings| {
7997 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
7998 });
7999
8000 let mut language = Language::new(
8001 LanguageConfig {
8002 name: "Rust".into(),
8003 path_suffixes: vec!["rs".to_string()],
8004 prettier_parser_name: Some("test_parser".to_string()),
8005 ..Default::default()
8006 },
8007 Some(tree_sitter_rust::language()),
8008 );
8009
8010 let test_plugin = "test_plugin";
8011 let _ = language
8012 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
8013 prettier_plugins: vec![test_plugin],
8014 ..Default::default()
8015 }))
8016 .await;
8017
8018 let fs = FakeFs::new(cx.executor());
8019 fs.insert_file("/file.rs", Default::default()).await;
8020
8021 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8022 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8023 project.update(cx, |project, _| {
8024 project.languages().add(Arc::new(language));
8025 });
8026 let buffer = project
8027 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8028 .await
8029 .unwrap();
8030
8031 let buffer_text = "one\ntwo\nthree\n";
8032 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
8033 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8034 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8035
8036 editor
8037 .update(cx, |editor, cx| {
8038 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8039 })
8040 .unwrap()
8041 .await;
8042 assert_eq!(
8043 editor.update(cx, |editor, cx| editor.text(cx)),
8044 buffer_text.to_string() + prettier_format_suffix,
8045 "Test prettier formatting was not applied to the original buffer text",
8046 );
8047
8048 update_test_language_settings(cx, |settings| {
8049 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8050 });
8051 let format = editor.update(cx, |editor, cx| {
8052 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8053 });
8054 format.await.unwrap();
8055 assert_eq!(
8056 editor.update(cx, |editor, cx| editor.text(cx)),
8057 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8058 "Autoformatting (via test prettier) was not applied to the original buffer text",
8059 );
8060}
8061
8062fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
8063 let point = DisplayPoint::new(row as u32, column as u32);
8064 point..point
8065}
8066
8067fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
8068 let (text, ranges) = marked_text_ranges(marked_text, true);
8069 assert_eq!(view.text(cx), text);
8070 assert_eq!(
8071 view.selections.ranges(cx),
8072 ranges,
8073 "Assert selections are {}",
8074 marked_text
8075 );
8076}
8077
8078/// Handle completion request passing a marked string specifying where the completion
8079/// should be triggered from using '|' character, what range should be replaced, and what completions
8080/// should be returned using '<' and '>' to delimit the range
8081pub fn handle_completion_request<'a>(
8082 cx: &mut EditorLspTestContext<'a>,
8083 marked_string: &str,
8084 completions: Vec<&'static str>,
8085) -> impl Future<Output = ()> {
8086 let complete_from_marker: TextRangeMarker = '|'.into();
8087 let replace_range_marker: TextRangeMarker = ('<', '>').into();
8088 let (_, mut marked_ranges) = marked_text_ranges_by(
8089 marked_string,
8090 vec![complete_from_marker.clone(), replace_range_marker.clone()],
8091 );
8092
8093 let complete_from_position =
8094 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
8095 let replace_range =
8096 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
8097
8098 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
8099 let completions = completions.clone();
8100 async move {
8101 assert_eq!(params.text_document_position.text_document.uri, url.clone());
8102 assert_eq!(
8103 params.text_document_position.position,
8104 complete_from_position
8105 );
8106 Ok(Some(lsp::CompletionResponse::Array(
8107 completions
8108 .iter()
8109 .map(|completion_text| lsp::CompletionItem {
8110 label: completion_text.to_string(),
8111 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8112 range: replace_range,
8113 new_text: completion_text.to_string(),
8114 })),
8115 ..Default::default()
8116 })
8117 .collect(),
8118 )))
8119 }
8120 });
8121
8122 async move {
8123 request.next().await;
8124 }
8125}
8126
8127fn handle_resolve_completion_request<'a>(
8128 cx: &mut EditorLspTestContext<'a>,
8129 edits: Option<Vec<(&'static str, &'static str)>>,
8130) -> impl Future<Output = ()> {
8131 let edits = edits.map(|edits| {
8132 edits
8133 .iter()
8134 .map(|(marked_string, new_text)| {
8135 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
8136 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
8137 lsp::TextEdit::new(replace_range, new_text.to_string())
8138 })
8139 .collect::<Vec<_>>()
8140 });
8141
8142 let mut request =
8143 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8144 let edits = edits.clone();
8145 async move {
8146 Ok(lsp::CompletionItem {
8147 additional_text_edits: edits,
8148 ..Default::default()
8149 })
8150 }
8151 });
8152
8153 async move {
8154 request.next().await;
8155 }
8156}
8157
8158fn handle_copilot_completion_request(
8159 lsp: &lsp::FakeLanguageServer,
8160 completions: Vec<copilot::request::Completion>,
8161 completions_cycling: Vec<copilot::request::Completion>,
8162) {
8163 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
8164 let completions = completions.clone();
8165 async move {
8166 Ok(copilot::request::GetCompletionsResult {
8167 completions: completions.clone(),
8168 })
8169 }
8170 });
8171 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
8172 let completions_cycling = completions_cycling.clone();
8173 async move {
8174 Ok(copilot::request::GetCompletionsResult {
8175 completions: completions_cycling.clone(),
8176 })
8177 }
8178 });
8179}
8180
8181pub(crate) fn update_test_language_settings(
8182 cx: &mut TestAppContext,
8183 f: impl Fn(&mut AllLanguageSettingsContent),
8184) {
8185 cx.update(|cx| {
8186 cx.update_global(|store: &mut SettingsStore, cx| {
8187 store.update_user_settings::<AllLanguageSettings>(cx, f);
8188 });
8189 });
8190}
8191
8192pub(crate) fn update_test_project_settings(
8193 cx: &mut TestAppContext,
8194 f: impl Fn(&mut ProjectSettings),
8195) {
8196 cx.update(|cx| {
8197 cx.update_global(|store: &mut SettingsStore, cx| {
8198 store.update_user_settings::<ProjectSettings>(cx, f);
8199 });
8200 });
8201}
8202
8203pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
8204 cx.update(|cx| {
8205 let store = SettingsStore::test(cx);
8206 cx.set_global(store);
8207 theme::init(theme::LoadThemes::JustBase, cx);
8208 client::init_settings(cx);
8209 language::init(cx);
8210 Project::init_settings(cx);
8211 workspace::init_settings(cx);
8212 crate::init(cx);
8213 });
8214
8215 update_test_language_settings(cx, f);
8216}