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