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().into_any()),
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, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
3855
3856 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3857 .await;
3858
3859 view.update(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(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(cx, |view, cx| {
3879 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3880 });
3881 assert_eq!(
3882 view.update(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(cx, |view, cx| {
3890 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3891 });
3892 assert_eq!(
3893 view.update(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(cx, |view, cx| {
3899 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3900 });
3901 assert_eq!(
3902 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3903 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3904 );
3905
3906 view.update(cx, |view, cx| {
3907 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3908 });
3909 assert_eq!(
3910 view.update(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(cx, |view, cx| {
3918 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3919 });
3920 assert_eq!(
3921 view.update(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(cx, |view, cx| {
3930 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3931 });
3932 assert_eq!(
3933 view.update(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(cx, |view, cx| {
3943 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3944 });
3945 assert_eq!(
3946 view.update(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(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(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, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4021 editor
4022 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4023 .await;
4024
4025 editor.update(cx, |editor, cx| {
4026 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4027 editor.newline(&Newline, cx);
4028 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4029 assert_eq!(
4030 editor.selections.ranges(cx),
4031 &[
4032 Point::new(1, 4)..Point::new(1, 4),
4033 Point::new(3, 4)..Point::new(3, 4),
4034 Point::new(5, 0)..Point::new(5, 0)
4035 ]
4036 );
4037 });
4038}
4039
4040#[gpui::test]
4041async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4042 init_test(cx, |_| {});
4043
4044 let mut cx = EditorTestContext::new(cx).await;
4045
4046 let language = Arc::new(Language::new(
4047 LanguageConfig {
4048 brackets: BracketPairConfig {
4049 pairs: vec![
4050 BracketPair {
4051 start: "{".to_string(),
4052 end: "}".to_string(),
4053 close: true,
4054 newline: true,
4055 },
4056 BracketPair {
4057 start: "(".to_string(),
4058 end: ")".to_string(),
4059 close: true,
4060 newline: true,
4061 },
4062 BracketPair {
4063 start: "/*".to_string(),
4064 end: " */".to_string(),
4065 close: true,
4066 newline: true,
4067 },
4068 BracketPair {
4069 start: "[".to_string(),
4070 end: "]".to_string(),
4071 close: false,
4072 newline: true,
4073 },
4074 BracketPair {
4075 start: "\"".to_string(),
4076 end: "\"".to_string(),
4077 close: true,
4078 newline: false,
4079 },
4080 ],
4081 ..Default::default()
4082 },
4083 autoclose_before: "})]".to_string(),
4084 ..Default::default()
4085 },
4086 Some(tree_sitter_rust::language()),
4087 ));
4088
4089 let registry = Arc::new(LanguageRegistry::test());
4090 registry.add(language.clone());
4091 cx.update_buffer(|buffer, cx| {
4092 buffer.set_language_registry(registry);
4093 buffer.set_language(Some(language), cx);
4094 });
4095
4096 cx.set_state(
4097 &r#"
4098 🏀ˇ
4099 εˇ
4100 ❤️ˇ
4101 "#
4102 .unindent(),
4103 );
4104
4105 // autoclose multiple nested brackets at multiple cursors
4106 cx.update_editor(|view, cx| {
4107 view.handle_input("{", cx);
4108 view.handle_input("{", cx);
4109 view.handle_input("{", cx);
4110 });
4111 cx.assert_editor_state(
4112 &"
4113 🏀{{{ˇ}}}
4114 ε{{{ˇ}}}
4115 ❤️{{{ˇ}}}
4116 "
4117 .unindent(),
4118 );
4119
4120 // insert a different closing bracket
4121 cx.update_editor(|view, cx| {
4122 view.handle_input(")", cx);
4123 });
4124 cx.assert_editor_state(
4125 &"
4126 🏀{{{)ˇ}}}
4127 ε{{{)ˇ}}}
4128 ❤️{{{)ˇ}}}
4129 "
4130 .unindent(),
4131 );
4132
4133 // skip over the auto-closed brackets when typing a closing bracket
4134 cx.update_editor(|view, cx| {
4135 view.move_right(&MoveRight, cx);
4136 view.handle_input("}", cx);
4137 view.handle_input("}", cx);
4138 view.handle_input("}", cx);
4139 });
4140 cx.assert_editor_state(
4141 &"
4142 🏀{{{)}}}}ˇ
4143 ε{{{)}}}}ˇ
4144 ❤️{{{)}}}}ˇ
4145 "
4146 .unindent(),
4147 );
4148
4149 // autoclose multi-character pairs
4150 cx.set_state(
4151 &"
4152 ˇ
4153 ˇ
4154 "
4155 .unindent(),
4156 );
4157 cx.update_editor(|view, cx| {
4158 view.handle_input("/", cx);
4159 view.handle_input("*", cx);
4160 });
4161 cx.assert_editor_state(
4162 &"
4163 /*ˇ */
4164 /*ˇ */
4165 "
4166 .unindent(),
4167 );
4168
4169 // one cursor autocloses a multi-character pair, one cursor
4170 // does not autoclose.
4171 cx.set_state(
4172 &"
4173 /ˇ
4174 ˇ
4175 "
4176 .unindent(),
4177 );
4178 cx.update_editor(|view, cx| view.handle_input("*", cx));
4179 cx.assert_editor_state(
4180 &"
4181 /*ˇ */
4182 *ˇ
4183 "
4184 .unindent(),
4185 );
4186
4187 // Don't autoclose if the next character isn't whitespace and isn't
4188 // listed in the language's "autoclose_before" section.
4189 cx.set_state("ˇa b");
4190 cx.update_editor(|view, cx| view.handle_input("{", cx));
4191 cx.assert_editor_state("{ˇa b");
4192
4193 // Don't autoclose if `close` is false for the bracket pair
4194 cx.set_state("ˇ");
4195 cx.update_editor(|view, cx| view.handle_input("[", cx));
4196 cx.assert_editor_state("[ˇ");
4197
4198 // Surround with brackets if text is selected
4199 cx.set_state("«aˇ» b");
4200 cx.update_editor(|view, cx| view.handle_input("{", cx));
4201 cx.assert_editor_state("{«aˇ»} b");
4202
4203 // Autclose pair where the start and end characters are the same
4204 cx.set_state("aˇ");
4205 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4206 cx.assert_editor_state("a\"ˇ\"");
4207 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4208 cx.assert_editor_state("a\"\"ˇ");
4209}
4210
4211#[gpui::test]
4212async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4213 init_test(cx, |_| {});
4214
4215 let mut cx = EditorTestContext::new(cx).await;
4216
4217 let html_language = Arc::new(
4218 Language::new(
4219 LanguageConfig {
4220 name: "HTML".into(),
4221 brackets: BracketPairConfig {
4222 pairs: vec![
4223 BracketPair {
4224 start: "<".into(),
4225 end: ">".into(),
4226 close: true,
4227 ..Default::default()
4228 },
4229 BracketPair {
4230 start: "{".into(),
4231 end: "}".into(),
4232 close: true,
4233 ..Default::default()
4234 },
4235 BracketPair {
4236 start: "(".into(),
4237 end: ")".into(),
4238 close: true,
4239 ..Default::default()
4240 },
4241 ],
4242 ..Default::default()
4243 },
4244 autoclose_before: "})]>".into(),
4245 ..Default::default()
4246 },
4247 Some(tree_sitter_html::language()),
4248 )
4249 .with_injection_query(
4250 r#"
4251 (script_element
4252 (raw_text) @content
4253 (#set! "language" "javascript"))
4254 "#,
4255 )
4256 .unwrap(),
4257 );
4258
4259 let javascript_language = Arc::new(Language::new(
4260 LanguageConfig {
4261 name: "JavaScript".into(),
4262 brackets: BracketPairConfig {
4263 pairs: vec![
4264 BracketPair {
4265 start: "/*".into(),
4266 end: " */".into(),
4267 close: true,
4268 ..Default::default()
4269 },
4270 BracketPair {
4271 start: "{".into(),
4272 end: "}".into(),
4273 close: true,
4274 ..Default::default()
4275 },
4276 BracketPair {
4277 start: "(".into(),
4278 end: ")".into(),
4279 close: true,
4280 ..Default::default()
4281 },
4282 ],
4283 ..Default::default()
4284 },
4285 autoclose_before: "})]>".into(),
4286 ..Default::default()
4287 },
4288 Some(tree_sitter_typescript::language_tsx()),
4289 ));
4290
4291 let registry = Arc::new(LanguageRegistry::test());
4292 registry.add(html_language.clone());
4293 registry.add(javascript_language.clone());
4294
4295 cx.update_buffer(|buffer, cx| {
4296 buffer.set_language_registry(registry);
4297 buffer.set_language(Some(html_language), cx);
4298 });
4299
4300 cx.set_state(
4301 &r#"
4302 <body>ˇ
4303 <script>
4304 var x = 1;ˇ
4305 </script>
4306 </body>ˇ
4307 "#
4308 .unindent(),
4309 );
4310
4311 // Precondition: different languages are active at different locations.
4312 cx.update_editor(|editor, cx| {
4313 let snapshot = editor.snapshot(cx);
4314 let cursors = editor.selections.ranges::<usize>(cx);
4315 let languages = cursors
4316 .iter()
4317 .map(|c| snapshot.language_at(c.start).unwrap().name())
4318 .collect::<Vec<_>>();
4319 assert_eq!(
4320 languages,
4321 &["HTML".into(), "JavaScript".into(), "HTML".into()]
4322 );
4323 });
4324
4325 // Angle brackets autoclose in HTML, but not JavaScript.
4326 cx.update_editor(|editor, cx| {
4327 editor.handle_input("<", cx);
4328 editor.handle_input("a", cx);
4329 });
4330 cx.assert_editor_state(
4331 &r#"
4332 <body><aˇ>
4333 <script>
4334 var x = 1;<aˇ
4335 </script>
4336 </body><aˇ>
4337 "#
4338 .unindent(),
4339 );
4340
4341 // Curly braces and parens autoclose in both HTML and JavaScript.
4342 cx.update_editor(|editor, cx| {
4343 editor.handle_input(" b=", cx);
4344 editor.handle_input("{", cx);
4345 editor.handle_input("c", cx);
4346 editor.handle_input("(", cx);
4347 });
4348 cx.assert_editor_state(
4349 &r#"
4350 <body><a b={c(ˇ)}>
4351 <script>
4352 var x = 1;<a b={c(ˇ)}
4353 </script>
4354 </body><a b={c(ˇ)}>
4355 "#
4356 .unindent(),
4357 );
4358
4359 // Brackets that were already autoclosed are skipped.
4360 cx.update_editor(|editor, cx| {
4361 editor.handle_input(")", cx);
4362 editor.handle_input("d", cx);
4363 editor.handle_input("}", cx);
4364 });
4365 cx.assert_editor_state(
4366 &r#"
4367 <body><a b={c()d}ˇ>
4368 <script>
4369 var x = 1;<a b={c()d}ˇ
4370 </script>
4371 </body><a b={c()d}ˇ>
4372 "#
4373 .unindent(),
4374 );
4375 cx.update_editor(|editor, cx| {
4376 editor.handle_input(">", cx);
4377 });
4378 cx.assert_editor_state(
4379 &r#"
4380 <body><a b={c()d}>ˇ
4381 <script>
4382 var x = 1;<a b={c()d}>ˇ
4383 </script>
4384 </body><a b={c()d}>ˇ
4385 "#
4386 .unindent(),
4387 );
4388
4389 // Reset
4390 cx.set_state(
4391 &r#"
4392 <body>ˇ
4393 <script>
4394 var x = 1;ˇ
4395 </script>
4396 </body>ˇ
4397 "#
4398 .unindent(),
4399 );
4400
4401 cx.update_editor(|editor, cx| {
4402 editor.handle_input("<", cx);
4403 });
4404 cx.assert_editor_state(
4405 &r#"
4406 <body><ˇ>
4407 <script>
4408 var x = 1;<ˇ
4409 </script>
4410 </body><ˇ>
4411 "#
4412 .unindent(),
4413 );
4414
4415 // When backspacing, the closing angle brackets are removed.
4416 cx.update_editor(|editor, cx| {
4417 editor.backspace(&Backspace, cx);
4418 });
4419 cx.assert_editor_state(
4420 &r#"
4421 <body>ˇ
4422 <script>
4423 var x = 1;ˇ
4424 </script>
4425 </body>ˇ
4426 "#
4427 .unindent(),
4428 );
4429
4430 // Block comments autoclose in JavaScript, but not HTML.
4431 cx.update_editor(|editor, cx| {
4432 editor.handle_input("/", cx);
4433 editor.handle_input("*", cx);
4434 });
4435 cx.assert_editor_state(
4436 &r#"
4437 <body>/*ˇ
4438 <script>
4439 var x = 1;/*ˇ */
4440 </script>
4441 </body>/*ˇ
4442 "#
4443 .unindent(),
4444 );
4445}
4446
4447#[gpui::test]
4448async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4449 init_test(cx, |_| {});
4450
4451 let mut cx = EditorTestContext::new(cx).await;
4452
4453 let rust_language = Arc::new(
4454 Language::new(
4455 LanguageConfig {
4456 name: "Rust".into(),
4457 brackets: serde_json::from_value(json!([
4458 { "start": "{", "end": "}", "close": true, "newline": true },
4459 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4460 ]))
4461 .unwrap(),
4462 autoclose_before: "})]>".into(),
4463 ..Default::default()
4464 },
4465 Some(tree_sitter_rust::language()),
4466 )
4467 .with_override_query("(string_literal) @string")
4468 .unwrap(),
4469 );
4470
4471 let registry = Arc::new(LanguageRegistry::test());
4472 registry.add(rust_language.clone());
4473
4474 cx.update_buffer(|buffer, cx| {
4475 buffer.set_language_registry(registry);
4476 buffer.set_language(Some(rust_language), cx);
4477 });
4478
4479 cx.set_state(
4480 &r#"
4481 let x = ˇ
4482 "#
4483 .unindent(),
4484 );
4485
4486 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4487 cx.update_editor(|editor, cx| {
4488 editor.handle_input("\"", cx);
4489 });
4490 cx.assert_editor_state(
4491 &r#"
4492 let x = "ˇ"
4493 "#
4494 .unindent(),
4495 );
4496
4497 // Inserting another quotation mark. The cursor moves across the existing
4498 // automatically-inserted quotation mark.
4499 cx.update_editor(|editor, cx| {
4500 editor.handle_input("\"", cx);
4501 });
4502 cx.assert_editor_state(
4503 &r#"
4504 let x = ""ˇ
4505 "#
4506 .unindent(),
4507 );
4508
4509 // Reset
4510 cx.set_state(
4511 &r#"
4512 let x = ˇ
4513 "#
4514 .unindent(),
4515 );
4516
4517 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4518 cx.update_editor(|editor, cx| {
4519 editor.handle_input("\"", cx);
4520 editor.handle_input(" ", cx);
4521 editor.move_left(&Default::default(), cx);
4522 editor.handle_input("\\", cx);
4523 editor.handle_input("\"", cx);
4524 });
4525 cx.assert_editor_state(
4526 &r#"
4527 let x = "\"ˇ "
4528 "#
4529 .unindent(),
4530 );
4531
4532 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4533 // mark. Nothing is inserted.
4534 cx.update_editor(|editor, cx| {
4535 editor.move_right(&Default::default(), cx);
4536 editor.handle_input("\"", cx);
4537 });
4538 cx.assert_editor_state(
4539 &r#"
4540 let x = "\" "ˇ
4541 "#
4542 .unindent(),
4543 );
4544}
4545
4546#[gpui::test]
4547async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4548 init_test(cx, |_| {});
4549
4550 let language = Arc::new(Language::new(
4551 LanguageConfig {
4552 brackets: BracketPairConfig {
4553 pairs: vec![
4554 BracketPair {
4555 start: "{".to_string(),
4556 end: "}".to_string(),
4557 close: true,
4558 newline: true,
4559 },
4560 BracketPair {
4561 start: "/* ".to_string(),
4562 end: "*/".to_string(),
4563 close: true,
4564 ..Default::default()
4565 },
4566 ],
4567 ..Default::default()
4568 },
4569 ..Default::default()
4570 },
4571 Some(tree_sitter_rust::language()),
4572 ));
4573
4574 let text = r#"
4575 a
4576 b
4577 c
4578 "#
4579 .unindent();
4580
4581 let buffer = cx.build_model(|cx| {
4582 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4583 });
4584 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4585 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4586 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4587 .await;
4588
4589 view.update(cx, |view, cx| {
4590 view.change_selections(None, cx, |s| {
4591 s.select_display_ranges([
4592 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4593 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4594 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4595 ])
4596 });
4597
4598 view.handle_input("{", cx);
4599 view.handle_input("{", cx);
4600 view.handle_input("{", cx);
4601 assert_eq!(
4602 view.text(cx),
4603 "
4604 {{{a}}}
4605 {{{b}}}
4606 {{{c}}}
4607 "
4608 .unindent()
4609 );
4610 assert_eq!(
4611 view.selections.display_ranges(cx),
4612 [
4613 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4614 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4615 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4616 ]
4617 );
4618
4619 view.undo(&Undo, cx);
4620 view.undo(&Undo, cx);
4621 view.undo(&Undo, cx);
4622 assert_eq!(
4623 view.text(cx),
4624 "
4625 a
4626 b
4627 c
4628 "
4629 .unindent()
4630 );
4631 assert_eq!(
4632 view.selections.display_ranges(cx),
4633 [
4634 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4635 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4636 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4637 ]
4638 );
4639
4640 // Ensure inserting the first character of a multi-byte bracket pair
4641 // doesn't surround the selections with the bracket.
4642 view.handle_input("/", cx);
4643 assert_eq!(
4644 view.text(cx),
4645 "
4646 /
4647 /
4648 /
4649 "
4650 .unindent()
4651 );
4652 assert_eq!(
4653 view.selections.display_ranges(cx),
4654 [
4655 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4656 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4657 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4658 ]
4659 );
4660
4661 view.undo(&Undo, cx);
4662 assert_eq!(
4663 view.text(cx),
4664 "
4665 a
4666 b
4667 c
4668 "
4669 .unindent()
4670 );
4671 assert_eq!(
4672 view.selections.display_ranges(cx),
4673 [
4674 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4675 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4676 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4677 ]
4678 );
4679
4680 // Ensure inserting the last character of a multi-byte bracket pair
4681 // doesn't surround the selections with the bracket.
4682 view.handle_input("*", cx);
4683 assert_eq!(
4684 view.text(cx),
4685 "
4686 *
4687 *
4688 *
4689 "
4690 .unindent()
4691 );
4692 assert_eq!(
4693 view.selections.display_ranges(cx),
4694 [
4695 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4696 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4697 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4698 ]
4699 );
4700 });
4701}
4702
4703#[gpui::test]
4704async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4705 init_test(cx, |_| {});
4706
4707 let language = Arc::new(Language::new(
4708 LanguageConfig {
4709 brackets: BracketPairConfig {
4710 pairs: vec![BracketPair {
4711 start: "{".to_string(),
4712 end: "}".to_string(),
4713 close: true,
4714 newline: true,
4715 }],
4716 ..Default::default()
4717 },
4718 autoclose_before: "}".to_string(),
4719 ..Default::default()
4720 },
4721 Some(tree_sitter_rust::language()),
4722 ));
4723
4724 let text = r#"
4725 a
4726 b
4727 c
4728 "#
4729 .unindent();
4730
4731 let buffer = cx.build_model(|cx| {
4732 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4733 });
4734 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4735 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4736 editor
4737 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4738 .await;
4739
4740 editor.update(cx, |editor, cx| {
4741 editor.change_selections(None, cx, |s| {
4742 s.select_ranges([
4743 Point::new(0, 1)..Point::new(0, 1),
4744 Point::new(1, 1)..Point::new(1, 1),
4745 Point::new(2, 1)..Point::new(2, 1),
4746 ])
4747 });
4748
4749 editor.handle_input("{", cx);
4750 editor.handle_input("{", cx);
4751 editor.handle_input("_", cx);
4752 assert_eq!(
4753 editor.text(cx),
4754 "
4755 a{{_}}
4756 b{{_}}
4757 c{{_}}
4758 "
4759 .unindent()
4760 );
4761 assert_eq!(
4762 editor.selections.ranges::<Point>(cx),
4763 [
4764 Point::new(0, 4)..Point::new(0, 4),
4765 Point::new(1, 4)..Point::new(1, 4),
4766 Point::new(2, 4)..Point::new(2, 4)
4767 ]
4768 );
4769
4770 editor.backspace(&Default::default(), cx);
4771 editor.backspace(&Default::default(), cx);
4772 assert_eq!(
4773 editor.text(cx),
4774 "
4775 a{}
4776 b{}
4777 c{}
4778 "
4779 .unindent()
4780 );
4781 assert_eq!(
4782 editor.selections.ranges::<Point>(cx),
4783 [
4784 Point::new(0, 2)..Point::new(0, 2),
4785 Point::new(1, 2)..Point::new(1, 2),
4786 Point::new(2, 2)..Point::new(2, 2)
4787 ]
4788 );
4789
4790 editor.delete_to_previous_word_start(&Default::default(), cx);
4791 assert_eq!(
4792 editor.text(cx),
4793 "
4794 a
4795 b
4796 c
4797 "
4798 .unindent()
4799 );
4800 assert_eq!(
4801 editor.selections.ranges::<Point>(cx),
4802 [
4803 Point::new(0, 1)..Point::new(0, 1),
4804 Point::new(1, 1)..Point::new(1, 1),
4805 Point::new(2, 1)..Point::new(2, 1)
4806 ]
4807 );
4808 });
4809}
4810
4811// todo!(select_anchor_ranges)
4812// #[gpui::test]
4813// async fn test_snippets(cx: &mut gpui::TestAppContext) {
4814// init_test(cx, |_| {});
4815
4816// let (text, insertion_ranges) = marked_text_ranges(
4817// indoc! {"
4818// a.ˇ b
4819// a.ˇ b
4820// a.ˇ b
4821// "},
4822// false,
4823// );
4824
4825// let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4826// let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4827// let cx = &mut cx;
4828
4829// editor.update(cx, |editor, cx| {
4830// let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4831
4832// editor
4833// .insert_snippet(&insertion_ranges, snippet, cx)
4834// .unwrap();
4835
4836// fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4837// let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4838// assert_eq!(editor.text(cx), expected_text);
4839// assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4840// }
4841
4842// assert(
4843// editor,
4844// cx,
4845// indoc! {"
4846// a.f(«one», two, «three») b
4847// a.f(«one», two, «three») b
4848// a.f(«one», two, «three») b
4849// "},
4850// );
4851
4852// // Can't move earlier than the first tab stop
4853// assert!(!editor.move_to_prev_snippet_tabstop(cx));
4854// assert(
4855// editor,
4856// cx,
4857// indoc! {"
4858// a.f(«one», two, «three») b
4859// a.f(«one», two, «three») b
4860// a.f(«one», two, «three») b
4861// "},
4862// );
4863
4864// assert!(editor.move_to_next_snippet_tabstop(cx));
4865// assert(
4866// editor,
4867// cx,
4868// indoc! {"
4869// a.f(one, «two», three) b
4870// a.f(one, «two», three) b
4871// a.f(one, «two», three) b
4872// "},
4873// );
4874
4875// editor.move_to_prev_snippet_tabstop(cx);
4876// assert(
4877// editor,
4878// cx,
4879// indoc! {"
4880// a.f(«one», two, «three») b
4881// a.f(«one», two, «three») b
4882// a.f(«one», two, «three») b
4883// "},
4884// );
4885
4886// assert!(editor.move_to_next_snippet_tabstop(cx));
4887// assert(
4888// editor,
4889// cx,
4890// indoc! {"
4891// a.f(one, «two», three) b
4892// a.f(one, «two», three) b
4893// a.f(one, «two», three) b
4894// "},
4895// );
4896// assert!(editor.move_to_next_snippet_tabstop(cx));
4897// assert(
4898// editor,
4899// cx,
4900// indoc! {"
4901// a.f(one, two, three)ˇ b
4902// a.f(one, two, three)ˇ b
4903// a.f(one, two, three)ˇ b
4904// "},
4905// );
4906
4907// // As soon as the last tab stop is reached, snippet state is gone
4908// editor.move_to_prev_snippet_tabstop(cx);
4909// assert(
4910// editor,
4911// cx,
4912// indoc! {"
4913// a.f(one, two, three)ˇ b
4914// a.f(one, two, three)ˇ b
4915// a.f(one, two, three)ˇ b
4916// "},
4917// );
4918// });
4919// }
4920
4921#[gpui::test]
4922async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4923 init_test(cx, |_| {});
4924
4925 let mut language = Language::new(
4926 LanguageConfig {
4927 name: "Rust".into(),
4928 path_suffixes: vec!["rs".to_string()],
4929 ..Default::default()
4930 },
4931 Some(tree_sitter_rust::language()),
4932 );
4933 let mut fake_servers = language
4934 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4935 capabilities: lsp::ServerCapabilities {
4936 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4937 ..Default::default()
4938 },
4939 ..Default::default()
4940 }))
4941 .await;
4942
4943 let fs = FakeFs::new(cx.executor());
4944 fs.insert_file("/file.rs", Default::default()).await;
4945
4946 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4947 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4948 let buffer = project
4949 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4950 .await
4951 .unwrap();
4952
4953 cx.executor().start_waiting();
4954 let fake_server = fake_servers.next().await.unwrap();
4955
4956 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4957 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4958 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4959 assert!(cx.read(|cx| editor.is_dirty(cx)));
4960
4961 let save = editor
4962 .update(cx, |editor, cx| editor.save(project.clone(), cx))
4963 .unwrap();
4964 fake_server
4965 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4966 assert_eq!(
4967 params.text_document.uri,
4968 lsp::Url::from_file_path("/file.rs").unwrap()
4969 );
4970 assert_eq!(params.options.tab_size, 4);
4971 Ok(Some(vec![lsp::TextEdit::new(
4972 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4973 ", ".to_string(),
4974 )]))
4975 })
4976 .next()
4977 .await;
4978 cx.executor().start_waiting();
4979 let x = save.await;
4980
4981 assert_eq!(
4982 editor.update(cx, |editor, cx| editor.text(cx)),
4983 "one, two\nthree\n"
4984 );
4985 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4986
4987 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4988 assert!(cx.read(|cx| editor.is_dirty(cx)));
4989
4990 // Ensure we can still save even if formatting hangs.
4991 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4992 assert_eq!(
4993 params.text_document.uri,
4994 lsp::Url::from_file_path("/file.rs").unwrap()
4995 );
4996 futures::future::pending::<()>().await;
4997 unreachable!()
4998 });
4999 let save = editor
5000 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5001 .unwrap();
5002 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5003 cx.executor().start_waiting();
5004 save.await;
5005 assert_eq!(
5006 editor.update(cx, |editor, cx| editor.text(cx)),
5007 "one\ntwo\nthree\n"
5008 );
5009 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5010
5011 // Set rust language override and assert overridden tabsize is sent to language server
5012 update_test_language_settings(cx, |settings| {
5013 settings.languages.insert(
5014 "Rust".into(),
5015 LanguageSettingsContent {
5016 tab_size: NonZeroU32::new(8),
5017 ..Default::default()
5018 },
5019 );
5020 });
5021
5022 let save = editor
5023 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5024 .unwrap();
5025 fake_server
5026 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5027 assert_eq!(
5028 params.text_document.uri,
5029 lsp::Url::from_file_path("/file.rs").unwrap()
5030 );
5031 assert_eq!(params.options.tab_size, 8);
5032 Ok(Some(vec![]))
5033 })
5034 .next()
5035 .await;
5036 cx.executor().start_waiting();
5037 save.await;
5038}
5039
5040#[gpui::test]
5041async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5042 init_test(cx, |_| {});
5043
5044 let mut language = Language::new(
5045 LanguageConfig {
5046 name: "Rust".into(),
5047 path_suffixes: vec!["rs".to_string()],
5048 ..Default::default()
5049 },
5050 Some(tree_sitter_rust::language()),
5051 );
5052 let mut fake_servers = language
5053 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5054 capabilities: lsp::ServerCapabilities {
5055 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5056 ..Default::default()
5057 },
5058 ..Default::default()
5059 }))
5060 .await;
5061
5062 let fs = FakeFs::new(cx.executor());
5063 fs.insert_file("/file.rs", Default::default()).await;
5064
5065 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5066 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5067 let buffer = project
5068 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5069 .await
5070 .unwrap();
5071
5072 cx.executor().start_waiting();
5073 let fake_server = fake_servers.next().await.unwrap();
5074
5075 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
5076 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5077 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5078 assert!(cx.read(|cx| editor.is_dirty(cx)));
5079
5080 let save = editor
5081 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5082 .unwrap();
5083 fake_server
5084 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5085 assert_eq!(
5086 params.text_document.uri,
5087 lsp::Url::from_file_path("/file.rs").unwrap()
5088 );
5089 assert_eq!(params.options.tab_size, 4);
5090 Ok(Some(vec![lsp::TextEdit::new(
5091 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5092 ", ".to_string(),
5093 )]))
5094 })
5095 .next()
5096 .await;
5097 cx.executor().start_waiting();
5098 save.await;
5099 assert_eq!(
5100 editor.update(cx, |editor, cx| editor.text(cx)),
5101 "one, two\nthree\n"
5102 );
5103 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5104
5105 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5106 assert!(cx.read(|cx| editor.is_dirty(cx)));
5107
5108 // Ensure we can still save even if formatting hangs.
5109 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5110 move |params, _| async move {
5111 assert_eq!(
5112 params.text_document.uri,
5113 lsp::Url::from_file_path("/file.rs").unwrap()
5114 );
5115 futures::future::pending::<()>().await;
5116 unreachable!()
5117 },
5118 );
5119 let save = editor
5120 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5121 .unwrap();
5122 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5123 cx.executor().start_waiting();
5124 save.await;
5125 assert_eq!(
5126 editor.update(cx, |editor, cx| editor.text(cx)),
5127 "one\ntwo\nthree\n"
5128 );
5129 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5130
5131 // Set rust language override and assert overridden tabsize is sent to language server
5132 update_test_language_settings(cx, |settings| {
5133 settings.languages.insert(
5134 "Rust".into(),
5135 LanguageSettingsContent {
5136 tab_size: NonZeroU32::new(8),
5137 ..Default::default()
5138 },
5139 );
5140 });
5141
5142 let save = editor
5143 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5144 .unwrap();
5145 fake_server
5146 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5147 assert_eq!(
5148 params.text_document.uri,
5149 lsp::Url::from_file_path("/file.rs").unwrap()
5150 );
5151 assert_eq!(params.options.tab_size, 8);
5152 Ok(Some(vec![]))
5153 })
5154 .next()
5155 .await;
5156 cx.executor().start_waiting();
5157 save.await;
5158}
5159
5160#[gpui::test]
5161async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5162 init_test(cx, |settings| {
5163 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
5164 });
5165
5166 let mut language = Language::new(
5167 LanguageConfig {
5168 name: "Rust".into(),
5169 path_suffixes: vec!["rs".to_string()],
5170 // Enable Prettier formatting for the same buffer, and ensure
5171 // LSP is called instead of Prettier.
5172 prettier_parser_name: Some("test_parser".to_string()),
5173 ..Default::default()
5174 },
5175 Some(tree_sitter_rust::language()),
5176 );
5177 let mut fake_servers = language
5178 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5179 capabilities: lsp::ServerCapabilities {
5180 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5181 ..Default::default()
5182 },
5183 ..Default::default()
5184 }))
5185 .await;
5186
5187 let fs = FakeFs::new(cx.executor());
5188 fs.insert_file("/file.rs", Default::default()).await;
5189
5190 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5191 project.update(cx, |project, _| {
5192 project.languages().add(Arc::new(language));
5193 });
5194 let buffer = project
5195 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5196 .await
5197 .unwrap();
5198
5199 cx.executor().start_waiting();
5200 let fake_server = fake_servers.next().await.unwrap();
5201
5202 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
5203 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5204 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5205
5206 let format = editor
5207 .update(cx, |editor, cx| {
5208 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5209 })
5210 .unwrap();
5211 fake_server
5212 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5213 assert_eq!(
5214 params.text_document.uri,
5215 lsp::Url::from_file_path("/file.rs").unwrap()
5216 );
5217 assert_eq!(params.options.tab_size, 4);
5218 Ok(Some(vec![lsp::TextEdit::new(
5219 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5220 ", ".to_string(),
5221 )]))
5222 })
5223 .next()
5224 .await;
5225 cx.executor().start_waiting();
5226 format.await;
5227 assert_eq!(
5228 editor.update(cx, |editor, cx| editor.text(cx)),
5229 "one, two\nthree\n"
5230 );
5231
5232 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5233 // Ensure we don't lock if formatting hangs.
5234 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5235 assert_eq!(
5236 params.text_document.uri,
5237 lsp::Url::from_file_path("/file.rs").unwrap()
5238 );
5239 futures::future::pending::<()>().await;
5240 unreachable!()
5241 });
5242 let format = editor
5243 .update(cx, |editor, cx| {
5244 editor.perform_format(project, FormatTrigger::Manual, cx)
5245 })
5246 .unwrap();
5247 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5248 cx.executor().start_waiting();
5249 format.await;
5250 assert_eq!(
5251 editor.update(cx, |editor, cx| editor.text(cx)),
5252 "one\ntwo\nthree\n"
5253 );
5254}
5255
5256#[gpui::test]
5257async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5258 init_test(cx, |_| {});
5259
5260 let mut cx = EditorLspTestContext::new_rust(
5261 lsp::ServerCapabilities {
5262 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5263 ..Default::default()
5264 },
5265 cx,
5266 )
5267 .await;
5268
5269 cx.set_state(indoc! {"
5270 one.twoˇ
5271 "});
5272
5273 // The format request takes a long time. When it completes, it inserts
5274 // a newline and an indent before the `.`
5275 cx.lsp
5276 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5277 let executor = cx.background_executor().clone();
5278 async move {
5279 executor.timer(Duration::from_millis(100)).await;
5280 Ok(Some(vec![lsp::TextEdit {
5281 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5282 new_text: "\n ".into(),
5283 }]))
5284 }
5285 });
5286
5287 // Submit a format request.
5288 let format_1 = cx
5289 .update_editor(|editor, cx| editor.format(&Format, cx))
5290 .unwrap();
5291 cx.executor().run_until_parked();
5292
5293 // Submit a second format request.
5294 let format_2 = cx
5295 .update_editor(|editor, cx| editor.format(&Format, cx))
5296 .unwrap();
5297 cx.executor().run_until_parked();
5298
5299 // Wait for both format requests to complete
5300 cx.executor().advance_clock(Duration::from_millis(200));
5301 cx.executor().start_waiting();
5302 format_1.await.unwrap();
5303 cx.executor().start_waiting();
5304 format_2.await.unwrap();
5305
5306 // The formatting edits only happens once.
5307 cx.assert_editor_state(indoc! {"
5308 one
5309 .twoˇ
5310 "});
5311}
5312
5313#[gpui::test]
5314async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5315 init_test(cx, |settings| {
5316 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5317 });
5318
5319 let mut cx = EditorLspTestContext::new_rust(
5320 lsp::ServerCapabilities {
5321 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5322 ..Default::default()
5323 },
5324 cx,
5325 )
5326 .await;
5327
5328 // Set up a buffer white some trailing whitespace and no trailing newline.
5329 cx.set_state(
5330 &[
5331 "one ", //
5332 "twoˇ", //
5333 "three ", //
5334 "four", //
5335 ]
5336 .join("\n"),
5337 );
5338
5339 // Submit a format request.
5340 let format = cx
5341 .update_editor(|editor, cx| editor.format(&Format, cx))
5342 .unwrap();
5343
5344 // Record which buffer changes have been sent to the language server
5345 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5346 cx.lsp
5347 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5348 let buffer_changes = buffer_changes.clone();
5349 move |params, _| {
5350 buffer_changes.lock().extend(
5351 params
5352 .content_changes
5353 .into_iter()
5354 .map(|e| (e.range.unwrap(), e.text)),
5355 );
5356 }
5357 });
5358
5359 // Handle formatting requests to the language server.
5360 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5361 let buffer_changes = buffer_changes.clone();
5362 move |_, _| {
5363 // When formatting is requested, trailing whitespace has already been stripped,
5364 // and the trailing newline has already been added.
5365 assert_eq!(
5366 &buffer_changes.lock()[1..],
5367 &[
5368 (
5369 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5370 "".into()
5371 ),
5372 (
5373 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5374 "".into()
5375 ),
5376 (
5377 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5378 "\n".into()
5379 ),
5380 ]
5381 );
5382
5383 // Insert blank lines between each line of the buffer.
5384 async move {
5385 Ok(Some(vec![
5386 lsp::TextEdit {
5387 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5388 new_text: "\n".into(),
5389 },
5390 lsp::TextEdit {
5391 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5392 new_text: "\n".into(),
5393 },
5394 ]))
5395 }
5396 }
5397 });
5398
5399 // After formatting the buffer, the trailing whitespace is stripped,
5400 // a newline is appended, and the edits provided by the language server
5401 // have been applied.
5402 format.await.unwrap();
5403 cx.assert_editor_state(
5404 &[
5405 "one", //
5406 "", //
5407 "twoˇ", //
5408 "", //
5409 "three", //
5410 "four", //
5411 "", //
5412 ]
5413 .join("\n"),
5414 );
5415
5416 // Undoing the formatting undoes the trailing whitespace removal, the
5417 // trailing newline, and the LSP edits.
5418 cx.update_buffer(|buffer, cx| buffer.undo(cx));
5419 cx.assert_editor_state(
5420 &[
5421 "one ", //
5422 "twoˇ", //
5423 "three ", //
5424 "four", //
5425 ]
5426 .join("\n"),
5427 );
5428}
5429
5430#[gpui::test]
5431async fn test_completion(cx: &mut gpui::TestAppContext) {
5432 init_test(cx, |_| {});
5433
5434 let mut cx = EditorLspTestContext::new_rust(
5435 lsp::ServerCapabilities {
5436 completion_provider: Some(lsp::CompletionOptions {
5437 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5438 resolve_provider: Some(true),
5439 ..Default::default()
5440 }),
5441 ..Default::default()
5442 },
5443 cx,
5444 )
5445 .await;
5446
5447 cx.set_state(indoc! {"
5448 oneˇ
5449 two
5450 three
5451 "});
5452 cx.simulate_keystroke(".");
5453 handle_completion_request(
5454 &mut cx,
5455 indoc! {"
5456 one.|<>
5457 two
5458 three
5459 "},
5460 vec!["first_completion", "second_completion"],
5461 )
5462 .await;
5463 cx.condition(|editor, _| editor.context_menu_visible())
5464 .await;
5465 let apply_additional_edits = cx.update_editor(|editor, cx| {
5466 editor.context_menu_next(&Default::default(), cx);
5467 editor
5468 .confirm_completion(&ConfirmCompletion::default(), cx)
5469 .unwrap()
5470 });
5471 cx.assert_editor_state(indoc! {"
5472 one.second_completionˇ
5473 two
5474 three
5475 "});
5476
5477 handle_resolve_completion_request(
5478 &mut cx,
5479 Some(vec![
5480 (
5481 //This overlaps with the primary completion edit which is
5482 //misbehavior from the LSP spec, test that we filter it out
5483 indoc! {"
5484 one.second_ˇcompletion
5485 two
5486 threeˇ
5487 "},
5488 "overlapping additional edit",
5489 ),
5490 (
5491 indoc! {"
5492 one.second_completion
5493 two
5494 threeˇ
5495 "},
5496 "\nadditional edit",
5497 ),
5498 ]),
5499 )
5500 .await;
5501 apply_additional_edits.await.unwrap();
5502 cx.assert_editor_state(indoc! {"
5503 one.second_completionˇ
5504 two
5505 three
5506 additional edit
5507 "});
5508
5509 cx.set_state(indoc! {"
5510 one.second_completion
5511 twoˇ
5512 threeˇ
5513 additional edit
5514 "});
5515 cx.simulate_keystroke(" ");
5516 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5517 cx.simulate_keystroke("s");
5518 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5519
5520 cx.assert_editor_state(indoc! {"
5521 one.second_completion
5522 two sˇ
5523 three sˇ
5524 additional edit
5525 "});
5526 handle_completion_request(
5527 &mut cx,
5528 indoc! {"
5529 one.second_completion
5530 two s
5531 three <s|>
5532 additional edit
5533 "},
5534 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5535 )
5536 .await;
5537 cx.condition(|editor, _| editor.context_menu_visible())
5538 .await;
5539
5540 cx.simulate_keystroke("i");
5541
5542 handle_completion_request(
5543 &mut cx,
5544 indoc! {"
5545 one.second_completion
5546 two si
5547 three <si|>
5548 additional edit
5549 "},
5550 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5551 )
5552 .await;
5553 cx.condition(|editor, _| editor.context_menu_visible())
5554 .await;
5555
5556 let apply_additional_edits = cx.update_editor(|editor, cx| {
5557 editor
5558 .confirm_completion(&ConfirmCompletion::default(), cx)
5559 .unwrap()
5560 });
5561 cx.assert_editor_state(indoc! {"
5562 one.second_completion
5563 two sixth_completionˇ
5564 three sixth_completionˇ
5565 additional edit
5566 "});
5567
5568 handle_resolve_completion_request(&mut cx, None).await;
5569 apply_additional_edits.await.unwrap();
5570
5571 cx.update(|cx| {
5572 cx.update_global::<SettingsStore, _>(|settings, cx| {
5573 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5574 settings.show_completions_on_input = Some(false);
5575 });
5576 })
5577 });
5578 cx.set_state("editorˇ");
5579 cx.simulate_keystroke(".");
5580 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5581 cx.simulate_keystroke("c");
5582 cx.simulate_keystroke("l");
5583 cx.simulate_keystroke("o");
5584 cx.assert_editor_state("editor.cloˇ");
5585 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5586 cx.update_editor(|editor, cx| {
5587 editor.show_completions(&ShowCompletions, cx);
5588 });
5589 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5590 cx.condition(|editor, _| editor.context_menu_visible())
5591 .await;
5592 let apply_additional_edits = cx.update_editor(|editor, cx| {
5593 editor
5594 .confirm_completion(&ConfirmCompletion::default(), cx)
5595 .unwrap()
5596 });
5597 cx.assert_editor_state("editor.closeˇ");
5598 handle_resolve_completion_request(&mut cx, None).await;
5599 apply_additional_edits.await.unwrap();
5600}
5601
5602#[gpui::test]
5603async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5604 init_test(cx, |_| {});
5605 let mut cx = EditorTestContext::new(cx).await;
5606 let language = Arc::new(Language::new(
5607 LanguageConfig {
5608 line_comment: Some("// ".into()),
5609 ..Default::default()
5610 },
5611 Some(tree_sitter_rust::language()),
5612 ));
5613 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5614
5615 // If multiple selections intersect a line, the line is only toggled once.
5616 cx.set_state(indoc! {"
5617 fn a() {
5618 «//b();
5619 ˇ»// «c();
5620 //ˇ» d();
5621 }
5622 "});
5623
5624 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5625
5626 cx.assert_editor_state(indoc! {"
5627 fn a() {
5628 «b();
5629 c();
5630 ˇ» d();
5631 }
5632 "});
5633
5634 // The comment prefix is inserted at the same column for every line in a
5635 // selection.
5636 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5637
5638 cx.assert_editor_state(indoc! {"
5639 fn a() {
5640 // «b();
5641 // c();
5642 ˇ»// d();
5643 }
5644 "});
5645
5646 // If a selection ends at the beginning of a line, that line is not toggled.
5647 cx.set_selections_state(indoc! {"
5648 fn a() {
5649 // b();
5650 «// c();
5651 ˇ» // d();
5652 }
5653 "});
5654
5655 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5656
5657 cx.assert_editor_state(indoc! {"
5658 fn a() {
5659 // b();
5660 «c();
5661 ˇ» // d();
5662 }
5663 "});
5664
5665 // If a selection span a single line and is empty, the line is toggled.
5666 cx.set_state(indoc! {"
5667 fn a() {
5668 a();
5669 b();
5670 ˇ
5671 }
5672 "});
5673
5674 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5675
5676 cx.assert_editor_state(indoc! {"
5677 fn a() {
5678 a();
5679 b();
5680 //•ˇ
5681 }
5682 "});
5683
5684 // If a selection span multiple lines, empty lines are not toggled.
5685 cx.set_state(indoc! {"
5686 fn a() {
5687 «a();
5688
5689 c();ˇ»
5690 }
5691 "});
5692
5693 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5694
5695 cx.assert_editor_state(indoc! {"
5696 fn a() {
5697 // «a();
5698
5699 // c();ˇ»
5700 }
5701 "});
5702}
5703
5704#[gpui::test]
5705async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5706 init_test(cx, |_| {});
5707
5708 let language = Arc::new(Language::new(
5709 LanguageConfig {
5710 line_comment: Some("// ".into()),
5711 ..Default::default()
5712 },
5713 Some(tree_sitter_rust::language()),
5714 ));
5715
5716 let registry = Arc::new(LanguageRegistry::test());
5717 registry.add(language.clone());
5718
5719 let mut cx = EditorTestContext::new(cx).await;
5720 cx.update_buffer(|buffer, cx| {
5721 buffer.set_language_registry(registry);
5722 buffer.set_language(Some(language), cx);
5723 });
5724
5725 let toggle_comments = &ToggleComments {
5726 advance_downwards: true,
5727 };
5728
5729 // Single cursor on one line -> advance
5730 // Cursor moves horizontally 3 characters as well on non-blank line
5731 cx.set_state(indoc!(
5732 "fn a() {
5733 ˇdog();
5734 cat();
5735 }"
5736 ));
5737 cx.update_editor(|editor, cx| {
5738 editor.toggle_comments(toggle_comments, cx);
5739 });
5740 cx.assert_editor_state(indoc!(
5741 "fn a() {
5742 // dog();
5743 catˇ();
5744 }"
5745 ));
5746
5747 // Single selection on one line -> don't advance
5748 cx.set_state(indoc!(
5749 "fn a() {
5750 «dog()ˇ»;
5751 cat();
5752 }"
5753 ));
5754 cx.update_editor(|editor, cx| {
5755 editor.toggle_comments(toggle_comments, cx);
5756 });
5757 cx.assert_editor_state(indoc!(
5758 "fn a() {
5759 // «dog()ˇ»;
5760 cat();
5761 }"
5762 ));
5763
5764 // Multiple cursors on one line -> advance
5765 cx.set_state(indoc!(
5766 "fn a() {
5767 ˇdˇog();
5768 cat();
5769 }"
5770 ));
5771 cx.update_editor(|editor, cx| {
5772 editor.toggle_comments(toggle_comments, cx);
5773 });
5774 cx.assert_editor_state(indoc!(
5775 "fn a() {
5776 // dog();
5777 catˇ(ˇ);
5778 }"
5779 ));
5780
5781 // Multiple cursors on one line, with selection -> don't advance
5782 cx.set_state(indoc!(
5783 "fn a() {
5784 ˇdˇog«()ˇ»;
5785 cat();
5786 }"
5787 ));
5788 cx.update_editor(|editor, cx| {
5789 editor.toggle_comments(toggle_comments, cx);
5790 });
5791 cx.assert_editor_state(indoc!(
5792 "fn a() {
5793 // ˇdˇog«()ˇ»;
5794 cat();
5795 }"
5796 ));
5797
5798 // Single cursor on one line -> advance
5799 // Cursor moves to column 0 on blank line
5800 cx.set_state(indoc!(
5801 "fn a() {
5802 ˇdog();
5803
5804 cat();
5805 }"
5806 ));
5807 cx.update_editor(|editor, cx| {
5808 editor.toggle_comments(toggle_comments, cx);
5809 });
5810 cx.assert_editor_state(indoc!(
5811 "fn a() {
5812 // dog();
5813 ˇ
5814 cat();
5815 }"
5816 ));
5817
5818 // Single cursor on one line -> advance
5819 // Cursor starts and ends at column 0
5820 cx.set_state(indoc!(
5821 "fn a() {
5822 ˇ dog();
5823 cat();
5824 }"
5825 ));
5826 cx.update_editor(|editor, cx| {
5827 editor.toggle_comments(toggle_comments, cx);
5828 });
5829 cx.assert_editor_state(indoc!(
5830 "fn a() {
5831 // dog();
5832 ˇ cat();
5833 }"
5834 ));
5835}
5836
5837#[gpui::test]
5838async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5839 init_test(cx, |_| {});
5840
5841 let mut cx = EditorTestContext::new(cx).await;
5842
5843 let html_language = Arc::new(
5844 Language::new(
5845 LanguageConfig {
5846 name: "HTML".into(),
5847 block_comment: Some(("<!-- ".into(), " -->".into())),
5848 ..Default::default()
5849 },
5850 Some(tree_sitter_html::language()),
5851 )
5852 .with_injection_query(
5853 r#"
5854 (script_element
5855 (raw_text) @content
5856 (#set! "language" "javascript"))
5857 "#,
5858 )
5859 .unwrap(),
5860 );
5861
5862 let javascript_language = Arc::new(Language::new(
5863 LanguageConfig {
5864 name: "JavaScript".into(),
5865 line_comment: Some("// ".into()),
5866 ..Default::default()
5867 },
5868 Some(tree_sitter_typescript::language_tsx()),
5869 ));
5870
5871 let registry = Arc::new(LanguageRegistry::test());
5872 registry.add(html_language.clone());
5873 registry.add(javascript_language.clone());
5874
5875 cx.update_buffer(|buffer, cx| {
5876 buffer.set_language_registry(registry);
5877 buffer.set_language(Some(html_language), cx);
5878 });
5879
5880 // Toggle comments for empty selections
5881 cx.set_state(
5882 &r#"
5883 <p>A</p>ˇ
5884 <p>B</p>ˇ
5885 <p>C</p>ˇ
5886 "#
5887 .unindent(),
5888 );
5889 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5890 cx.assert_editor_state(
5891 &r#"
5892 <!-- <p>A</p>ˇ -->
5893 <!-- <p>B</p>ˇ -->
5894 <!-- <p>C</p>ˇ -->
5895 "#
5896 .unindent(),
5897 );
5898 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5899 cx.assert_editor_state(
5900 &r#"
5901 <p>A</p>ˇ
5902 <p>B</p>ˇ
5903 <p>C</p>ˇ
5904 "#
5905 .unindent(),
5906 );
5907
5908 // Toggle comments for mixture of empty and non-empty selections, where
5909 // multiple selections occupy a given line.
5910 cx.set_state(
5911 &r#"
5912 <p>A«</p>
5913 <p>ˇ»B</p>ˇ
5914 <p>C«</p>
5915 <p>ˇ»D</p>ˇ
5916 "#
5917 .unindent(),
5918 );
5919
5920 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5921 cx.assert_editor_state(
5922 &r#"
5923 <!-- <p>A«</p>
5924 <p>ˇ»B</p>ˇ -->
5925 <!-- <p>C«</p>
5926 <p>ˇ»D</p>ˇ -->
5927 "#
5928 .unindent(),
5929 );
5930 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5931 cx.assert_editor_state(
5932 &r#"
5933 <p>A«</p>
5934 <p>ˇ»B</p>ˇ
5935 <p>C«</p>
5936 <p>ˇ»D</p>ˇ
5937 "#
5938 .unindent(),
5939 );
5940
5941 // Toggle comments when different languages are active for different
5942 // selections.
5943 cx.set_state(
5944 &r#"
5945 ˇ<script>
5946 ˇvar x = new Y();
5947 ˇ</script>
5948 "#
5949 .unindent(),
5950 );
5951 cx.executor().run_until_parked();
5952 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5953 cx.assert_editor_state(
5954 &r#"
5955 <!-- ˇ<script> -->
5956 // ˇvar x = new Y();
5957 <!-- ˇ</script> -->
5958 "#
5959 .unindent(),
5960 );
5961}
5962
5963#[gpui::test]
5964fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5965 init_test(cx, |_| {});
5966
5967 let buffer =
5968 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
5969 let multibuffer = cx.build_model(|cx| {
5970 let mut multibuffer = MultiBuffer::new(0);
5971 multibuffer.push_excerpts(
5972 buffer.clone(),
5973 [
5974 ExcerptRange {
5975 context: Point::new(0, 0)..Point::new(0, 4),
5976 primary: None,
5977 },
5978 ExcerptRange {
5979 context: Point::new(1, 0)..Point::new(1, 4),
5980 primary: None,
5981 },
5982 ],
5983 cx,
5984 );
5985 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5986 multibuffer
5987 });
5988
5989 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
5990 view.update(cx, |view, cx| {
5991 assert_eq!(view.text(cx), "aaaa\nbbbb");
5992 view.change_selections(None, cx, |s| {
5993 s.select_ranges([
5994 Point::new(0, 0)..Point::new(0, 0),
5995 Point::new(1, 0)..Point::new(1, 0),
5996 ])
5997 });
5998
5999 view.handle_input("X", cx);
6000 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6001 assert_eq!(
6002 view.selections.ranges(cx),
6003 [
6004 Point::new(0, 1)..Point::new(0, 1),
6005 Point::new(1, 1)..Point::new(1, 1),
6006 ]
6007 );
6008
6009 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6010 view.change_selections(None, cx, |s| {
6011 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6012 });
6013 view.backspace(&Default::default(), cx);
6014 assert_eq!(view.text(cx), "Xa\nbbb");
6015 assert_eq!(
6016 view.selections.ranges(cx),
6017 [Point::new(1, 0)..Point::new(1, 0)]
6018 );
6019
6020 view.change_selections(None, cx, |s| {
6021 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6022 });
6023 view.backspace(&Default::default(), cx);
6024 assert_eq!(view.text(cx), "X\nbb");
6025 assert_eq!(
6026 view.selections.ranges(cx),
6027 [Point::new(0, 1)..Point::new(0, 1)]
6028 );
6029 });
6030}
6031
6032#[gpui::test]
6033fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6034 init_test(cx, |_| {});
6035
6036 let markers = vec![('[', ']').into(), ('(', ')').into()];
6037 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6038 indoc! {"
6039 [aaaa
6040 (bbbb]
6041 cccc)",
6042 },
6043 markers.clone(),
6044 );
6045 let excerpt_ranges = markers.into_iter().map(|marker| {
6046 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6047 ExcerptRange {
6048 context,
6049 primary: None,
6050 }
6051 });
6052 let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
6053 let multibuffer = cx.build_model(|cx| {
6054 let mut multibuffer = MultiBuffer::new(0);
6055 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6056 multibuffer
6057 });
6058
6059 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6060 view.update(cx, |view, cx| {
6061 let (expected_text, selection_ranges) = marked_text_ranges(
6062 indoc! {"
6063 aaaa
6064 bˇbbb
6065 bˇbbˇb
6066 cccc"
6067 },
6068 true,
6069 );
6070 assert_eq!(view.text(cx), expected_text);
6071 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6072
6073 view.handle_input("X", cx);
6074
6075 let (expected_text, expected_selections) = marked_text_ranges(
6076 indoc! {"
6077 aaaa
6078 bXˇbbXb
6079 bXˇbbXˇb
6080 cccc"
6081 },
6082 false,
6083 );
6084 assert_eq!(view.text(cx), expected_text);
6085 assert_eq!(view.selections.ranges(cx), expected_selections);
6086
6087 view.newline(&Newline, cx);
6088 let (expected_text, expected_selections) = marked_text_ranges(
6089 indoc! {"
6090 aaaa
6091 bX
6092 ˇbbX
6093 b
6094 bX
6095 ˇbbX
6096 ˇb
6097 cccc"
6098 },
6099 false,
6100 );
6101 assert_eq!(view.text(cx), expected_text);
6102 assert_eq!(view.selections.ranges(cx), expected_selections);
6103 });
6104}
6105
6106#[gpui::test]
6107fn test_refresh_selections(cx: &mut TestAppContext) {
6108 init_test(cx, |_| {});
6109
6110 let buffer =
6111 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6112 let mut excerpt1_id = None;
6113 let multibuffer = cx.build_model(|cx| {
6114 let mut multibuffer = MultiBuffer::new(0);
6115 excerpt1_id = multibuffer
6116 .push_excerpts(
6117 buffer.clone(),
6118 [
6119 ExcerptRange {
6120 context: Point::new(0, 0)..Point::new(1, 4),
6121 primary: None,
6122 },
6123 ExcerptRange {
6124 context: Point::new(1, 0)..Point::new(2, 4),
6125 primary: None,
6126 },
6127 ],
6128 cx,
6129 )
6130 .into_iter()
6131 .next();
6132 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6133 multibuffer
6134 });
6135
6136 let editor = cx.add_window(|cx| {
6137 let mut editor = build_editor(multibuffer.clone(), cx);
6138 let snapshot = editor.snapshot(cx);
6139 editor.change_selections(None, cx, |s| {
6140 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6141 });
6142 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6143 assert_eq!(
6144 editor.selections.ranges(cx),
6145 [
6146 Point::new(1, 3)..Point::new(1, 3),
6147 Point::new(2, 1)..Point::new(2, 1),
6148 ]
6149 );
6150 editor
6151 });
6152
6153 // Refreshing selections is a no-op when excerpts haven't changed.
6154 editor.update(cx, |editor, cx| {
6155 editor.change_selections(None, cx, |s| s.refresh());
6156 assert_eq!(
6157 editor.selections.ranges(cx),
6158 [
6159 Point::new(1, 3)..Point::new(1, 3),
6160 Point::new(2, 1)..Point::new(2, 1),
6161 ]
6162 );
6163 });
6164
6165 multibuffer.update(cx, |multibuffer, cx| {
6166 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6167 });
6168 editor.update(cx, |editor, cx| {
6169 // Removing an excerpt causes the first selection to become degenerate.
6170 assert_eq!(
6171 editor.selections.ranges(cx),
6172 [
6173 Point::new(0, 0)..Point::new(0, 0),
6174 Point::new(0, 1)..Point::new(0, 1)
6175 ]
6176 );
6177
6178 // Refreshing selections will relocate the first selection to the original buffer
6179 // location.
6180 editor.change_selections(None, cx, |s| s.refresh());
6181 assert_eq!(
6182 editor.selections.ranges(cx),
6183 [
6184 Point::new(0, 1)..Point::new(0, 1),
6185 Point::new(0, 3)..Point::new(0, 3)
6186 ]
6187 );
6188 assert!(editor.selections.pending_anchor().is_some());
6189 });
6190}
6191
6192#[gpui::test]
6193fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6194 init_test(cx, |_| {});
6195
6196 let buffer =
6197 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6198 let mut excerpt1_id = None;
6199 let multibuffer = cx.build_model(|cx| {
6200 let mut multibuffer = MultiBuffer::new(0);
6201 excerpt1_id = multibuffer
6202 .push_excerpts(
6203 buffer.clone(),
6204 [
6205 ExcerptRange {
6206 context: Point::new(0, 0)..Point::new(1, 4),
6207 primary: None,
6208 },
6209 ExcerptRange {
6210 context: Point::new(1, 0)..Point::new(2, 4),
6211 primary: None,
6212 },
6213 ],
6214 cx,
6215 )
6216 .into_iter()
6217 .next();
6218 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6219 multibuffer
6220 });
6221
6222 let editor = cx.add_window(|cx| {
6223 let mut editor = build_editor(multibuffer.clone(), cx);
6224 let snapshot = editor.snapshot(cx);
6225 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6226 assert_eq!(
6227 editor.selections.ranges(cx),
6228 [Point::new(1, 3)..Point::new(1, 3)]
6229 );
6230 editor
6231 });
6232
6233 multibuffer.update(cx, |multibuffer, cx| {
6234 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6235 });
6236 editor.update(cx, |editor, cx| {
6237 assert_eq!(
6238 editor.selections.ranges(cx),
6239 [Point::new(0, 0)..Point::new(0, 0)]
6240 );
6241
6242 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6243 editor.change_selections(None, cx, |s| s.refresh());
6244 assert_eq!(
6245 editor.selections.ranges(cx),
6246 [Point::new(0, 3)..Point::new(0, 3)]
6247 );
6248 assert!(editor.selections.pending_anchor().is_some());
6249 });
6250}
6251
6252#[gpui::test]
6253async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6254 init_test(cx, |_| {});
6255
6256 let language = Arc::new(
6257 Language::new(
6258 LanguageConfig {
6259 brackets: BracketPairConfig {
6260 pairs: vec![
6261 BracketPair {
6262 start: "{".to_string(),
6263 end: "}".to_string(),
6264 close: true,
6265 newline: true,
6266 },
6267 BracketPair {
6268 start: "/* ".to_string(),
6269 end: " */".to_string(),
6270 close: true,
6271 newline: true,
6272 },
6273 ],
6274 ..Default::default()
6275 },
6276 ..Default::default()
6277 },
6278 Some(tree_sitter_rust::language()),
6279 )
6280 .with_indents_query("")
6281 .unwrap(),
6282 );
6283
6284 let text = concat!(
6285 "{ }\n", //
6286 " x\n", //
6287 " /* */\n", //
6288 "x\n", //
6289 "{{} }\n", //
6290 );
6291
6292 let buffer = cx.build_model(|cx| {
6293 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
6294 });
6295 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
6296 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6297 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6298 .await;
6299
6300 view.update(cx, |view, cx| {
6301 view.change_selections(None, cx, |s| {
6302 s.select_display_ranges([
6303 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6304 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6305 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6306 ])
6307 });
6308 view.newline(&Newline, cx);
6309
6310 assert_eq!(
6311 view.buffer().read(cx).read(cx).text(),
6312 concat!(
6313 "{ \n", // Suppress rustfmt
6314 "\n", //
6315 "}\n", //
6316 " x\n", //
6317 " /* \n", //
6318 " \n", //
6319 " */\n", //
6320 "x\n", //
6321 "{{} \n", //
6322 "}\n", //
6323 )
6324 );
6325 });
6326}
6327
6328//todo!(finish editor tests)
6329// #[gpui::test]
6330// fn test_highlighted_ranges(cx: &mut TestAppContext) {
6331// init_test(cx, |_| {});
6332
6333// let editor = cx.add_window(|cx| {
6334// let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6335// build_editor(buffer.clone(), cx)
6336// });
6337
6338// editor.update(cx, |editor, cx| {
6339// struct Type1;
6340// struct Type2;
6341
6342// let buffer = editor.buffer.read(cx).snapshot(cx);
6343
6344// let anchor_range =
6345// |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6346
6347// editor.highlight_background::<Type1>(
6348// vec![
6349// anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6350// anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6351// anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6352// anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6353// ],
6354// |_| Hsla::red(),
6355// cx,
6356// );
6357// editor.highlight_background::<Type2>(
6358// vec![
6359// anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6360// anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6361// anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6362// anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6363// ],
6364// |_| Hsla::green(),
6365// cx,
6366// );
6367
6368// let snapshot = editor.snapshot(cx);
6369// let mut highlighted_ranges = editor.background_highlights_in_range(
6370// anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6371// &snapshot,
6372// cx.theme().colors(),
6373// );
6374// // Enforce a consistent ordering based on color without relying on the ordering of the
6375// // highlight's `TypeId` which is non-executor.
6376// highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6377// assert_eq!(
6378// highlighted_ranges,
6379// &[
6380// (
6381// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6382// Hsla::green(),
6383// ),
6384// (
6385// DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6386// Hsla::green(),
6387// ),
6388// (
6389// DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6390// Hsla::red(),
6391// ),
6392// (
6393// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6394// Hsla::red(),
6395// ),
6396// ]
6397// );
6398// assert_eq!(
6399// editor.background_highlights_in_range(
6400// anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6401// &snapshot,
6402// cx.theme().colors(),
6403// ),
6404// &[(
6405// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6406// Hsla::red(),
6407// )]
6408// );
6409// });
6410// }
6411
6412// todo!(following)
6413// #[gpui::test]
6414// async fn test_following(cx: &mut gpui::TestAppContext) {
6415// init_test(cx, |_| {});
6416
6417// let fs = FakeFs::new(cx.executor());
6418// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6419
6420// let buffer = project.update(cx, |project, cx| {
6421// let buffer = project
6422// .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6423// .unwrap();
6424// cx.build_model(|cx| MultiBuffer::singleton(buffer, cx))
6425// });
6426// let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6427// let follower = cx.update(|cx| {
6428// cx.open_window(
6429// WindowOptions {
6430// bounds: WindowBounds::Fixed(Bounds::from_corners(
6431// gpui::Point::new((0. as f64).into(), (0. as f64).into()),
6432// gpui::Point::new((10. as f64).into(), (80. as f64).into()),
6433// )),
6434// ..Default::default()
6435// },
6436// |cx| cx.build_view(|cx| build_editor(buffer.clone(), cx)),
6437// )
6438// });
6439
6440// let is_still_following = Rc::new(RefCell::new(true));
6441// let follower_edit_event_count = Rc::new(RefCell::new(0));
6442// let pending_update = Rc::new(RefCell::new(None));
6443// follower.update(cx, {
6444// let update = pending_update.clone();
6445// let is_still_following = is_still_following.clone();
6446// let follower_edit_event_count = follower_edit_event_count.clone();
6447// |_, cx| {
6448// cx.subscribe(
6449// &leader.root_view(cx).unwrap(),
6450// move |_, leader, event, cx| {
6451// leader
6452// .read(cx)
6453// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6454// },
6455// )
6456// .detach();
6457
6458// cx.subscribe(
6459// &follower.root_view(cx).unwrap(),
6460// move |_, _, event: &Event, cx| {
6461// if matches!(event.to_follow_event(), Some(FollowEvent::Unfollow)) {
6462// *is_still_following.borrow_mut() = false;
6463// }
6464
6465// if let Event::BufferEdited = event {
6466// *follower_edit_event_count.borrow_mut() += 1;
6467// }
6468// },
6469// )
6470// .detach();
6471// }
6472// });
6473
6474// // Update the selections only
6475// leader.update(cx, |leader, cx| {
6476// leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6477// });
6478// follower
6479// .update(cx, |follower, cx| {
6480// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6481// })
6482// .unwrap()
6483// .await
6484// .unwrap();
6485// follower.update(cx, |follower, cx| {
6486// assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6487// });
6488// assert_eq!(*is_still_following.borrow(), true);
6489// assert_eq!(*follower_edit_event_count.borrow(), 0);
6490
6491// // Update the scroll position only
6492// leader.update(cx, |leader, cx| {
6493// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6494// });
6495// follower
6496// .update(cx, |follower, cx| {
6497// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6498// })
6499// .unwrap()
6500// .await
6501// .unwrap();
6502// assert_eq!(
6503// follower
6504// .update(cx, |follower, cx| follower.scroll_position(cx))
6505// .unwrap(),
6506// gpui::Point::new(1.5, 3.5)
6507// );
6508// assert_eq!(*is_still_following.borrow(), true);
6509// assert_eq!(*follower_edit_event_count.borrow(), 0);
6510
6511// // Update the selections and scroll position. The follower's scroll position is updated
6512// // via autoscroll, not via the leader's exact scroll position.
6513// leader.update(cx, |leader, cx| {
6514// leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6515// leader.request_autoscroll(Autoscroll::newest(), cx);
6516// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6517// });
6518// follower
6519// .update(cx, |follower, cx| {
6520// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6521// })
6522// .unwrap()
6523// .await
6524// .unwrap();
6525// follower.update(cx, |follower, cx| {
6526// assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
6527// assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6528// });
6529// assert_eq!(*is_still_following.borrow(), true);
6530
6531// // Creating a pending selection that precedes another selection
6532// leader.update(cx, |leader, cx| {
6533// leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6534// leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6535// });
6536// follower
6537// .update(cx, |follower, cx| {
6538// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6539// })
6540// .unwrap()
6541// .await
6542// .unwrap();
6543// follower.update(cx, |follower, cx| {
6544// assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6545// });
6546// assert_eq!(*is_still_following.borrow(), true);
6547
6548// // Extend the pending selection so that it surrounds another selection
6549// leader.update(cx, |leader, cx| {
6550// leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6551// });
6552// follower
6553// .update(cx, |follower, cx| {
6554// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6555// })
6556// .unwrap()
6557// .await
6558// .unwrap();
6559// follower.update(cx, |follower, cx| {
6560// assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6561// });
6562
6563// // Scrolling locally breaks the follow
6564// follower.update(cx, |follower, cx| {
6565// let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6566// follower.set_scroll_anchor(
6567// ScrollAnchor {
6568// anchor: top_anchor,
6569// offset: gpui::Point::new(0.0, 0.5),
6570// },
6571// cx,
6572// );
6573// });
6574// assert_eq!(*is_still_following.borrow(), false);
6575// }
6576
6577// #[gpui::test]
6578// async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6579// init_test(cx, |_| {});
6580
6581// let fs = FakeFs::new(cx.executor());
6582// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6583// let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6584// let pane = workspace
6585// .update(cx, |workspace, _| workspace.active_pane().clone())
6586// .unwrap();
6587
6588// let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6589
6590// let leader = pane.update(cx, |_, cx| {
6591// let multibuffer = cx.build_model(|_| MultiBuffer::new(0));
6592// cx.build_view(|cx| build_editor(multibuffer.clone(), cx))
6593// });
6594
6595// // Start following the editor when it has no excerpts.
6596// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6597// let follower_1 = cx
6598// .update(|cx| {
6599// Editor::from_state_proto(
6600// pane.clone(),
6601// workspace.root_view(cx).unwrap(),
6602// ViewId {
6603// creator: Default::default(),
6604// id: 0,
6605// },
6606// &mut state_message,
6607// cx,
6608// )
6609// })
6610// .unwrap()
6611// .await
6612// .unwrap();
6613
6614// let update_message = Rc::new(RefCell::new(None));
6615// follower_1.update(cx, {
6616// let update = update_message.clone();
6617// |_, cx| {
6618// cx.subscribe(&leader, move |_, leader, event, cx| {
6619// leader
6620// .read(cx)
6621// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6622// })
6623// .detach();
6624// }
6625// });
6626
6627// let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6628// (
6629// project
6630// .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6631// .unwrap(),
6632// project
6633// .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6634// .unwrap(),
6635// )
6636// });
6637
6638// // Insert some excerpts.
6639// leader.update(cx, |leader, cx| {
6640// leader.buffer.update(cx, |multibuffer, cx| {
6641// let excerpt_ids = multibuffer.push_excerpts(
6642// buffer_1.clone(),
6643// [
6644// ExcerptRange {
6645// context: 1..6,
6646// primary: None,
6647// },
6648// ExcerptRange {
6649// context: 12..15,
6650// primary: None,
6651// },
6652// ExcerptRange {
6653// context: 0..3,
6654// primary: None,
6655// },
6656// ],
6657// cx,
6658// );
6659// multibuffer.insert_excerpts_after(
6660// excerpt_ids[0],
6661// buffer_2.clone(),
6662// [
6663// ExcerptRange {
6664// context: 8..12,
6665// primary: None,
6666// },
6667// ExcerptRange {
6668// context: 0..6,
6669// primary: None,
6670// },
6671// ],
6672// cx,
6673// );
6674// });
6675// });
6676
6677// // Apply the update of adding the excerpts.
6678// follower_1
6679// .update(cx, |follower, cx| {
6680// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6681// })
6682// .await
6683// .unwrap();
6684// assert_eq!(
6685// follower_1.update(cx, |editor, cx| editor.text(cx)),
6686// leader.update(cx, |editor, cx| editor.text(cx))
6687// );
6688// update_message.borrow_mut().take();
6689
6690// // Start following separately after it already has excerpts.
6691// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6692// let follower_2 = cx
6693// .update(|cx| {
6694// Editor::from_state_proto(
6695// pane.clone(),
6696// workspace.clone(),
6697// ViewId {
6698// creator: Default::default(),
6699// id: 0,
6700// },
6701// &mut state_message,
6702// cx,
6703// )
6704// })
6705// .unwrap()
6706// .await
6707// .unwrap();
6708// assert_eq!(
6709// follower_2.update(cx, |editor, cx| editor.text(cx)),
6710// leader.update(cx, |editor, cx| editor.text(cx))
6711// );
6712
6713// // Remove some excerpts.
6714// leader.update(cx, |leader, cx| {
6715// leader.buffer.update(cx, |multibuffer, cx| {
6716// let excerpt_ids = multibuffer.excerpt_ids();
6717// multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6718// multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6719// });
6720// });
6721
6722// // Apply the update of removing the excerpts.
6723// follower_1
6724// .update(cx, |follower, cx| {
6725// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6726// })
6727// .await
6728// .unwrap();
6729// follower_2
6730// .update(cx, |follower, cx| {
6731// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6732// })
6733// .await
6734// .unwrap();
6735// update_message.borrow_mut().take();
6736// assert_eq!(
6737// follower_1.update(cx, |editor, cx| editor.text(cx)),
6738// leader.update(cx, |editor, cx| editor.text(cx))
6739// );
6740// }
6741
6742#[gpui::test]
6743async fn go_to_prev_overlapping_diagnostic(
6744 executor: BackgroundExecutor,
6745 cx: &mut gpui::TestAppContext,
6746) {
6747 init_test(cx, |_| {});
6748
6749 let mut cx = EditorTestContext::new(cx).await;
6750 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
6751
6752 cx.set_state(indoc! {"
6753 ˇfn func(abc def: i32) -> u32 {
6754 }
6755 "});
6756
6757 cx.update(|cx| {
6758 project.update(cx, |project, cx| {
6759 project
6760 .update_diagnostics(
6761 LanguageServerId(0),
6762 lsp::PublishDiagnosticsParams {
6763 uri: lsp::Url::from_file_path("/root/file").unwrap(),
6764 version: None,
6765 diagnostics: vec![
6766 lsp::Diagnostic {
6767 range: lsp::Range::new(
6768 lsp::Position::new(0, 11),
6769 lsp::Position::new(0, 12),
6770 ),
6771 severity: Some(lsp::DiagnosticSeverity::ERROR),
6772 ..Default::default()
6773 },
6774 lsp::Diagnostic {
6775 range: lsp::Range::new(
6776 lsp::Position::new(0, 12),
6777 lsp::Position::new(0, 15),
6778 ),
6779 severity: Some(lsp::DiagnosticSeverity::ERROR),
6780 ..Default::default()
6781 },
6782 lsp::Diagnostic {
6783 range: lsp::Range::new(
6784 lsp::Position::new(0, 25),
6785 lsp::Position::new(0, 28),
6786 ),
6787 severity: Some(lsp::DiagnosticSeverity::ERROR),
6788 ..Default::default()
6789 },
6790 ],
6791 },
6792 &[],
6793 cx,
6794 )
6795 .unwrap()
6796 });
6797 });
6798
6799 executor.run_until_parked();
6800
6801 cx.update_editor(|editor, cx| {
6802 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6803 });
6804
6805 cx.assert_editor_state(indoc! {"
6806 fn func(abc def: i32) -> ˇu32 {
6807 }
6808 "});
6809
6810 cx.update_editor(|editor, cx| {
6811 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6812 });
6813
6814 cx.assert_editor_state(indoc! {"
6815 fn func(abc ˇdef: i32) -> u32 {
6816 }
6817 "});
6818
6819 cx.update_editor(|editor, cx| {
6820 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6821 });
6822
6823 cx.assert_editor_state(indoc! {"
6824 fn func(abcˇ def: i32) -> u32 {
6825 }
6826 "});
6827
6828 cx.update_editor(|editor, cx| {
6829 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6830 });
6831
6832 cx.assert_editor_state(indoc! {"
6833 fn func(abc def: i32) -> ˇu32 {
6834 }
6835 "});
6836}
6837
6838#[gpui::test]
6839async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
6840 init_test(cx, |_| {});
6841
6842 let mut cx = EditorTestContext::new(cx).await;
6843
6844 let diff_base = r#"
6845 use some::mod;
6846
6847 const A: u32 = 42;
6848
6849 fn main() {
6850 println!("hello");
6851
6852 println!("world");
6853 }
6854 "#
6855 .unindent();
6856
6857 // Edits are modified, removed, modified, added
6858 cx.set_state(
6859 &r#"
6860 use some::modified;
6861
6862 ˇ
6863 fn main() {
6864 println!("hello there");
6865
6866 println!("around the");
6867 println!("world");
6868 }
6869 "#
6870 .unindent(),
6871 );
6872
6873 cx.set_diff_base(Some(&diff_base));
6874 executor.run_until_parked();
6875
6876 cx.update_editor(|editor, cx| {
6877 //Wrap around the bottom of the buffer
6878 for _ in 0..3 {
6879 editor.go_to_hunk(&GoToHunk, cx);
6880 }
6881 });
6882
6883 cx.assert_editor_state(
6884 &r#"
6885 ˇuse some::modified;
6886
6887
6888 fn main() {
6889 println!("hello there");
6890
6891 println!("around the");
6892 println!("world");
6893 }
6894 "#
6895 .unindent(),
6896 );
6897
6898 cx.update_editor(|editor, cx| {
6899 //Wrap around the top of the buffer
6900 for _ in 0..2 {
6901 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6902 }
6903 });
6904
6905 cx.assert_editor_state(
6906 &r#"
6907 use some::modified;
6908
6909
6910 fn main() {
6911 ˇ println!("hello there");
6912
6913 println!("around the");
6914 println!("world");
6915 }
6916 "#
6917 .unindent(),
6918 );
6919
6920 cx.update_editor(|editor, cx| {
6921 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6922 });
6923
6924 cx.assert_editor_state(
6925 &r#"
6926 use some::modified;
6927
6928 ˇ
6929 fn main() {
6930 println!("hello there");
6931
6932 println!("around the");
6933 println!("world");
6934 }
6935 "#
6936 .unindent(),
6937 );
6938
6939 cx.update_editor(|editor, cx| {
6940 for _ in 0..3 {
6941 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6942 }
6943 });
6944
6945 cx.assert_editor_state(
6946 &r#"
6947 use some::modified;
6948
6949
6950 fn main() {
6951 ˇ println!("hello there");
6952
6953 println!("around the");
6954 println!("world");
6955 }
6956 "#
6957 .unindent(),
6958 );
6959
6960 cx.update_editor(|editor, cx| {
6961 editor.fold(&Fold, cx);
6962
6963 //Make sure that the fold only gets one hunk
6964 for _ in 0..4 {
6965 editor.go_to_hunk(&GoToHunk, cx);
6966 }
6967 });
6968
6969 cx.assert_editor_state(
6970 &r#"
6971 ˇuse some::modified;
6972
6973
6974 fn main() {
6975 println!("hello there");
6976
6977 println!("around the");
6978 println!("world");
6979 }
6980 "#
6981 .unindent(),
6982 );
6983}
6984
6985#[test]
6986fn test_split_words() {
6987 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6988 split_words(text).collect()
6989 }
6990
6991 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6992 assert_eq!(split("hello_world"), &["hello_", "world"]);
6993 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6994 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6995 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6996 assert_eq!(split("helloworld"), &["helloworld"]);
6997}
6998
6999#[gpui::test]
7000async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7001 init_test(cx, |_| {});
7002
7003 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7004 let mut assert = |before, after| {
7005 let _state_context = cx.set_state(before);
7006 cx.update_editor(|editor, cx| {
7007 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7008 });
7009 cx.assert_editor_state(after);
7010 };
7011
7012 // Outside bracket jumps to outside of matching bracket
7013 assert("console.logˇ(var);", "console.log(var)ˇ;");
7014 assert("console.log(var)ˇ;", "console.logˇ(var);");
7015
7016 // Inside bracket jumps to inside of matching bracket
7017 assert("console.log(ˇvar);", "console.log(varˇ);");
7018 assert("console.log(varˇ);", "console.log(ˇvar);");
7019
7020 // When outside a bracket and inside, favor jumping to the inside bracket
7021 assert(
7022 "console.log('foo', [1, 2, 3]ˇ);",
7023 "console.log(ˇ'foo', [1, 2, 3]);",
7024 );
7025 assert(
7026 "console.log(ˇ'foo', [1, 2, 3]);",
7027 "console.log('foo', [1, 2, 3]ˇ);",
7028 );
7029
7030 // Bias forward if two options are equally likely
7031 assert(
7032 "let result = curried_fun()ˇ();",
7033 "let result = curried_fun()()ˇ;",
7034 );
7035
7036 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7037 assert(
7038 indoc! {"
7039 function test() {
7040 console.log('test')ˇ
7041 }"},
7042 indoc! {"
7043 function test() {
7044 console.logˇ('test')
7045 }"},
7046 );
7047}
7048
7049// todo!(completions)
7050// #[gpui::test(iterations = 10)]
7051// async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7052// init_test(cx, |_| {});
7053
7054// let (copilot, copilot_lsp) = Copilot::fake(cx);
7055// cx.update(|cx| cx.set_global(copilot));
7056// let mut cx = EditorLspTestContext::new_rust(
7057// lsp::ServerCapabilities {
7058// completion_provider: Some(lsp::CompletionOptions {
7059// trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7060// ..Default::default()
7061// }),
7062// ..Default::default()
7063// },
7064// cx,
7065// )
7066// .await;
7067
7068// // When inserting, ensure autocompletion is favored over Copilot suggestions.
7069// cx.set_state(indoc! {"
7070// oneˇ
7071// two
7072// three
7073// "});
7074// cx.simulate_keystroke(".");
7075// let _ = handle_completion_request(
7076// &mut cx,
7077// indoc! {"
7078// one.|<>
7079// two
7080// three
7081// "},
7082// vec!["completion_a", "completion_b"],
7083// );
7084// handle_copilot_completion_request(
7085// &copilot_lsp,
7086// vec![copilot::request::Completion {
7087// text: "one.copilot1".into(),
7088// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7089// ..Default::default()
7090// }],
7091// vec![],
7092// );
7093// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7094// cx.update_editor(|editor, cx| {
7095// assert!(editor.context_menu_visible());
7096// assert!(!editor.has_active_copilot_suggestion(cx));
7097
7098// // Confirming a completion inserts it and hides the context menu, without showing
7099// // the copilot suggestion afterwards.
7100// editor
7101// .confirm_completion(&Default::default(), cx)
7102// .unwrap()
7103// .detach();
7104// assert!(!editor.context_menu_visible());
7105// assert!(!editor.has_active_copilot_suggestion(cx));
7106// assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
7107// assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
7108// });
7109
7110// // Ensure Copilot suggestions are shown right away if no autocompletion is available.
7111// cx.set_state(indoc! {"
7112// oneˇ
7113// two
7114// three
7115// "});
7116// cx.simulate_keystroke(".");
7117// let _ = handle_completion_request(
7118// &mut cx,
7119// indoc! {"
7120// one.|<>
7121// two
7122// three
7123// "},
7124// vec![],
7125// );
7126// handle_copilot_completion_request(
7127// &copilot_lsp,
7128// vec![copilot::request::Completion {
7129// text: "one.copilot1".into(),
7130// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7131// ..Default::default()
7132// }],
7133// vec![],
7134// );
7135// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7136// cx.update_editor(|editor, cx| {
7137// assert!(!editor.context_menu_visible());
7138// assert!(editor.has_active_copilot_suggestion(cx));
7139// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7140// assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7141// });
7142
7143// // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
7144// cx.set_state(indoc! {"
7145// oneˇ
7146// two
7147// three
7148// "});
7149// cx.simulate_keystroke(".");
7150// let _ = handle_completion_request(
7151// &mut cx,
7152// indoc! {"
7153// one.|<>
7154// two
7155// three
7156// "},
7157// vec!["completion_a", "completion_b"],
7158// );
7159// handle_copilot_completion_request(
7160// &copilot_lsp,
7161// vec![copilot::request::Completion {
7162// text: "one.copilot1".into(),
7163// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7164// ..Default::default()
7165// }],
7166// vec![],
7167// );
7168// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7169// cx.update_editor(|editor, cx| {
7170// assert!(editor.context_menu_visible());
7171// assert!(!editor.has_active_copilot_suggestion(cx));
7172
7173// // When hiding the context menu, the Copilot suggestion becomes visible.
7174// editor.hide_context_menu(cx);
7175// assert!(!editor.context_menu_visible());
7176// assert!(editor.has_active_copilot_suggestion(cx));
7177// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7178// assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7179// });
7180
7181// // Ensure existing completion is interpolated when inserting again.
7182// cx.simulate_keystroke("c");
7183// executor.run_until_parked();
7184// cx.update_editor(|editor, cx| {
7185// assert!(!editor.context_menu_visible());
7186// assert!(editor.has_active_copilot_suggestion(cx));
7187// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7188// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7189// });
7190
7191// // After debouncing, new Copilot completions should be requested.
7192// handle_copilot_completion_request(
7193// &copilot_lsp,
7194// vec![copilot::request::Completion {
7195// text: "one.copilot2".into(),
7196// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7197// ..Default::default()
7198// }],
7199// vec![],
7200// );
7201// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7202// cx.update_editor(|editor, cx| {
7203// assert!(!editor.context_menu_visible());
7204// assert!(editor.has_active_copilot_suggestion(cx));
7205// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7206// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7207
7208// // Canceling should remove the active Copilot suggestion.
7209// editor.cancel(&Default::default(), cx);
7210// assert!(!editor.has_active_copilot_suggestion(cx));
7211// assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7212// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7213
7214// // After canceling, tabbing shouldn't insert the previously shown suggestion.
7215// editor.tab(&Default::default(), cx);
7216// assert!(!editor.has_active_copilot_suggestion(cx));
7217// assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
7218// assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
7219
7220// // When undoing the previously active suggestion is shown again.
7221// editor.undo(&Default::default(), cx);
7222// assert!(editor.has_active_copilot_suggestion(cx));
7223// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7224// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7225// });
7226
7227// // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7228// cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7229// cx.update_editor(|editor, cx| {
7230// assert!(editor.has_active_copilot_suggestion(cx));
7231// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7232// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7233
7234// // Tabbing when there is an active suggestion inserts it.
7235// editor.tab(&Default::default(), cx);
7236// assert!(!editor.has_active_copilot_suggestion(cx));
7237// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7238// assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7239
7240// // When undoing the previously active suggestion is shown again.
7241// editor.undo(&Default::default(), cx);
7242// assert!(editor.has_active_copilot_suggestion(cx));
7243// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7244// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7245
7246// // Hide suggestion.
7247// editor.cancel(&Default::default(), cx);
7248// assert!(!editor.has_active_copilot_suggestion(cx));
7249// assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7250// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7251// });
7252
7253// // If an edit occurs outside of this editor but no suggestion is being shown,
7254// // we won't make it visible.
7255// cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7256// cx.update_editor(|editor, cx| {
7257// assert!(!editor.has_active_copilot_suggestion(cx));
7258// assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7259// assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7260// });
7261
7262// // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7263// cx.update_editor(|editor, cx| {
7264// editor.set_text("fn foo() {\n \n}", cx);
7265// editor.change_selections(None, cx, |s| {
7266// s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7267// });
7268// });
7269// handle_copilot_completion_request(
7270// &copilot_lsp,
7271// vec![copilot::request::Completion {
7272// text: " let x = 4;".into(),
7273// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7274// ..Default::default()
7275// }],
7276// vec![],
7277// );
7278
7279// cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7280// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7281// cx.update_editor(|editor, cx| {
7282// assert!(editor.has_active_copilot_suggestion(cx));
7283// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7284// assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7285
7286// // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7287// editor.tab(&Default::default(), cx);
7288// assert!(editor.has_active_copilot_suggestion(cx));
7289// assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7290// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7291
7292// // Tabbing again accepts the suggestion.
7293// editor.tab(&Default::default(), cx);
7294// assert!(!editor.has_active_copilot_suggestion(cx));
7295// assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7296// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7297// });
7298// }
7299
7300#[gpui::test]
7301async fn test_copilot_completion_invalidation(
7302 executor: BackgroundExecutor,
7303 cx: &mut gpui::TestAppContext,
7304) {
7305 init_test(cx, |_| {});
7306
7307 let (copilot, copilot_lsp) = Copilot::fake(cx);
7308 cx.update(|cx| cx.set_global(copilot));
7309 let mut cx = EditorLspTestContext::new_rust(
7310 lsp::ServerCapabilities {
7311 completion_provider: Some(lsp::CompletionOptions {
7312 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7313 ..Default::default()
7314 }),
7315 ..Default::default()
7316 },
7317 cx,
7318 )
7319 .await;
7320
7321 cx.set_state(indoc! {"
7322 one
7323 twˇ
7324 three
7325 "});
7326
7327 handle_copilot_completion_request(
7328 &copilot_lsp,
7329 vec![copilot::request::Completion {
7330 text: "two.foo()".into(),
7331 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7332 ..Default::default()
7333 }],
7334 vec![],
7335 );
7336 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7337 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7338 cx.update_editor(|editor, cx| {
7339 assert!(editor.has_active_copilot_suggestion(cx));
7340 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7341 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7342
7343 editor.backspace(&Default::default(), cx);
7344 assert!(editor.has_active_copilot_suggestion(cx));
7345 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7346 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7347
7348 editor.backspace(&Default::default(), cx);
7349 assert!(editor.has_active_copilot_suggestion(cx));
7350 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7351 assert_eq!(editor.text(cx), "one\n\nthree\n");
7352
7353 // Deleting across the original suggestion range invalidates it.
7354 editor.backspace(&Default::default(), cx);
7355 assert!(!editor.has_active_copilot_suggestion(cx));
7356 assert_eq!(editor.display_text(cx), "one\nthree\n");
7357 assert_eq!(editor.text(cx), "one\nthree\n");
7358
7359 // Undoing the deletion restores the suggestion.
7360 editor.undo(&Default::default(), cx);
7361 assert!(editor.has_active_copilot_suggestion(cx));
7362 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7363 assert_eq!(editor.text(cx), "one\n\nthree\n");
7364 });
7365}
7366
7367//todo!()
7368// #[gpui::test]
7369// async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7370// init_test(cx, |_| {});
7371
7372// let (copilot, copilot_lsp) = Copilot::fake(cx);
7373// cx.update(|cx| cx.set_global(copilot));
7374
7375// let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
7376// let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
7377// let multibuffer = cx.build_model(|cx| {
7378// let mut multibuffer = MultiBuffer::new(0);
7379// multibuffer.push_excerpts(
7380// buffer_1.clone(),
7381// [ExcerptRange {
7382// context: Point::new(0, 0)..Point::new(2, 0),
7383// primary: None,
7384// }],
7385// cx,
7386// );
7387// multibuffer.push_excerpts(
7388// buffer_2.clone(),
7389// [ExcerptRange {
7390// context: Point::new(0, 0)..Point::new(2, 0),
7391// primary: None,
7392// }],
7393// cx,
7394// );
7395// multibuffer
7396// });
7397// let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7398
7399// handle_copilot_completion_request(
7400// &copilot_lsp,
7401// vec![copilot::request::Completion {
7402// text: "b = 2 + a".into(),
7403// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7404// ..Default::default()
7405// }],
7406// vec![],
7407// );
7408// editor.update(cx, |editor, cx| {
7409// // Ensure copilot suggestions are shown for the first excerpt.
7410// editor.change_selections(None, cx, |s| {
7411// s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7412// });
7413// editor.next_copilot_suggestion(&Default::default(), cx);
7414// });
7415// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7416// editor.update(cx, |editor, cx| {
7417// assert!(editor.has_active_copilot_suggestion(cx));
7418// assert_eq!(
7419// editor.display_text(cx),
7420// "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7421// );
7422// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7423// });
7424
7425// handle_copilot_completion_request(
7426// &copilot_lsp,
7427// vec![copilot::request::Completion {
7428// text: "d = 4 + c".into(),
7429// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7430// ..Default::default()
7431// }],
7432// vec![],
7433// );
7434// editor.update(cx, |editor, cx| {
7435// // Move to another excerpt, ensuring the suggestion gets cleared.
7436// editor.change_selections(None, cx, |s| {
7437// s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7438// });
7439// assert!(!editor.has_active_copilot_suggestion(cx));
7440// assert_eq!(
7441// editor.display_text(cx),
7442// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7443// );
7444// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7445
7446// // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7447// editor.handle_input(" ", cx);
7448// assert!(!editor.has_active_copilot_suggestion(cx));
7449// assert_eq!(
7450// editor.display_text(cx),
7451// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7452// );
7453// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7454// });
7455
7456// // Ensure the new suggestion is displayed when the debounce timeout expires.
7457// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7458// editor.update(cx, |editor, cx| {
7459// assert!(editor.has_active_copilot_suggestion(cx));
7460// assert_eq!(
7461// editor.display_text(cx),
7462// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7463// );
7464// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7465// });
7466// }
7467
7468#[gpui::test]
7469async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7470 init_test(cx, |settings| {
7471 settings
7472 .copilot
7473 .get_or_insert(Default::default())
7474 .disabled_globs = Some(vec![".env*".to_string()]);
7475 });
7476
7477 let (copilot, copilot_lsp) = Copilot::fake(cx);
7478 cx.update(|cx| cx.set_global(copilot));
7479
7480 let fs = FakeFs::new(cx.executor());
7481 fs.insert_tree(
7482 "/test",
7483 json!({
7484 ".env": "SECRET=something\n",
7485 "README.md": "hello\n"
7486 }),
7487 )
7488 .await;
7489 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7490
7491 let private_buffer = project
7492 .update(cx, |project, cx| {
7493 project.open_local_buffer("/test/.env", cx)
7494 })
7495 .await
7496 .unwrap();
7497 let public_buffer = project
7498 .update(cx, |project, cx| {
7499 project.open_local_buffer("/test/README.md", cx)
7500 })
7501 .await
7502 .unwrap();
7503
7504 let multibuffer = cx.build_model(|cx| {
7505 let mut multibuffer = MultiBuffer::new(0);
7506 multibuffer.push_excerpts(
7507 private_buffer.clone(),
7508 [ExcerptRange {
7509 context: Point::new(0, 0)..Point::new(1, 0),
7510 primary: None,
7511 }],
7512 cx,
7513 );
7514 multibuffer.push_excerpts(
7515 public_buffer.clone(),
7516 [ExcerptRange {
7517 context: Point::new(0, 0)..Point::new(1, 0),
7518 primary: None,
7519 }],
7520 cx,
7521 );
7522 multibuffer
7523 });
7524 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7525
7526 let mut copilot_requests = copilot_lsp
7527 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7528 Ok(copilot::request::GetCompletionsResult {
7529 completions: vec![copilot::request::Completion {
7530 text: "next line".into(),
7531 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7532 ..Default::default()
7533 }],
7534 })
7535 });
7536
7537 editor.update(cx, |editor, cx| {
7538 editor.change_selections(None, cx, |selections| {
7539 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7540 });
7541 editor.next_copilot_suggestion(&Default::default(), cx);
7542 });
7543
7544 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7545 assert!(copilot_requests.try_next().is_err());
7546
7547 editor.update(cx, |editor, cx| {
7548 editor.change_selections(None, cx, |s| {
7549 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7550 });
7551 editor.next_copilot_suggestion(&Default::default(), cx);
7552 });
7553
7554 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7555 assert!(copilot_requests.try_next().is_ok());
7556}
7557
7558#[gpui::test]
7559async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7560 init_test(cx, |_| {});
7561
7562 let mut language = Language::new(
7563 LanguageConfig {
7564 name: "Rust".into(),
7565 path_suffixes: vec!["rs".to_string()],
7566 brackets: BracketPairConfig {
7567 pairs: vec![BracketPair {
7568 start: "{".to_string(),
7569 end: "}".to_string(),
7570 close: true,
7571 newline: true,
7572 }],
7573 disabled_scopes_by_bracket_ix: Vec::new(),
7574 },
7575 ..Default::default()
7576 },
7577 Some(tree_sitter_rust::language()),
7578 );
7579 let mut fake_servers = language
7580 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7581 capabilities: lsp::ServerCapabilities {
7582 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7583 first_trigger_character: "{".to_string(),
7584 more_trigger_character: None,
7585 }),
7586 ..Default::default()
7587 },
7588 ..Default::default()
7589 }))
7590 .await;
7591
7592 let fs = FakeFs::new(cx.executor());
7593 fs.insert_tree(
7594 "/a",
7595 json!({
7596 "main.rs": "fn main() { let a = 5; }",
7597 "other.rs": "// Test file",
7598 }),
7599 )
7600 .await;
7601 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7602 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7603 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7604
7605 let cx = &mut VisualTestContext::from_window(*workspace, cx);
7606
7607 let worktree_id = workspace
7608 .update(cx, |workspace, cx| {
7609 workspace.project().update(cx, |project, cx| {
7610 project.worktrees().next().unwrap().read(cx).id()
7611 })
7612 })
7613 .unwrap();
7614
7615 let buffer = project
7616 .update(cx, |project, cx| {
7617 project.open_local_buffer("/a/main.rs", cx)
7618 })
7619 .await
7620 .unwrap();
7621 cx.executor().run_until_parked();
7622 cx.executor().start_waiting();
7623 let fake_server = fake_servers.next().await.unwrap();
7624 let editor_handle = workspace
7625 .update(cx, |workspace, cx| {
7626 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7627 })
7628 .unwrap()
7629 .await
7630 .unwrap()
7631 .downcast::<Editor>()
7632 .unwrap();
7633
7634 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7635 assert_eq!(
7636 params.text_document_position.text_document.uri,
7637 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7638 );
7639 assert_eq!(
7640 params.text_document_position.position,
7641 lsp::Position::new(0, 21),
7642 );
7643
7644 Ok(Some(vec![lsp::TextEdit {
7645 new_text: "]".to_string(),
7646 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7647 }]))
7648 });
7649
7650 editor_handle.update(cx, |editor, cx| {
7651 editor.focus(cx);
7652 editor.change_selections(None, cx, |s| {
7653 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7654 });
7655 editor.handle_input("{", cx);
7656 });
7657
7658 cx.executor().run_until_parked();
7659
7660 buffer.update(cx, |buffer, _| {
7661 assert_eq!(
7662 buffer.text(),
7663 "fn main() { let a = {5}; }",
7664 "No extra braces from on type formatting should appear in the buffer"
7665 )
7666 });
7667}
7668
7669#[gpui::test]
7670async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7671 init_test(cx, |_| {});
7672
7673 let language_name: Arc<str> = "Rust".into();
7674 let mut language = Language::new(
7675 LanguageConfig {
7676 name: Arc::clone(&language_name),
7677 path_suffixes: vec!["rs".to_string()],
7678 ..Default::default()
7679 },
7680 Some(tree_sitter_rust::language()),
7681 );
7682
7683 let server_restarts = Arc::new(AtomicUsize::new(0));
7684 let closure_restarts = Arc::clone(&server_restarts);
7685 let language_server_name = "test language server";
7686 let mut fake_servers = language
7687 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7688 name: language_server_name,
7689 initialization_options: Some(json!({
7690 "testOptionValue": true
7691 })),
7692 initializer: Some(Box::new(move |fake_server| {
7693 let task_restarts = Arc::clone(&closure_restarts);
7694 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7695 task_restarts.fetch_add(1, atomic::Ordering::Release);
7696 futures::future::ready(Ok(()))
7697 });
7698 })),
7699 ..Default::default()
7700 }))
7701 .await;
7702
7703 let fs = FakeFs::new(cx.executor());
7704 fs.insert_tree(
7705 "/a",
7706 json!({
7707 "main.rs": "fn main() { let a = 5; }",
7708 "other.rs": "// Test file",
7709 }),
7710 )
7711 .await;
7712 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7713 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7714 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7715 let _buffer = project
7716 .update(cx, |project, cx| {
7717 project.open_local_buffer("/a/main.rs", cx)
7718 })
7719 .await
7720 .unwrap();
7721 let _fake_server = fake_servers.next().await.unwrap();
7722 update_test_language_settings(cx, |language_settings| {
7723 language_settings.languages.insert(
7724 Arc::clone(&language_name),
7725 LanguageSettingsContent {
7726 tab_size: NonZeroU32::new(8),
7727 ..Default::default()
7728 },
7729 );
7730 });
7731 cx.executor().run_until_parked();
7732 assert_eq!(
7733 server_restarts.load(atomic::Ordering::Acquire),
7734 0,
7735 "Should not restart LSP server on an unrelated change"
7736 );
7737
7738 update_test_project_settings(cx, |project_settings| {
7739 project_settings.lsp.insert(
7740 "Some other server name".into(),
7741 LspSettings {
7742 initialization_options: Some(json!({
7743 "some other init value": false
7744 })),
7745 },
7746 );
7747 });
7748 cx.executor().run_until_parked();
7749 assert_eq!(
7750 server_restarts.load(atomic::Ordering::Acquire),
7751 0,
7752 "Should not restart LSP server on an unrelated LSP settings change"
7753 );
7754
7755 update_test_project_settings(cx, |project_settings| {
7756 project_settings.lsp.insert(
7757 language_server_name.into(),
7758 LspSettings {
7759 initialization_options: Some(json!({
7760 "anotherInitValue": false
7761 })),
7762 },
7763 );
7764 });
7765 cx.executor().run_until_parked();
7766 assert_eq!(
7767 server_restarts.load(atomic::Ordering::Acquire),
7768 1,
7769 "Should restart LSP server on a related LSP settings change"
7770 );
7771
7772 update_test_project_settings(cx, |project_settings| {
7773 project_settings.lsp.insert(
7774 language_server_name.into(),
7775 LspSettings {
7776 initialization_options: Some(json!({
7777 "anotherInitValue": false
7778 })),
7779 },
7780 );
7781 });
7782 cx.executor().run_until_parked();
7783 assert_eq!(
7784 server_restarts.load(atomic::Ordering::Acquire),
7785 1,
7786 "Should not restart LSP server on a related LSP settings change that is the same"
7787 );
7788
7789 update_test_project_settings(cx, |project_settings| {
7790 project_settings.lsp.insert(
7791 language_server_name.into(),
7792 LspSettings {
7793 initialization_options: None,
7794 },
7795 );
7796 });
7797 cx.executor().run_until_parked();
7798 assert_eq!(
7799 server_restarts.load(atomic::Ordering::Acquire),
7800 2,
7801 "Should restart LSP server on another related LSP settings change"
7802 );
7803}
7804
7805#[gpui::test]
7806async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7807 init_test(cx, |_| {});
7808
7809 let mut cx = EditorLspTestContext::new_rust(
7810 lsp::ServerCapabilities {
7811 completion_provider: Some(lsp::CompletionOptions {
7812 trigger_characters: Some(vec![".".to_string()]),
7813 resolve_provider: Some(true),
7814 ..Default::default()
7815 }),
7816 ..Default::default()
7817 },
7818 cx,
7819 )
7820 .await;
7821
7822 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7823 cx.simulate_keystroke(".");
7824 let completion_item = lsp::CompletionItem {
7825 label: "some".into(),
7826 kind: Some(lsp::CompletionItemKind::SNIPPET),
7827 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7828 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7829 kind: lsp::MarkupKind::Markdown,
7830 value: "```rust\nSome(2)\n```".to_string(),
7831 })),
7832 deprecated: Some(false),
7833 sort_text: Some("fffffff2".to_string()),
7834 filter_text: Some("some".to_string()),
7835 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7836 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7837 range: lsp::Range {
7838 start: lsp::Position {
7839 line: 0,
7840 character: 22,
7841 },
7842 end: lsp::Position {
7843 line: 0,
7844 character: 22,
7845 },
7846 },
7847 new_text: "Some(2)".to_string(),
7848 })),
7849 additional_text_edits: Some(vec![lsp::TextEdit {
7850 range: lsp::Range {
7851 start: lsp::Position {
7852 line: 0,
7853 character: 20,
7854 },
7855 end: lsp::Position {
7856 line: 0,
7857 character: 22,
7858 },
7859 },
7860 new_text: "".to_string(),
7861 }]),
7862 ..Default::default()
7863 };
7864
7865 let closure_completion_item = completion_item.clone();
7866 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7867 let task_completion_item = closure_completion_item.clone();
7868 async move {
7869 Ok(Some(lsp::CompletionResponse::Array(vec![
7870 task_completion_item,
7871 ])))
7872 }
7873 });
7874
7875 request.next().await;
7876
7877 cx.condition(|editor, _| editor.context_menu_visible())
7878 .await;
7879 let apply_additional_edits = cx.update_editor(|editor, cx| {
7880 editor
7881 .confirm_completion(&ConfirmCompletion::default(), cx)
7882 .unwrap()
7883 });
7884 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7885
7886 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7887 let task_completion_item = completion_item.clone();
7888 async move { Ok(task_completion_item) }
7889 })
7890 .next()
7891 .await
7892 .unwrap();
7893 apply_additional_edits.await.unwrap();
7894 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7895}
7896
7897#[gpui::test]
7898async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
7899 init_test(cx, |_| {});
7900
7901 let mut cx = EditorLspTestContext::new(
7902 Language::new(
7903 LanguageConfig {
7904 path_suffixes: vec!["jsx".into()],
7905 overrides: [(
7906 "element".into(),
7907 LanguageConfigOverride {
7908 word_characters: Override::Set(['-'].into_iter().collect()),
7909 ..Default::default()
7910 },
7911 )]
7912 .into_iter()
7913 .collect(),
7914 ..Default::default()
7915 },
7916 Some(tree_sitter_typescript::language_tsx()),
7917 )
7918 .with_override_query("(jsx_self_closing_element) @element")
7919 .unwrap(),
7920 lsp::ServerCapabilities {
7921 completion_provider: Some(lsp::CompletionOptions {
7922 trigger_characters: Some(vec![":".to_string()]),
7923 ..Default::default()
7924 }),
7925 ..Default::default()
7926 },
7927 cx,
7928 )
7929 .await;
7930
7931 cx.lsp
7932 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7933 Ok(Some(lsp::CompletionResponse::Array(vec![
7934 lsp::CompletionItem {
7935 label: "bg-blue".into(),
7936 ..Default::default()
7937 },
7938 lsp::CompletionItem {
7939 label: "bg-red".into(),
7940 ..Default::default()
7941 },
7942 lsp::CompletionItem {
7943 label: "bg-yellow".into(),
7944 ..Default::default()
7945 },
7946 ])))
7947 });
7948
7949 cx.set_state(r#"<p class="bgˇ" />"#);
7950
7951 // Trigger completion when typing a dash, because the dash is an extra
7952 // word character in the 'element' scope, which contains the cursor.
7953 cx.simulate_keystroke("-");
7954 cx.executor().run_until_parked();
7955 cx.update_editor(|editor, _| {
7956 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7957 assert_eq!(
7958 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7959 &["bg-red", "bg-blue", "bg-yellow"]
7960 );
7961 } else {
7962 panic!("expected completion menu to be open");
7963 }
7964 });
7965
7966 cx.simulate_keystroke("l");
7967 cx.executor().run_until_parked();
7968 cx.update_editor(|editor, _| {
7969 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7970 assert_eq!(
7971 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7972 &["bg-blue", "bg-yellow"]
7973 );
7974 } else {
7975 panic!("expected completion menu to be open");
7976 }
7977 });
7978
7979 // When filtering completions, consider the character after the '-' to
7980 // be the start of a subword.
7981 cx.set_state(r#"<p class="yelˇ" />"#);
7982 cx.simulate_keystroke("l");
7983 cx.executor().run_until_parked();
7984 cx.update_editor(|editor, _| {
7985 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7986 assert_eq!(
7987 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7988 &["bg-yellow"]
7989 );
7990 } else {
7991 panic!("expected completion menu to be open");
7992 }
7993 });
7994}
7995
7996#[gpui::test]
7997async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
7998 init_test(cx, |settings| {
7999 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8000 });
8001
8002 let mut language = Language::new(
8003 LanguageConfig {
8004 name: "Rust".into(),
8005 path_suffixes: vec!["rs".to_string()],
8006 prettier_parser_name: Some("test_parser".to_string()),
8007 ..Default::default()
8008 },
8009 Some(tree_sitter_rust::language()),
8010 );
8011
8012 let test_plugin = "test_plugin";
8013 let _ = language
8014 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
8015 prettier_plugins: vec![test_plugin],
8016 ..Default::default()
8017 }))
8018 .await;
8019
8020 let fs = FakeFs::new(cx.executor());
8021 fs.insert_file("/file.rs", Default::default()).await;
8022
8023 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8024 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8025 project.update(cx, |project, _| {
8026 project.languages().add(Arc::new(language));
8027 });
8028 let buffer = project
8029 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8030 .await
8031 .unwrap();
8032
8033 let buffer_text = "one\ntwo\nthree\n";
8034 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
8035 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8036 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8037
8038 editor
8039 .update(cx, |editor, cx| {
8040 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8041 })
8042 .unwrap()
8043 .await;
8044 assert_eq!(
8045 editor.update(cx, |editor, cx| editor.text(cx)),
8046 buffer_text.to_string() + prettier_format_suffix,
8047 "Test prettier formatting was not applied to the original buffer text",
8048 );
8049
8050 update_test_language_settings(cx, |settings| {
8051 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8052 });
8053 let format = editor.update(cx, |editor, cx| {
8054 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8055 });
8056 format.await.unwrap();
8057 assert_eq!(
8058 editor.update(cx, |editor, cx| editor.text(cx)),
8059 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8060 "Autoformatting (via test prettier) was not applied to the original buffer text",
8061 );
8062}
8063
8064fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
8065 let point = DisplayPoint::new(row as u32, column as u32);
8066 point..point
8067}
8068
8069fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
8070 let (text, ranges) = marked_text_ranges(marked_text, true);
8071 assert_eq!(view.text(cx), text);
8072 assert_eq!(
8073 view.selections.ranges(cx),
8074 ranges,
8075 "Assert selections are {}",
8076 marked_text
8077 );
8078}
8079
8080/// Handle completion request passing a marked string specifying where the completion
8081/// should be triggered from using '|' character, what range should be replaced, and what completions
8082/// should be returned using '<' and '>' to delimit the range
8083pub fn handle_completion_request<'a>(
8084 cx: &mut EditorLspTestContext<'a>,
8085 marked_string: &str,
8086 completions: Vec<&'static str>,
8087) -> impl Future<Output = ()> {
8088 let complete_from_marker: TextRangeMarker = '|'.into();
8089 let replace_range_marker: TextRangeMarker = ('<', '>').into();
8090 let (_, mut marked_ranges) = marked_text_ranges_by(
8091 marked_string,
8092 vec![complete_from_marker.clone(), replace_range_marker.clone()],
8093 );
8094
8095 let complete_from_position =
8096 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
8097 let replace_range =
8098 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
8099
8100 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
8101 let completions = completions.clone();
8102 async move {
8103 assert_eq!(params.text_document_position.text_document.uri, url.clone());
8104 assert_eq!(
8105 params.text_document_position.position,
8106 complete_from_position
8107 );
8108 Ok(Some(lsp::CompletionResponse::Array(
8109 completions
8110 .iter()
8111 .map(|completion_text| lsp::CompletionItem {
8112 label: completion_text.to_string(),
8113 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8114 range: replace_range,
8115 new_text: completion_text.to_string(),
8116 })),
8117 ..Default::default()
8118 })
8119 .collect(),
8120 )))
8121 }
8122 });
8123
8124 async move {
8125 request.next().await;
8126 }
8127}
8128
8129fn handle_resolve_completion_request<'a>(
8130 cx: &mut EditorLspTestContext<'a>,
8131 edits: Option<Vec<(&'static str, &'static str)>>,
8132) -> impl Future<Output = ()> {
8133 let edits = edits.map(|edits| {
8134 edits
8135 .iter()
8136 .map(|(marked_string, new_text)| {
8137 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
8138 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
8139 lsp::TextEdit::new(replace_range, new_text.to_string())
8140 })
8141 .collect::<Vec<_>>()
8142 });
8143
8144 let mut request =
8145 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8146 let edits = edits.clone();
8147 async move {
8148 Ok(lsp::CompletionItem {
8149 additional_text_edits: edits,
8150 ..Default::default()
8151 })
8152 }
8153 });
8154
8155 async move {
8156 request.next().await;
8157 }
8158}
8159
8160fn handle_copilot_completion_request(
8161 lsp: &lsp::FakeLanguageServer,
8162 completions: Vec<copilot::request::Completion>,
8163 completions_cycling: Vec<copilot::request::Completion>,
8164) {
8165 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
8166 let completions = completions.clone();
8167 async move {
8168 Ok(copilot::request::GetCompletionsResult {
8169 completions: completions.clone(),
8170 })
8171 }
8172 });
8173 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
8174 let completions_cycling = completions_cycling.clone();
8175 async move {
8176 Ok(copilot::request::GetCompletionsResult {
8177 completions: completions_cycling.clone(),
8178 })
8179 }
8180 });
8181}
8182
8183pub(crate) fn update_test_language_settings(
8184 cx: &mut TestAppContext,
8185 f: impl Fn(&mut AllLanguageSettingsContent),
8186) {
8187 cx.update(|cx| {
8188 cx.update_global(|store: &mut SettingsStore, cx| {
8189 store.update_user_settings::<AllLanguageSettings>(cx, f);
8190 });
8191 });
8192}
8193
8194pub(crate) fn update_test_project_settings(
8195 cx: &mut TestAppContext,
8196 f: impl Fn(&mut ProjectSettings),
8197) {
8198 cx.update(|cx| {
8199 cx.update_global(|store: &mut SettingsStore, cx| {
8200 store.update_user_settings::<ProjectSettings>(cx, f);
8201 });
8202 });
8203}
8204
8205pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
8206 cx.update(|cx| {
8207 let store = SettingsStore::test(cx);
8208 cx.set_global(store);
8209 theme::init(theme::LoadThemes::JustBase, cx);
8210 client::init_settings(cx);
8211 language::init(cx);
8212 Project::init_settings(cx);
8213 workspace::init_settings(cx);
8214 crate::init(cx);
8215 });
8216
8217 update_test_language_settings(cx, f);
8218}