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//todo!(completion)
5431// #[gpui::test]
5432// async fn test_completion(cx: &mut gpui::TestAppContext) {
5433// init_test(cx, |_| {});
5434
5435// let mut cx = EditorLspTestContext::new_rust(
5436// lsp::ServerCapabilities {
5437// completion_provider: Some(lsp::CompletionOptions {
5438// trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5439// resolve_provider: Some(true),
5440// ..Default::default()
5441// }),
5442// ..Default::default()
5443// },
5444// cx,
5445// )
5446// .await;
5447
5448// cx.set_state(indoc! {"
5449// oneˇ
5450// two
5451// three
5452// "});
5453// cx.simulate_keystroke(".");
5454// handle_completion_request(
5455// &mut cx,
5456// indoc! {"
5457// one.|<>
5458// two
5459// three
5460// "},
5461// vec!["first_completion", "second_completion"],
5462// )
5463// .await;
5464// cx.condition(|editor, _| editor.context_menu_visible())
5465// .await;
5466// let apply_additional_edits = cx.update_editor(|editor, cx| {
5467// editor.context_menu_next(&Default::default(), cx);
5468// editor
5469// .confirm_completion(&ConfirmCompletion::default(), cx)
5470// .unwrap()
5471// });
5472// cx.assert_editor_state(indoc! {"
5473// one.second_completionˇ
5474// two
5475// three
5476// "});
5477
5478// handle_resolve_completion_request(
5479// &mut cx,
5480// Some(vec![
5481// (
5482// //This overlaps with the primary completion edit which is
5483// //misbehavior from the LSP spec, test that we filter it out
5484// indoc! {"
5485// one.second_ˇcompletion
5486// two
5487// threeˇ
5488// "},
5489// "overlapping additional edit",
5490// ),
5491// (
5492// indoc! {"
5493// one.second_completion
5494// two
5495// threeˇ
5496// "},
5497// "\nadditional edit",
5498// ),
5499// ]),
5500// )
5501// .await;
5502// apply_additional_edits.await.unwrap();
5503// cx.assert_editor_state(indoc! {"
5504// one.second_completionˇ
5505// two
5506// three
5507// additional edit
5508// "});
5509
5510// cx.set_state(indoc! {"
5511// one.second_completion
5512// twoˇ
5513// threeˇ
5514// additional edit
5515// "});
5516// cx.simulate_keystroke(" ");
5517// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5518// cx.simulate_keystroke("s");
5519// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5520
5521// cx.assert_editor_state(indoc! {"
5522// one.second_completion
5523// two sˇ
5524// three sˇ
5525// additional edit
5526// "});
5527// handle_completion_request(
5528// &mut cx,
5529// indoc! {"
5530// one.second_completion
5531// two s
5532// three <s|>
5533// additional edit
5534// "},
5535// vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5536// )
5537// .await;
5538// cx.condition(|editor, _| editor.context_menu_visible())
5539// .await;
5540
5541// cx.simulate_keystroke("i");
5542
5543// handle_completion_request(
5544// &mut cx,
5545// indoc! {"
5546// one.second_completion
5547// two si
5548// three <si|>
5549// additional edit
5550// "},
5551// vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5552// )
5553// .await;
5554// cx.condition(|editor, _| editor.context_menu_visible())
5555// .await;
5556
5557// let apply_additional_edits = cx.update_editor(|editor, cx| {
5558// editor
5559// .confirm_completion(&ConfirmCompletion::default(), cx)
5560// .unwrap()
5561// });
5562// cx.assert_editor_state(indoc! {"
5563// one.second_completion
5564// two sixth_completionˇ
5565// three sixth_completionˇ
5566// additional edit
5567// "});
5568
5569// handle_resolve_completion_request(&mut cx, None).await;
5570// apply_additional_edits.await.unwrap();
5571
5572// cx.update(|cx| {
5573// cx.update_global::<SettingsStore, _, _>(|settings, cx| {
5574// settings.update_user_settings::<EditorSettings>(cx, |settings| {
5575// settings.show_completions_on_input = Some(false);
5576// });
5577// })
5578// });
5579// cx.set_state("editorˇ");
5580// cx.simulate_keystroke(".");
5581// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5582// cx.simulate_keystroke("c");
5583// cx.simulate_keystroke("l");
5584// cx.simulate_keystroke("o");
5585// cx.assert_editor_state("editor.cloˇ");
5586// assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5587// cx.update_editor(|editor, cx| {
5588// editor.show_completions(&ShowCompletions, cx);
5589// });
5590// handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5591// cx.condition(|editor, _| editor.context_menu_visible())
5592// .await;
5593// let apply_additional_edits = cx.update_editor(|editor, cx| {
5594// editor
5595// .confirm_completion(&ConfirmCompletion::default(), cx)
5596// .unwrap()
5597// });
5598// cx.assert_editor_state("editor.closeˇ");
5599// handle_resolve_completion_request(&mut cx, None).await;
5600// apply_additional_edits.await.unwrap();
5601// }
5602
5603#[gpui::test]
5604async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5605 init_test(cx, |_| {});
5606 let mut cx = EditorTestContext::new(cx).await;
5607 let language = Arc::new(Language::new(
5608 LanguageConfig {
5609 line_comment: Some("// ".into()),
5610 ..Default::default()
5611 },
5612 Some(tree_sitter_rust::language()),
5613 ));
5614 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5615
5616 // If multiple selections intersect a line, the line is only toggled once.
5617 cx.set_state(indoc! {"
5618 fn a() {
5619 «//b();
5620 ˇ»// «c();
5621 //ˇ» d();
5622 }
5623 "});
5624
5625 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5626
5627 cx.assert_editor_state(indoc! {"
5628 fn a() {
5629 «b();
5630 c();
5631 ˇ» d();
5632 }
5633 "});
5634
5635 // The comment prefix is inserted at the same column for every line in a
5636 // selection.
5637 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5638
5639 cx.assert_editor_state(indoc! {"
5640 fn a() {
5641 // «b();
5642 // c();
5643 ˇ»// d();
5644 }
5645 "});
5646
5647 // If a selection ends at the beginning of a line, that line is not toggled.
5648 cx.set_selections_state(indoc! {"
5649 fn a() {
5650 // b();
5651 «// c();
5652 ˇ» // d();
5653 }
5654 "});
5655
5656 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5657
5658 cx.assert_editor_state(indoc! {"
5659 fn a() {
5660 // b();
5661 «c();
5662 ˇ» // d();
5663 }
5664 "});
5665
5666 // If a selection span a single line and is empty, the line is toggled.
5667 cx.set_state(indoc! {"
5668 fn a() {
5669 a();
5670 b();
5671 ˇ
5672 }
5673 "});
5674
5675 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5676
5677 cx.assert_editor_state(indoc! {"
5678 fn a() {
5679 a();
5680 b();
5681 //•ˇ
5682 }
5683 "});
5684
5685 // If a selection span multiple lines, empty lines are not toggled.
5686 cx.set_state(indoc! {"
5687 fn a() {
5688 «a();
5689
5690 c();ˇ»
5691 }
5692 "});
5693
5694 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5695
5696 cx.assert_editor_state(indoc! {"
5697 fn a() {
5698 // «a();
5699
5700 // c();ˇ»
5701 }
5702 "});
5703}
5704
5705#[gpui::test]
5706async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5707 init_test(cx, |_| {});
5708
5709 let language = Arc::new(Language::new(
5710 LanguageConfig {
5711 line_comment: Some("// ".into()),
5712 ..Default::default()
5713 },
5714 Some(tree_sitter_rust::language()),
5715 ));
5716
5717 let registry = Arc::new(LanguageRegistry::test());
5718 registry.add(language.clone());
5719
5720 let mut cx = EditorTestContext::new(cx).await;
5721 cx.update_buffer(|buffer, cx| {
5722 buffer.set_language_registry(registry);
5723 buffer.set_language(Some(language), cx);
5724 });
5725
5726 let toggle_comments = &ToggleComments {
5727 advance_downwards: true,
5728 };
5729
5730 // Single cursor on one line -> advance
5731 // Cursor moves horizontally 3 characters as well on non-blank line
5732 cx.set_state(indoc!(
5733 "fn a() {
5734 ˇdog();
5735 cat();
5736 }"
5737 ));
5738 cx.update_editor(|editor, cx| {
5739 editor.toggle_comments(toggle_comments, cx);
5740 });
5741 cx.assert_editor_state(indoc!(
5742 "fn a() {
5743 // dog();
5744 catˇ();
5745 }"
5746 ));
5747
5748 // Single selection on one line -> don't advance
5749 cx.set_state(indoc!(
5750 "fn a() {
5751 «dog()ˇ»;
5752 cat();
5753 }"
5754 ));
5755 cx.update_editor(|editor, cx| {
5756 editor.toggle_comments(toggle_comments, cx);
5757 });
5758 cx.assert_editor_state(indoc!(
5759 "fn a() {
5760 // «dog()ˇ»;
5761 cat();
5762 }"
5763 ));
5764
5765 // Multiple cursors on one line -> advance
5766 cx.set_state(indoc!(
5767 "fn a() {
5768 ˇdˇog();
5769 cat();
5770 }"
5771 ));
5772 cx.update_editor(|editor, cx| {
5773 editor.toggle_comments(toggle_comments, cx);
5774 });
5775 cx.assert_editor_state(indoc!(
5776 "fn a() {
5777 // dog();
5778 catˇ(ˇ);
5779 }"
5780 ));
5781
5782 // Multiple cursors on one line, with selection -> don't advance
5783 cx.set_state(indoc!(
5784 "fn a() {
5785 ˇdˇog«()ˇ»;
5786 cat();
5787 }"
5788 ));
5789 cx.update_editor(|editor, cx| {
5790 editor.toggle_comments(toggle_comments, cx);
5791 });
5792 cx.assert_editor_state(indoc!(
5793 "fn a() {
5794 // ˇdˇog«()ˇ»;
5795 cat();
5796 }"
5797 ));
5798
5799 // Single cursor on one line -> advance
5800 // Cursor moves to column 0 on blank line
5801 cx.set_state(indoc!(
5802 "fn a() {
5803 ˇdog();
5804
5805 cat();
5806 }"
5807 ));
5808 cx.update_editor(|editor, cx| {
5809 editor.toggle_comments(toggle_comments, cx);
5810 });
5811 cx.assert_editor_state(indoc!(
5812 "fn a() {
5813 // dog();
5814 ˇ
5815 cat();
5816 }"
5817 ));
5818
5819 // Single cursor on one line -> advance
5820 // Cursor starts and ends at column 0
5821 cx.set_state(indoc!(
5822 "fn a() {
5823 ˇ dog();
5824 cat();
5825 }"
5826 ));
5827 cx.update_editor(|editor, cx| {
5828 editor.toggle_comments(toggle_comments, cx);
5829 });
5830 cx.assert_editor_state(indoc!(
5831 "fn a() {
5832 // dog();
5833 ˇ cat();
5834 }"
5835 ));
5836}
5837
5838#[gpui::test]
5839async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5840 init_test(cx, |_| {});
5841
5842 let mut cx = EditorTestContext::new(cx).await;
5843
5844 let html_language = Arc::new(
5845 Language::new(
5846 LanguageConfig {
5847 name: "HTML".into(),
5848 block_comment: Some(("<!-- ".into(), " -->".into())),
5849 ..Default::default()
5850 },
5851 Some(tree_sitter_html::language()),
5852 )
5853 .with_injection_query(
5854 r#"
5855 (script_element
5856 (raw_text) @content
5857 (#set! "language" "javascript"))
5858 "#,
5859 )
5860 .unwrap(),
5861 );
5862
5863 let javascript_language = Arc::new(Language::new(
5864 LanguageConfig {
5865 name: "JavaScript".into(),
5866 line_comment: Some("// ".into()),
5867 ..Default::default()
5868 },
5869 Some(tree_sitter_typescript::language_tsx()),
5870 ));
5871
5872 let registry = Arc::new(LanguageRegistry::test());
5873 registry.add(html_language.clone());
5874 registry.add(javascript_language.clone());
5875
5876 cx.update_buffer(|buffer, cx| {
5877 buffer.set_language_registry(registry);
5878 buffer.set_language(Some(html_language), cx);
5879 });
5880
5881 // Toggle comments for empty selections
5882 cx.set_state(
5883 &r#"
5884 <p>A</p>ˇ
5885 <p>B</p>ˇ
5886 <p>C</p>ˇ
5887 "#
5888 .unindent(),
5889 );
5890 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5891 cx.assert_editor_state(
5892 &r#"
5893 <!-- <p>A</p>ˇ -->
5894 <!-- <p>B</p>ˇ -->
5895 <!-- <p>C</p>ˇ -->
5896 "#
5897 .unindent(),
5898 );
5899 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5900 cx.assert_editor_state(
5901 &r#"
5902 <p>A</p>ˇ
5903 <p>B</p>ˇ
5904 <p>C</p>ˇ
5905 "#
5906 .unindent(),
5907 );
5908
5909 // Toggle comments for mixture of empty and non-empty selections, where
5910 // multiple selections occupy a given line.
5911 cx.set_state(
5912 &r#"
5913 <p>A«</p>
5914 <p>ˇ»B</p>ˇ
5915 <p>C«</p>
5916 <p>ˇ»D</p>ˇ
5917 "#
5918 .unindent(),
5919 );
5920
5921 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5922 cx.assert_editor_state(
5923 &r#"
5924 <!-- <p>A«</p>
5925 <p>ˇ»B</p>ˇ -->
5926 <!-- <p>C«</p>
5927 <p>ˇ»D</p>ˇ -->
5928 "#
5929 .unindent(),
5930 );
5931 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5932 cx.assert_editor_state(
5933 &r#"
5934 <p>A«</p>
5935 <p>ˇ»B</p>ˇ
5936 <p>C«</p>
5937 <p>ˇ»D</p>ˇ
5938 "#
5939 .unindent(),
5940 );
5941
5942 // Toggle comments when different languages are active for different
5943 // selections.
5944 cx.set_state(
5945 &r#"
5946 ˇ<script>
5947 ˇvar x = new Y();
5948 ˇ</script>
5949 "#
5950 .unindent(),
5951 );
5952 cx.executor().run_until_parked();
5953 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5954 cx.assert_editor_state(
5955 &r#"
5956 <!-- ˇ<script> -->
5957 // ˇvar x = new Y();
5958 <!-- ˇ</script> -->
5959 "#
5960 .unindent(),
5961 );
5962}
5963
5964#[gpui::test]
5965fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5966 init_test(cx, |_| {});
5967
5968 let buffer =
5969 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
5970 let multibuffer = cx.build_model(|cx| {
5971 let mut multibuffer = MultiBuffer::new(0);
5972 multibuffer.push_excerpts(
5973 buffer.clone(),
5974 [
5975 ExcerptRange {
5976 context: Point::new(0, 0)..Point::new(0, 4),
5977 primary: None,
5978 },
5979 ExcerptRange {
5980 context: Point::new(1, 0)..Point::new(1, 4),
5981 primary: None,
5982 },
5983 ],
5984 cx,
5985 );
5986 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5987 multibuffer
5988 });
5989
5990 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
5991 view.update(cx, |view, cx| {
5992 assert_eq!(view.text(cx), "aaaa\nbbbb");
5993 view.change_selections(None, cx, |s| {
5994 s.select_ranges([
5995 Point::new(0, 0)..Point::new(0, 0),
5996 Point::new(1, 0)..Point::new(1, 0),
5997 ])
5998 });
5999
6000 view.handle_input("X", cx);
6001 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6002 assert_eq!(
6003 view.selections.ranges(cx),
6004 [
6005 Point::new(0, 1)..Point::new(0, 1),
6006 Point::new(1, 1)..Point::new(1, 1),
6007 ]
6008 );
6009
6010 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6011 view.change_selections(None, cx, |s| {
6012 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6013 });
6014 view.backspace(&Default::default(), cx);
6015 assert_eq!(view.text(cx), "Xa\nbbb");
6016 assert_eq!(
6017 view.selections.ranges(cx),
6018 [Point::new(1, 0)..Point::new(1, 0)]
6019 );
6020
6021 view.change_selections(None, cx, |s| {
6022 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6023 });
6024 view.backspace(&Default::default(), cx);
6025 assert_eq!(view.text(cx), "X\nbb");
6026 assert_eq!(
6027 view.selections.ranges(cx),
6028 [Point::new(0, 1)..Point::new(0, 1)]
6029 );
6030 });
6031}
6032
6033#[gpui::test]
6034fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6035 init_test(cx, |_| {});
6036
6037 let markers = vec![('[', ']').into(), ('(', ')').into()];
6038 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6039 indoc! {"
6040 [aaaa
6041 (bbbb]
6042 cccc)",
6043 },
6044 markers.clone(),
6045 );
6046 let excerpt_ranges = markers.into_iter().map(|marker| {
6047 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6048 ExcerptRange {
6049 context,
6050 primary: None,
6051 }
6052 });
6053 let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
6054 let multibuffer = cx.build_model(|cx| {
6055 let mut multibuffer = MultiBuffer::new(0);
6056 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6057 multibuffer
6058 });
6059
6060 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6061 view.update(cx, |view, cx| {
6062 let (expected_text, selection_ranges) = marked_text_ranges(
6063 indoc! {"
6064 aaaa
6065 bˇbbb
6066 bˇbbˇb
6067 cccc"
6068 },
6069 true,
6070 );
6071 assert_eq!(view.text(cx), expected_text);
6072 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6073
6074 view.handle_input("X", cx);
6075
6076 let (expected_text, expected_selections) = marked_text_ranges(
6077 indoc! {"
6078 aaaa
6079 bXˇbbXb
6080 bXˇbbXˇb
6081 cccc"
6082 },
6083 false,
6084 );
6085 assert_eq!(view.text(cx), expected_text);
6086 assert_eq!(view.selections.ranges(cx), expected_selections);
6087
6088 view.newline(&Newline, cx);
6089 let (expected_text, expected_selections) = marked_text_ranges(
6090 indoc! {"
6091 aaaa
6092 bX
6093 ˇbbX
6094 b
6095 bX
6096 ˇbbX
6097 ˇb
6098 cccc"
6099 },
6100 false,
6101 );
6102 assert_eq!(view.text(cx), expected_text);
6103 assert_eq!(view.selections.ranges(cx), expected_selections);
6104 });
6105}
6106
6107#[gpui::test]
6108fn test_refresh_selections(cx: &mut TestAppContext) {
6109 init_test(cx, |_| {});
6110
6111 let buffer =
6112 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6113 let mut excerpt1_id = None;
6114 let multibuffer = cx.build_model(|cx| {
6115 let mut multibuffer = MultiBuffer::new(0);
6116 excerpt1_id = multibuffer
6117 .push_excerpts(
6118 buffer.clone(),
6119 [
6120 ExcerptRange {
6121 context: Point::new(0, 0)..Point::new(1, 4),
6122 primary: None,
6123 },
6124 ExcerptRange {
6125 context: Point::new(1, 0)..Point::new(2, 4),
6126 primary: None,
6127 },
6128 ],
6129 cx,
6130 )
6131 .into_iter()
6132 .next();
6133 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6134 multibuffer
6135 });
6136
6137 let editor = cx.add_window(|cx| {
6138 let mut editor = build_editor(multibuffer.clone(), cx);
6139 let snapshot = editor.snapshot(cx);
6140 editor.change_selections(None, cx, |s| {
6141 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6142 });
6143 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6144 assert_eq!(
6145 editor.selections.ranges(cx),
6146 [
6147 Point::new(1, 3)..Point::new(1, 3),
6148 Point::new(2, 1)..Point::new(2, 1),
6149 ]
6150 );
6151 editor
6152 });
6153
6154 // Refreshing selections is a no-op when excerpts haven't changed.
6155 editor.update(cx, |editor, cx| {
6156 editor.change_selections(None, cx, |s| s.refresh());
6157 assert_eq!(
6158 editor.selections.ranges(cx),
6159 [
6160 Point::new(1, 3)..Point::new(1, 3),
6161 Point::new(2, 1)..Point::new(2, 1),
6162 ]
6163 );
6164 });
6165
6166 multibuffer.update(cx, |multibuffer, cx| {
6167 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6168 });
6169 editor.update(cx, |editor, cx| {
6170 // Removing an excerpt causes the first selection to become degenerate.
6171 assert_eq!(
6172 editor.selections.ranges(cx),
6173 [
6174 Point::new(0, 0)..Point::new(0, 0),
6175 Point::new(0, 1)..Point::new(0, 1)
6176 ]
6177 );
6178
6179 // Refreshing selections will relocate the first selection to the original buffer
6180 // location.
6181 editor.change_selections(None, cx, |s| s.refresh());
6182 assert_eq!(
6183 editor.selections.ranges(cx),
6184 [
6185 Point::new(0, 1)..Point::new(0, 1),
6186 Point::new(0, 3)..Point::new(0, 3)
6187 ]
6188 );
6189 assert!(editor.selections.pending_anchor().is_some());
6190 });
6191}
6192
6193#[gpui::test]
6194fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6195 init_test(cx, |_| {});
6196
6197 let buffer =
6198 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6199 let mut excerpt1_id = None;
6200 let multibuffer = cx.build_model(|cx| {
6201 let mut multibuffer = MultiBuffer::new(0);
6202 excerpt1_id = multibuffer
6203 .push_excerpts(
6204 buffer.clone(),
6205 [
6206 ExcerptRange {
6207 context: Point::new(0, 0)..Point::new(1, 4),
6208 primary: None,
6209 },
6210 ExcerptRange {
6211 context: Point::new(1, 0)..Point::new(2, 4),
6212 primary: None,
6213 },
6214 ],
6215 cx,
6216 )
6217 .into_iter()
6218 .next();
6219 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6220 multibuffer
6221 });
6222
6223 let editor = cx.add_window(|cx| {
6224 let mut editor = build_editor(multibuffer.clone(), cx);
6225 let snapshot = editor.snapshot(cx);
6226 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6227 assert_eq!(
6228 editor.selections.ranges(cx),
6229 [Point::new(1, 3)..Point::new(1, 3)]
6230 );
6231 editor
6232 });
6233
6234 multibuffer.update(cx, |multibuffer, cx| {
6235 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6236 });
6237 editor.update(cx, |editor, cx| {
6238 assert_eq!(
6239 editor.selections.ranges(cx),
6240 [Point::new(0, 0)..Point::new(0, 0)]
6241 );
6242
6243 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6244 editor.change_selections(None, cx, |s| s.refresh());
6245 assert_eq!(
6246 editor.selections.ranges(cx),
6247 [Point::new(0, 3)..Point::new(0, 3)]
6248 );
6249 assert!(editor.selections.pending_anchor().is_some());
6250 });
6251}
6252
6253#[gpui::test]
6254async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6255 init_test(cx, |_| {});
6256
6257 let language = Arc::new(
6258 Language::new(
6259 LanguageConfig {
6260 brackets: BracketPairConfig {
6261 pairs: vec![
6262 BracketPair {
6263 start: "{".to_string(),
6264 end: "}".to_string(),
6265 close: true,
6266 newline: true,
6267 },
6268 BracketPair {
6269 start: "/* ".to_string(),
6270 end: " */".to_string(),
6271 close: true,
6272 newline: true,
6273 },
6274 ],
6275 ..Default::default()
6276 },
6277 ..Default::default()
6278 },
6279 Some(tree_sitter_rust::language()),
6280 )
6281 .with_indents_query("")
6282 .unwrap(),
6283 );
6284
6285 let text = concat!(
6286 "{ }\n", //
6287 " x\n", //
6288 " /* */\n", //
6289 "x\n", //
6290 "{{} }\n", //
6291 );
6292
6293 let buffer = cx.build_model(|cx| {
6294 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
6295 });
6296 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
6297 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6298 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6299 .await;
6300
6301 view.update(cx, |view, cx| {
6302 view.change_selections(None, cx, |s| {
6303 s.select_display_ranges([
6304 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6305 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6306 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6307 ])
6308 });
6309 view.newline(&Newline, cx);
6310
6311 assert_eq!(
6312 view.buffer().read(cx).read(cx).text(),
6313 concat!(
6314 "{ \n", // Suppress rustfmt
6315 "\n", //
6316 "}\n", //
6317 " x\n", //
6318 " /* \n", //
6319 " \n", //
6320 " */\n", //
6321 "x\n", //
6322 "{{} \n", //
6323 "}\n", //
6324 )
6325 );
6326 });
6327}
6328
6329//todo!(finish editor tests)
6330// #[gpui::test]
6331// fn test_highlighted_ranges(cx: &mut TestAppContext) {
6332// init_test(cx, |_| {});
6333
6334// let editor = cx.add_window(|cx| {
6335// let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6336// build_editor(buffer.clone(), cx)
6337// });
6338
6339// editor.update(cx, |editor, cx| {
6340// struct Type1;
6341// struct Type2;
6342
6343// let buffer = editor.buffer.read(cx).snapshot(cx);
6344
6345// let anchor_range =
6346// |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6347
6348// editor.highlight_background::<Type1>(
6349// vec![
6350// anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6351// anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6352// anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6353// anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6354// ],
6355// |_| Hsla::red(),
6356// cx,
6357// );
6358// editor.highlight_background::<Type2>(
6359// vec![
6360// anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6361// anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6362// anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6363// anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6364// ],
6365// |_| Hsla::green(),
6366// cx,
6367// );
6368
6369// let snapshot = editor.snapshot(cx);
6370// let mut highlighted_ranges = editor.background_highlights_in_range(
6371// anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6372// &snapshot,
6373// cx.theme().colors(),
6374// );
6375// // Enforce a consistent ordering based on color without relying on the ordering of the
6376// // highlight's `TypeId` which is non-executor.
6377// highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6378// assert_eq!(
6379// highlighted_ranges,
6380// &[
6381// (
6382// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6383// Hsla::green(),
6384// ),
6385// (
6386// DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6387// Hsla::green(),
6388// ),
6389// (
6390// DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6391// Hsla::red(),
6392// ),
6393// (
6394// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6395// Hsla::red(),
6396// ),
6397// ]
6398// );
6399// assert_eq!(
6400// editor.background_highlights_in_range(
6401// anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6402// &snapshot,
6403// cx.theme().colors(),
6404// ),
6405// &[(
6406// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6407// Hsla::red(),
6408// )]
6409// );
6410// });
6411// }
6412
6413// todo!(following)
6414// #[gpui::test]
6415// async fn test_following(cx: &mut gpui::TestAppContext) {
6416// init_test(cx, |_| {});
6417
6418// let fs = FakeFs::new(cx.executor());
6419// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6420
6421// let buffer = project.update(cx, |project, cx| {
6422// let buffer = project
6423// .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6424// .unwrap();
6425// cx.build_model(|cx| MultiBuffer::singleton(buffer, cx))
6426// });
6427// let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6428// let follower = cx.update(|cx| {
6429// cx.open_window(
6430// WindowOptions {
6431// bounds: WindowBounds::Fixed(Bounds::from_corners(
6432// gpui::Point::new((0. as f64).into(), (0. as f64).into()),
6433// gpui::Point::new((10. as f64).into(), (80. as f64).into()),
6434// )),
6435// ..Default::default()
6436// },
6437// |cx| cx.build_view(|cx| build_editor(buffer.clone(), cx)),
6438// )
6439// });
6440
6441// let is_still_following = Rc::new(RefCell::new(true));
6442// let follower_edit_event_count = Rc::new(RefCell::new(0));
6443// let pending_update = Rc::new(RefCell::new(None));
6444// follower.update(cx, {
6445// let update = pending_update.clone();
6446// let is_still_following = is_still_following.clone();
6447// let follower_edit_event_count = follower_edit_event_count.clone();
6448// |_, cx| {
6449// cx.subscribe(
6450// &leader.root_view(cx).unwrap(),
6451// move |_, leader, event, cx| {
6452// leader
6453// .read(cx)
6454// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6455// },
6456// )
6457// .detach();
6458
6459// cx.subscribe(
6460// &follower.root_view(cx).unwrap(),
6461// move |_, _, event: &Event, cx| {
6462// if matches!(event.to_follow_event(), Some(FollowEvent::Unfollow)) {
6463// *is_still_following.borrow_mut() = false;
6464// }
6465
6466// if let Event::BufferEdited = event {
6467// *follower_edit_event_count.borrow_mut() += 1;
6468// }
6469// },
6470// )
6471// .detach();
6472// }
6473// });
6474
6475// // Update the selections only
6476// leader.update(cx, |leader, cx| {
6477// leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6478// });
6479// follower
6480// .update(cx, |follower, cx| {
6481// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6482// })
6483// .unwrap()
6484// .await
6485// .unwrap();
6486// follower.update(cx, |follower, cx| {
6487// assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6488// });
6489// assert_eq!(*is_still_following.borrow(), true);
6490// assert_eq!(*follower_edit_event_count.borrow(), 0);
6491
6492// // Update the scroll position only
6493// leader.update(cx, |leader, cx| {
6494// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6495// });
6496// follower
6497// .update(cx, |follower, cx| {
6498// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6499// })
6500// .unwrap()
6501// .await
6502// .unwrap();
6503// assert_eq!(
6504// follower
6505// .update(cx, |follower, cx| follower.scroll_position(cx))
6506// .unwrap(),
6507// gpui::Point::new(1.5, 3.5)
6508// );
6509// assert_eq!(*is_still_following.borrow(), true);
6510// assert_eq!(*follower_edit_event_count.borrow(), 0);
6511
6512// // Update the selections and scroll position. The follower's scroll position is updated
6513// // via autoscroll, not via the leader's exact scroll position.
6514// leader.update(cx, |leader, cx| {
6515// leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6516// leader.request_autoscroll(Autoscroll::newest(), cx);
6517// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6518// });
6519// follower
6520// .update(cx, |follower, cx| {
6521// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6522// })
6523// .unwrap()
6524// .await
6525// .unwrap();
6526// follower.update(cx, |follower, cx| {
6527// assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
6528// assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6529// });
6530// assert_eq!(*is_still_following.borrow(), true);
6531
6532// // Creating a pending selection that precedes another selection
6533// leader.update(cx, |leader, cx| {
6534// leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6535// leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6536// });
6537// follower
6538// .update(cx, |follower, cx| {
6539// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6540// })
6541// .unwrap()
6542// .await
6543// .unwrap();
6544// follower.update(cx, |follower, cx| {
6545// assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6546// });
6547// assert_eq!(*is_still_following.borrow(), true);
6548
6549// // Extend the pending selection so that it surrounds another selection
6550// leader.update(cx, |leader, cx| {
6551// leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6552// });
6553// follower
6554// .update(cx, |follower, cx| {
6555// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6556// })
6557// .unwrap()
6558// .await
6559// .unwrap();
6560// follower.update(cx, |follower, cx| {
6561// assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6562// });
6563
6564// // Scrolling locally breaks the follow
6565// follower.update(cx, |follower, cx| {
6566// let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6567// follower.set_scroll_anchor(
6568// ScrollAnchor {
6569// anchor: top_anchor,
6570// offset: gpui::Point::new(0.0, 0.5),
6571// },
6572// cx,
6573// );
6574// });
6575// assert_eq!(*is_still_following.borrow(), false);
6576// }
6577
6578// #[gpui::test]
6579// async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6580// init_test(cx, |_| {});
6581
6582// let fs = FakeFs::new(cx.executor());
6583// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6584// let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6585// let pane = workspace
6586// .update(cx, |workspace, _| workspace.active_pane().clone())
6587// .unwrap();
6588
6589// let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6590
6591// let leader = pane.update(cx, |_, cx| {
6592// let multibuffer = cx.build_model(|_| MultiBuffer::new(0));
6593// cx.build_view(|cx| build_editor(multibuffer.clone(), cx))
6594// });
6595
6596// // Start following the editor when it has no excerpts.
6597// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6598// let follower_1 = cx
6599// .update(|cx| {
6600// Editor::from_state_proto(
6601// pane.clone(),
6602// workspace.root_view(cx).unwrap(),
6603// ViewId {
6604// creator: Default::default(),
6605// id: 0,
6606// },
6607// &mut state_message,
6608// cx,
6609// )
6610// })
6611// .unwrap()
6612// .await
6613// .unwrap();
6614
6615// let update_message = Rc::new(RefCell::new(None));
6616// follower_1.update(cx, {
6617// let update = update_message.clone();
6618// |_, cx| {
6619// cx.subscribe(&leader, move |_, leader, event, cx| {
6620// leader
6621// .read(cx)
6622// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6623// })
6624// .detach();
6625// }
6626// });
6627
6628// let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6629// (
6630// project
6631// .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6632// .unwrap(),
6633// project
6634// .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6635// .unwrap(),
6636// )
6637// });
6638
6639// // Insert some excerpts.
6640// leader.update(cx, |leader, cx| {
6641// leader.buffer.update(cx, |multibuffer, cx| {
6642// let excerpt_ids = multibuffer.push_excerpts(
6643// buffer_1.clone(),
6644// [
6645// ExcerptRange {
6646// context: 1..6,
6647// primary: None,
6648// },
6649// ExcerptRange {
6650// context: 12..15,
6651// primary: None,
6652// },
6653// ExcerptRange {
6654// context: 0..3,
6655// primary: None,
6656// },
6657// ],
6658// cx,
6659// );
6660// multibuffer.insert_excerpts_after(
6661// excerpt_ids[0],
6662// buffer_2.clone(),
6663// [
6664// ExcerptRange {
6665// context: 8..12,
6666// primary: None,
6667// },
6668// ExcerptRange {
6669// context: 0..6,
6670// primary: None,
6671// },
6672// ],
6673// cx,
6674// );
6675// });
6676// });
6677
6678// // Apply the update of adding the excerpts.
6679// follower_1
6680// .update(cx, |follower, cx| {
6681// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6682// })
6683// .await
6684// .unwrap();
6685// assert_eq!(
6686// follower_1.update(cx, |editor, cx| editor.text(cx)),
6687// leader.update(cx, |editor, cx| editor.text(cx))
6688// );
6689// update_message.borrow_mut().take();
6690
6691// // Start following separately after it already has excerpts.
6692// let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6693// let follower_2 = cx
6694// .update(|cx| {
6695// Editor::from_state_proto(
6696// pane.clone(),
6697// workspace.clone(),
6698// ViewId {
6699// creator: Default::default(),
6700// id: 0,
6701// },
6702// &mut state_message,
6703// cx,
6704// )
6705// })
6706// .unwrap()
6707// .await
6708// .unwrap();
6709// assert_eq!(
6710// follower_2.update(cx, |editor, cx| editor.text(cx)),
6711// leader.update(cx, |editor, cx| editor.text(cx))
6712// );
6713
6714// // Remove some excerpts.
6715// leader.update(cx, |leader, cx| {
6716// leader.buffer.update(cx, |multibuffer, cx| {
6717// let excerpt_ids = multibuffer.excerpt_ids();
6718// multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6719// multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6720// });
6721// });
6722
6723// // Apply the update of removing the excerpts.
6724// follower_1
6725// .update(cx, |follower, cx| {
6726// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6727// })
6728// .await
6729// .unwrap();
6730// follower_2
6731// .update(cx, |follower, cx| {
6732// follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6733// })
6734// .await
6735// .unwrap();
6736// update_message.borrow_mut().take();
6737// assert_eq!(
6738// follower_1.update(cx, |editor, cx| editor.text(cx)),
6739// leader.update(cx, |editor, cx| editor.text(cx))
6740// );
6741// }
6742
6743#[test]
6744fn test_combine_syntax_and_fuzzy_match_highlights() {
6745 let string = "abcdefghijklmnop";
6746 let syntax_ranges = [
6747 (
6748 0..3,
6749 HighlightStyle {
6750 color: Some(Hsla::red()),
6751 ..Default::default()
6752 },
6753 ),
6754 (
6755 4..8,
6756 HighlightStyle {
6757 color: Some(Hsla::green()),
6758 ..Default::default()
6759 },
6760 ),
6761 ];
6762 let match_indices = [4, 6, 7, 8];
6763 assert_eq!(
6764 combine_syntax_and_fuzzy_match_highlights(
6765 string,
6766 Default::default(),
6767 syntax_ranges.into_iter(),
6768 &match_indices,
6769 ),
6770 &[
6771 (
6772 0..3,
6773 HighlightStyle {
6774 color: Some(Hsla::red()),
6775 ..Default::default()
6776 },
6777 ),
6778 (
6779 4..5,
6780 HighlightStyle {
6781 color: Some(Hsla::green()),
6782 font_weight: Some(gpui::FontWeight::BOLD),
6783 ..Default::default()
6784 },
6785 ),
6786 (
6787 5..6,
6788 HighlightStyle {
6789 color: Some(Hsla::green()),
6790 ..Default::default()
6791 },
6792 ),
6793 (
6794 6..8,
6795 HighlightStyle {
6796 color: Some(Hsla::green()),
6797 font_weight: Some(gpui::FontWeight::BOLD),
6798 ..Default::default()
6799 },
6800 ),
6801 (
6802 8..9,
6803 HighlightStyle {
6804 font_weight: Some(gpui::FontWeight::BOLD),
6805 ..Default::default()
6806 },
6807 ),
6808 ]
6809 );
6810}
6811
6812#[gpui::test]
6813async fn go_to_prev_overlapping_diagnostic(
6814 executor: BackgroundExecutor,
6815 cx: &mut gpui::TestAppContext,
6816) {
6817 init_test(cx, |_| {});
6818
6819 let mut cx = EditorTestContext::new(cx).await;
6820 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
6821
6822 cx.set_state(indoc! {"
6823 ˇfn func(abc def: i32) -> u32 {
6824 }
6825 "});
6826
6827 cx.update(|cx| {
6828 project.update(cx, |project, cx| {
6829 project
6830 .update_diagnostics(
6831 LanguageServerId(0),
6832 lsp::PublishDiagnosticsParams {
6833 uri: lsp::Url::from_file_path("/root/file").unwrap(),
6834 version: None,
6835 diagnostics: vec![
6836 lsp::Diagnostic {
6837 range: lsp::Range::new(
6838 lsp::Position::new(0, 11),
6839 lsp::Position::new(0, 12),
6840 ),
6841 severity: Some(lsp::DiagnosticSeverity::ERROR),
6842 ..Default::default()
6843 },
6844 lsp::Diagnostic {
6845 range: lsp::Range::new(
6846 lsp::Position::new(0, 12),
6847 lsp::Position::new(0, 15),
6848 ),
6849 severity: Some(lsp::DiagnosticSeverity::ERROR),
6850 ..Default::default()
6851 },
6852 lsp::Diagnostic {
6853 range: lsp::Range::new(
6854 lsp::Position::new(0, 25),
6855 lsp::Position::new(0, 28),
6856 ),
6857 severity: Some(lsp::DiagnosticSeverity::ERROR),
6858 ..Default::default()
6859 },
6860 ],
6861 },
6862 &[],
6863 cx,
6864 )
6865 .unwrap()
6866 });
6867 });
6868
6869 executor.run_until_parked();
6870
6871 cx.update_editor(|editor, cx| {
6872 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6873 });
6874
6875 cx.assert_editor_state(indoc! {"
6876 fn func(abc def: i32) -> ˇu32 {
6877 }
6878 "});
6879
6880 cx.update_editor(|editor, cx| {
6881 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6882 });
6883
6884 cx.assert_editor_state(indoc! {"
6885 fn func(abc ˇdef: i32) -> u32 {
6886 }
6887 "});
6888
6889 cx.update_editor(|editor, cx| {
6890 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6891 });
6892
6893 cx.assert_editor_state(indoc! {"
6894 fn func(abcˇ def: i32) -> u32 {
6895 }
6896 "});
6897
6898 cx.update_editor(|editor, cx| {
6899 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6900 });
6901
6902 cx.assert_editor_state(indoc! {"
6903 fn func(abc def: i32) -> ˇu32 {
6904 }
6905 "});
6906}
6907
6908#[gpui::test]
6909async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
6910 init_test(cx, |_| {});
6911
6912 let mut cx = EditorTestContext::new(cx).await;
6913
6914 let diff_base = r#"
6915 use some::mod;
6916
6917 const A: u32 = 42;
6918
6919 fn main() {
6920 println!("hello");
6921
6922 println!("world");
6923 }
6924 "#
6925 .unindent();
6926
6927 // Edits are modified, removed, modified, added
6928 cx.set_state(
6929 &r#"
6930 use some::modified;
6931
6932 ˇ
6933 fn main() {
6934 println!("hello there");
6935
6936 println!("around the");
6937 println!("world");
6938 }
6939 "#
6940 .unindent(),
6941 );
6942
6943 cx.set_diff_base(Some(&diff_base));
6944 executor.run_until_parked();
6945
6946 cx.update_editor(|editor, cx| {
6947 //Wrap around the bottom of the buffer
6948 for _ in 0..3 {
6949 editor.go_to_hunk(&GoToHunk, cx);
6950 }
6951 });
6952
6953 cx.assert_editor_state(
6954 &r#"
6955 ˇuse some::modified;
6956
6957
6958 fn main() {
6959 println!("hello there");
6960
6961 println!("around the");
6962 println!("world");
6963 }
6964 "#
6965 .unindent(),
6966 );
6967
6968 cx.update_editor(|editor, cx| {
6969 //Wrap around the top of the buffer
6970 for _ in 0..2 {
6971 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6972 }
6973 });
6974
6975 cx.assert_editor_state(
6976 &r#"
6977 use some::modified;
6978
6979
6980 fn main() {
6981 ˇ println!("hello there");
6982
6983 println!("around the");
6984 println!("world");
6985 }
6986 "#
6987 .unindent(),
6988 );
6989
6990 cx.update_editor(|editor, cx| {
6991 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6992 });
6993
6994 cx.assert_editor_state(
6995 &r#"
6996 use some::modified;
6997
6998 ˇ
6999 fn main() {
7000 println!("hello there");
7001
7002 println!("around the");
7003 println!("world");
7004 }
7005 "#
7006 .unindent(),
7007 );
7008
7009 cx.update_editor(|editor, cx| {
7010 for _ in 0..3 {
7011 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7012 }
7013 });
7014
7015 cx.assert_editor_state(
7016 &r#"
7017 use some::modified;
7018
7019
7020 fn main() {
7021 ˇ println!("hello there");
7022
7023 println!("around the");
7024 println!("world");
7025 }
7026 "#
7027 .unindent(),
7028 );
7029
7030 cx.update_editor(|editor, cx| {
7031 editor.fold(&Fold, cx);
7032
7033 //Make sure that the fold only gets one hunk
7034 for _ in 0..4 {
7035 editor.go_to_hunk(&GoToHunk, cx);
7036 }
7037 });
7038
7039 cx.assert_editor_state(
7040 &r#"
7041 ˇuse some::modified;
7042
7043
7044 fn main() {
7045 println!("hello there");
7046
7047 println!("around the");
7048 println!("world");
7049 }
7050 "#
7051 .unindent(),
7052 );
7053}
7054
7055#[test]
7056fn test_split_words() {
7057 fn split<'a>(text: &'a str) -> Vec<&'a str> {
7058 split_words(text).collect()
7059 }
7060
7061 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7062 assert_eq!(split("hello_world"), &["hello_", "world"]);
7063 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7064 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7065 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7066 assert_eq!(split("helloworld"), &["helloworld"]);
7067}
7068
7069#[gpui::test]
7070async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7071 init_test(cx, |_| {});
7072
7073 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7074 let mut assert = |before, after| {
7075 let _state_context = cx.set_state(before);
7076 cx.update_editor(|editor, cx| {
7077 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7078 });
7079 cx.assert_editor_state(after);
7080 };
7081
7082 // Outside bracket jumps to outside of matching bracket
7083 assert("console.logˇ(var);", "console.log(var)ˇ;");
7084 assert("console.log(var)ˇ;", "console.logˇ(var);");
7085
7086 // Inside bracket jumps to inside of matching bracket
7087 assert("console.log(ˇvar);", "console.log(varˇ);");
7088 assert("console.log(varˇ);", "console.log(ˇvar);");
7089
7090 // When outside a bracket and inside, favor jumping to the inside bracket
7091 assert(
7092 "console.log('foo', [1, 2, 3]ˇ);",
7093 "console.log(ˇ'foo', [1, 2, 3]);",
7094 );
7095 assert(
7096 "console.log(ˇ'foo', [1, 2, 3]);",
7097 "console.log('foo', [1, 2, 3]ˇ);",
7098 );
7099
7100 // Bias forward if two options are equally likely
7101 assert(
7102 "let result = curried_fun()ˇ();",
7103 "let result = curried_fun()()ˇ;",
7104 );
7105
7106 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7107 assert(
7108 indoc! {"
7109 function test() {
7110 console.log('test')ˇ
7111 }"},
7112 indoc! {"
7113 function test() {
7114 console.logˇ('test')
7115 }"},
7116 );
7117}
7118
7119// todo!(completions)
7120// #[gpui::test(iterations = 10)]
7121// async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7122// init_test(cx, |_| {});
7123
7124// let (copilot, copilot_lsp) = Copilot::fake(cx);
7125// cx.update(|cx| cx.set_global(copilot));
7126// let mut cx = EditorLspTestContext::new_rust(
7127// lsp::ServerCapabilities {
7128// completion_provider: Some(lsp::CompletionOptions {
7129// trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7130// ..Default::default()
7131// }),
7132// ..Default::default()
7133// },
7134// cx,
7135// )
7136// .await;
7137
7138// // When inserting, ensure autocompletion is favored over Copilot suggestions.
7139// cx.set_state(indoc! {"
7140// oneˇ
7141// two
7142// three
7143// "});
7144// cx.simulate_keystroke(".");
7145// let _ = handle_completion_request(
7146// &mut cx,
7147// indoc! {"
7148// one.|<>
7149// two
7150// three
7151// "},
7152// vec!["completion_a", "completion_b"],
7153// );
7154// handle_copilot_completion_request(
7155// &copilot_lsp,
7156// vec![copilot::request::Completion {
7157// text: "one.copilot1".into(),
7158// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7159// ..Default::default()
7160// }],
7161// vec![],
7162// );
7163// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7164// cx.update_editor(|editor, cx| {
7165// assert!(editor.context_menu_visible());
7166// assert!(!editor.has_active_copilot_suggestion(cx));
7167
7168// // Confirming a completion inserts it and hides the context menu, without showing
7169// // the copilot suggestion afterwards.
7170// editor
7171// .confirm_completion(&Default::default(), cx)
7172// .unwrap()
7173// .detach();
7174// assert!(!editor.context_menu_visible());
7175// assert!(!editor.has_active_copilot_suggestion(cx));
7176// assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
7177// assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
7178// });
7179
7180// // Ensure Copilot suggestions are shown right away if no autocompletion is available.
7181// cx.set_state(indoc! {"
7182// oneˇ
7183// two
7184// three
7185// "});
7186// cx.simulate_keystroke(".");
7187// let _ = handle_completion_request(
7188// &mut cx,
7189// indoc! {"
7190// one.|<>
7191// two
7192// three
7193// "},
7194// vec![],
7195// );
7196// handle_copilot_completion_request(
7197// &copilot_lsp,
7198// vec![copilot::request::Completion {
7199// text: "one.copilot1".into(),
7200// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7201// ..Default::default()
7202// }],
7203// vec![],
7204// );
7205// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7206// cx.update_editor(|editor, cx| {
7207// assert!(!editor.context_menu_visible());
7208// assert!(editor.has_active_copilot_suggestion(cx));
7209// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7210// assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7211// });
7212
7213// // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
7214// cx.set_state(indoc! {"
7215// oneˇ
7216// two
7217// three
7218// "});
7219// cx.simulate_keystroke(".");
7220// let _ = handle_completion_request(
7221// &mut cx,
7222// indoc! {"
7223// one.|<>
7224// two
7225// three
7226// "},
7227// vec!["completion_a", "completion_b"],
7228// );
7229// handle_copilot_completion_request(
7230// &copilot_lsp,
7231// vec![copilot::request::Completion {
7232// text: "one.copilot1".into(),
7233// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7234// ..Default::default()
7235// }],
7236// vec![],
7237// );
7238// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7239// cx.update_editor(|editor, cx| {
7240// assert!(editor.context_menu_visible());
7241// assert!(!editor.has_active_copilot_suggestion(cx));
7242
7243// // When hiding the context menu, the Copilot suggestion becomes visible.
7244// editor.hide_context_menu(cx);
7245// assert!(!editor.context_menu_visible());
7246// assert!(editor.has_active_copilot_suggestion(cx));
7247// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7248// assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7249// });
7250
7251// // Ensure existing completion is interpolated when inserting again.
7252// cx.simulate_keystroke("c");
7253// executor.run_until_parked();
7254// cx.update_editor(|editor, cx| {
7255// assert!(!editor.context_menu_visible());
7256// assert!(editor.has_active_copilot_suggestion(cx));
7257// assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7258// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7259// });
7260
7261// // After debouncing, new Copilot completions should be requested.
7262// handle_copilot_completion_request(
7263// &copilot_lsp,
7264// vec![copilot::request::Completion {
7265// text: "one.copilot2".into(),
7266// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7267// ..Default::default()
7268// }],
7269// vec![],
7270// );
7271// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7272// cx.update_editor(|editor, cx| {
7273// assert!(!editor.context_menu_visible());
7274// assert!(editor.has_active_copilot_suggestion(cx));
7275// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7276// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7277
7278// // Canceling should remove the active Copilot suggestion.
7279// editor.cancel(&Default::default(), cx);
7280// assert!(!editor.has_active_copilot_suggestion(cx));
7281// assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7282// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7283
7284// // After canceling, tabbing shouldn't insert the previously shown suggestion.
7285// editor.tab(&Default::default(), cx);
7286// assert!(!editor.has_active_copilot_suggestion(cx));
7287// assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
7288// assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
7289
7290// // When undoing the previously active suggestion is shown again.
7291// editor.undo(&Default::default(), cx);
7292// assert!(editor.has_active_copilot_suggestion(cx));
7293// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7294// assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7295// });
7296
7297// // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7298// cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7299// cx.update_editor(|editor, cx| {
7300// assert!(editor.has_active_copilot_suggestion(cx));
7301// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7302// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7303
7304// // Tabbing when there is an active suggestion inserts it.
7305// editor.tab(&Default::default(), cx);
7306// assert!(!editor.has_active_copilot_suggestion(cx));
7307// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7308// assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7309
7310// // When undoing the previously active suggestion is shown again.
7311// editor.undo(&Default::default(), cx);
7312// assert!(editor.has_active_copilot_suggestion(cx));
7313// assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7314// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7315
7316// // Hide suggestion.
7317// editor.cancel(&Default::default(), cx);
7318// assert!(!editor.has_active_copilot_suggestion(cx));
7319// assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7320// assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7321// });
7322
7323// // If an edit occurs outside of this editor but no suggestion is being shown,
7324// // we won't make it visible.
7325// cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7326// cx.update_editor(|editor, cx| {
7327// assert!(!editor.has_active_copilot_suggestion(cx));
7328// assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7329// assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7330// });
7331
7332// // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7333// cx.update_editor(|editor, cx| {
7334// editor.set_text("fn foo() {\n \n}", cx);
7335// editor.change_selections(None, cx, |s| {
7336// s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7337// });
7338// });
7339// handle_copilot_completion_request(
7340// &copilot_lsp,
7341// vec![copilot::request::Completion {
7342// text: " let x = 4;".into(),
7343// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7344// ..Default::default()
7345// }],
7346// vec![],
7347// );
7348
7349// cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7350// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7351// cx.update_editor(|editor, cx| {
7352// assert!(editor.has_active_copilot_suggestion(cx));
7353// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7354// assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7355
7356// // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7357// editor.tab(&Default::default(), cx);
7358// assert!(editor.has_active_copilot_suggestion(cx));
7359// assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7360// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7361
7362// // Tabbing again accepts the suggestion.
7363// editor.tab(&Default::default(), cx);
7364// assert!(!editor.has_active_copilot_suggestion(cx));
7365// assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7366// assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7367// });
7368// }
7369
7370#[gpui::test]
7371async fn test_copilot_completion_invalidation(
7372 executor: BackgroundExecutor,
7373 cx: &mut gpui::TestAppContext,
7374) {
7375 init_test(cx, |_| {});
7376
7377 let (copilot, copilot_lsp) = Copilot::fake(cx);
7378 cx.update(|cx| cx.set_global(copilot));
7379 let mut cx = EditorLspTestContext::new_rust(
7380 lsp::ServerCapabilities {
7381 completion_provider: Some(lsp::CompletionOptions {
7382 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7383 ..Default::default()
7384 }),
7385 ..Default::default()
7386 },
7387 cx,
7388 )
7389 .await;
7390
7391 cx.set_state(indoc! {"
7392 one
7393 twˇ
7394 three
7395 "});
7396
7397 handle_copilot_completion_request(
7398 &copilot_lsp,
7399 vec![copilot::request::Completion {
7400 text: "two.foo()".into(),
7401 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7402 ..Default::default()
7403 }],
7404 vec![],
7405 );
7406 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7407 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7408 cx.update_editor(|editor, cx| {
7409 assert!(editor.has_active_copilot_suggestion(cx));
7410 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7411 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7412
7413 editor.backspace(&Default::default(), cx);
7414 assert!(editor.has_active_copilot_suggestion(cx));
7415 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7416 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7417
7418 editor.backspace(&Default::default(), cx);
7419 assert!(editor.has_active_copilot_suggestion(cx));
7420 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7421 assert_eq!(editor.text(cx), "one\n\nthree\n");
7422
7423 // Deleting across the original suggestion range invalidates it.
7424 editor.backspace(&Default::default(), cx);
7425 assert!(!editor.has_active_copilot_suggestion(cx));
7426 assert_eq!(editor.display_text(cx), "one\nthree\n");
7427 assert_eq!(editor.text(cx), "one\nthree\n");
7428
7429 // Undoing the deletion restores the suggestion.
7430 editor.undo(&Default::default(), cx);
7431 assert!(editor.has_active_copilot_suggestion(cx));
7432 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7433 assert_eq!(editor.text(cx), "one\n\nthree\n");
7434 });
7435}
7436
7437//todo!()
7438// #[gpui::test]
7439// async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7440// init_test(cx, |_| {});
7441
7442// let (copilot, copilot_lsp) = Copilot::fake(cx);
7443// cx.update(|cx| cx.set_global(copilot));
7444
7445// let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
7446// let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
7447// let multibuffer = cx.build_model(|cx| {
7448// let mut multibuffer = MultiBuffer::new(0);
7449// multibuffer.push_excerpts(
7450// buffer_1.clone(),
7451// [ExcerptRange {
7452// context: Point::new(0, 0)..Point::new(2, 0),
7453// primary: None,
7454// }],
7455// cx,
7456// );
7457// multibuffer.push_excerpts(
7458// buffer_2.clone(),
7459// [ExcerptRange {
7460// context: Point::new(0, 0)..Point::new(2, 0),
7461// primary: None,
7462// }],
7463// cx,
7464// );
7465// multibuffer
7466// });
7467// let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7468
7469// handle_copilot_completion_request(
7470// &copilot_lsp,
7471// vec![copilot::request::Completion {
7472// text: "b = 2 + a".into(),
7473// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7474// ..Default::default()
7475// }],
7476// vec![],
7477// );
7478// editor.update(cx, |editor, cx| {
7479// // Ensure copilot suggestions are shown for the first excerpt.
7480// editor.change_selections(None, cx, |s| {
7481// s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7482// });
7483// editor.next_copilot_suggestion(&Default::default(), cx);
7484// });
7485// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7486// editor.update(cx, |editor, cx| {
7487// assert!(editor.has_active_copilot_suggestion(cx));
7488// assert_eq!(
7489// editor.display_text(cx),
7490// "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7491// );
7492// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7493// });
7494
7495// handle_copilot_completion_request(
7496// &copilot_lsp,
7497// vec![copilot::request::Completion {
7498// text: "d = 4 + c".into(),
7499// range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7500// ..Default::default()
7501// }],
7502// vec![],
7503// );
7504// editor.update(cx, |editor, cx| {
7505// // Move to another excerpt, ensuring the suggestion gets cleared.
7506// editor.change_selections(None, cx, |s| {
7507// s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7508// });
7509// assert!(!editor.has_active_copilot_suggestion(cx));
7510// assert_eq!(
7511// editor.display_text(cx),
7512// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7513// );
7514// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7515
7516// // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7517// editor.handle_input(" ", cx);
7518// assert!(!editor.has_active_copilot_suggestion(cx));
7519// assert_eq!(
7520// editor.display_text(cx),
7521// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7522// );
7523// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7524// });
7525
7526// // Ensure the new suggestion is displayed when the debounce timeout expires.
7527// executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7528// editor.update(cx, |editor, cx| {
7529// assert!(editor.has_active_copilot_suggestion(cx));
7530// assert_eq!(
7531// editor.display_text(cx),
7532// "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7533// );
7534// assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7535// });
7536// }
7537
7538#[gpui::test]
7539async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7540 init_test(cx, |settings| {
7541 settings
7542 .copilot
7543 .get_or_insert(Default::default())
7544 .disabled_globs = Some(vec![".env*".to_string()]);
7545 });
7546
7547 let (copilot, copilot_lsp) = Copilot::fake(cx);
7548 cx.update(|cx| cx.set_global(copilot));
7549
7550 let fs = FakeFs::new(cx.executor());
7551 fs.insert_tree(
7552 "/test",
7553 json!({
7554 ".env": "SECRET=something\n",
7555 "README.md": "hello\n"
7556 }),
7557 )
7558 .await;
7559 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7560
7561 let private_buffer = project
7562 .update(cx, |project, cx| {
7563 project.open_local_buffer("/test/.env", cx)
7564 })
7565 .await
7566 .unwrap();
7567 let public_buffer = project
7568 .update(cx, |project, cx| {
7569 project.open_local_buffer("/test/README.md", cx)
7570 })
7571 .await
7572 .unwrap();
7573
7574 let multibuffer = cx.build_model(|cx| {
7575 let mut multibuffer = MultiBuffer::new(0);
7576 multibuffer.push_excerpts(
7577 private_buffer.clone(),
7578 [ExcerptRange {
7579 context: Point::new(0, 0)..Point::new(1, 0),
7580 primary: None,
7581 }],
7582 cx,
7583 );
7584 multibuffer.push_excerpts(
7585 public_buffer.clone(),
7586 [ExcerptRange {
7587 context: Point::new(0, 0)..Point::new(1, 0),
7588 primary: None,
7589 }],
7590 cx,
7591 );
7592 multibuffer
7593 });
7594 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7595
7596 let mut copilot_requests = copilot_lsp
7597 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7598 Ok(copilot::request::GetCompletionsResult {
7599 completions: vec![copilot::request::Completion {
7600 text: "next line".into(),
7601 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7602 ..Default::default()
7603 }],
7604 })
7605 });
7606
7607 editor.update(cx, |editor, cx| {
7608 editor.change_selections(None, cx, |selections| {
7609 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7610 });
7611 editor.next_copilot_suggestion(&Default::default(), cx);
7612 });
7613
7614 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7615 assert!(copilot_requests.try_next().is_err());
7616
7617 editor.update(cx, |editor, cx| {
7618 editor.change_selections(None, cx, |s| {
7619 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7620 });
7621 editor.next_copilot_suggestion(&Default::default(), cx);
7622 });
7623
7624 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7625 assert!(copilot_requests.try_next().is_ok());
7626}
7627
7628#[gpui::test]
7629async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7630 init_test(cx, |_| {});
7631
7632 let mut language = Language::new(
7633 LanguageConfig {
7634 name: "Rust".into(),
7635 path_suffixes: vec!["rs".to_string()],
7636 brackets: BracketPairConfig {
7637 pairs: vec![BracketPair {
7638 start: "{".to_string(),
7639 end: "}".to_string(),
7640 close: true,
7641 newline: true,
7642 }],
7643 disabled_scopes_by_bracket_ix: Vec::new(),
7644 },
7645 ..Default::default()
7646 },
7647 Some(tree_sitter_rust::language()),
7648 );
7649 let mut fake_servers = language
7650 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7651 capabilities: lsp::ServerCapabilities {
7652 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7653 first_trigger_character: "{".to_string(),
7654 more_trigger_character: None,
7655 }),
7656 ..Default::default()
7657 },
7658 ..Default::default()
7659 }))
7660 .await;
7661
7662 let fs = FakeFs::new(cx.executor());
7663 fs.insert_tree(
7664 "/a",
7665 json!({
7666 "main.rs": "fn main() { let a = 5; }",
7667 "other.rs": "// Test file",
7668 }),
7669 )
7670 .await;
7671 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7672 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7673 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7674
7675 let cx = &mut VisualTestContext::from_window(*workspace, cx);
7676
7677 let worktree_id = workspace
7678 .update(cx, |workspace, cx| {
7679 workspace.project().update(cx, |project, cx| {
7680 project.worktrees().next().unwrap().read(cx).id()
7681 })
7682 })
7683 .unwrap();
7684
7685 let buffer = project
7686 .update(cx, |project, cx| {
7687 project.open_local_buffer("/a/main.rs", cx)
7688 })
7689 .await
7690 .unwrap();
7691 cx.executor().run_until_parked();
7692 cx.executor().start_waiting();
7693 let fake_server = fake_servers.next().await.unwrap();
7694 let editor_handle = workspace
7695 .update(cx, |workspace, cx| {
7696 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7697 })
7698 .unwrap()
7699 .await
7700 .unwrap()
7701 .downcast::<Editor>()
7702 .unwrap();
7703
7704 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7705 assert_eq!(
7706 params.text_document_position.text_document.uri,
7707 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7708 );
7709 assert_eq!(
7710 params.text_document_position.position,
7711 lsp::Position::new(0, 21),
7712 );
7713
7714 Ok(Some(vec![lsp::TextEdit {
7715 new_text: "]".to_string(),
7716 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7717 }]))
7718 });
7719
7720 editor_handle.update(cx, |editor, cx| {
7721 editor.focus(cx);
7722 editor.change_selections(None, cx, |s| {
7723 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7724 });
7725 editor.handle_input("{", cx);
7726 });
7727
7728 cx.executor().run_until_parked();
7729
7730 buffer.update(cx, |buffer, _| {
7731 assert_eq!(
7732 buffer.text(),
7733 "fn main() { let a = {5}; }",
7734 "No extra braces from on type formatting should appear in the buffer"
7735 )
7736 });
7737}
7738
7739#[gpui::test]
7740async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7741 init_test(cx, |_| {});
7742
7743 let language_name: Arc<str> = "Rust".into();
7744 let mut language = Language::new(
7745 LanguageConfig {
7746 name: Arc::clone(&language_name),
7747 path_suffixes: vec!["rs".to_string()],
7748 ..Default::default()
7749 },
7750 Some(tree_sitter_rust::language()),
7751 );
7752
7753 let server_restarts = Arc::new(AtomicUsize::new(0));
7754 let closure_restarts = Arc::clone(&server_restarts);
7755 let language_server_name = "test language server";
7756 let mut fake_servers = language
7757 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7758 name: language_server_name,
7759 initialization_options: Some(json!({
7760 "testOptionValue": true
7761 })),
7762 initializer: Some(Box::new(move |fake_server| {
7763 let task_restarts = Arc::clone(&closure_restarts);
7764 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7765 task_restarts.fetch_add(1, atomic::Ordering::Release);
7766 futures::future::ready(Ok(()))
7767 });
7768 })),
7769 ..Default::default()
7770 }))
7771 .await;
7772
7773 let fs = FakeFs::new(cx.executor());
7774 fs.insert_tree(
7775 "/a",
7776 json!({
7777 "main.rs": "fn main() { let a = 5; }",
7778 "other.rs": "// Test file",
7779 }),
7780 )
7781 .await;
7782 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7783 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7784 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7785 let _buffer = project
7786 .update(cx, |project, cx| {
7787 project.open_local_buffer("/a/main.rs", cx)
7788 })
7789 .await
7790 .unwrap();
7791 let _fake_server = fake_servers.next().await.unwrap();
7792 update_test_language_settings(cx, |language_settings| {
7793 language_settings.languages.insert(
7794 Arc::clone(&language_name),
7795 LanguageSettingsContent {
7796 tab_size: NonZeroU32::new(8),
7797 ..Default::default()
7798 },
7799 );
7800 });
7801 cx.executor().run_until_parked();
7802 assert_eq!(
7803 server_restarts.load(atomic::Ordering::Acquire),
7804 0,
7805 "Should not restart LSP server on an unrelated change"
7806 );
7807
7808 update_test_project_settings(cx, |project_settings| {
7809 project_settings.lsp.insert(
7810 "Some other server name".into(),
7811 LspSettings {
7812 initialization_options: Some(json!({
7813 "some other init value": false
7814 })),
7815 },
7816 );
7817 });
7818 cx.executor().run_until_parked();
7819 assert_eq!(
7820 server_restarts.load(atomic::Ordering::Acquire),
7821 0,
7822 "Should not restart LSP server on an unrelated LSP settings change"
7823 );
7824
7825 update_test_project_settings(cx, |project_settings| {
7826 project_settings.lsp.insert(
7827 language_server_name.into(),
7828 LspSettings {
7829 initialization_options: Some(json!({
7830 "anotherInitValue": false
7831 })),
7832 },
7833 );
7834 });
7835 cx.executor().run_until_parked();
7836 assert_eq!(
7837 server_restarts.load(atomic::Ordering::Acquire),
7838 1,
7839 "Should restart LSP server on a related LSP settings change"
7840 );
7841
7842 update_test_project_settings(cx, |project_settings| {
7843 project_settings.lsp.insert(
7844 language_server_name.into(),
7845 LspSettings {
7846 initialization_options: Some(json!({
7847 "anotherInitValue": false
7848 })),
7849 },
7850 );
7851 });
7852 cx.executor().run_until_parked();
7853 assert_eq!(
7854 server_restarts.load(atomic::Ordering::Acquire),
7855 1,
7856 "Should not restart LSP server on a related LSP settings change that is the same"
7857 );
7858
7859 update_test_project_settings(cx, |project_settings| {
7860 project_settings.lsp.insert(
7861 language_server_name.into(),
7862 LspSettings {
7863 initialization_options: None,
7864 },
7865 );
7866 });
7867 cx.executor().run_until_parked();
7868 assert_eq!(
7869 server_restarts.load(atomic::Ordering::Acquire),
7870 2,
7871 "Should restart LSP server on another related LSP settings change"
7872 );
7873}
7874
7875//todo!(completions)
7876// #[gpui::test]
7877// async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7878// init_test(cx, |_| {});
7879
7880// let mut cx = EditorLspTestContext::new_rust(
7881// lsp::ServerCapabilities {
7882// completion_provider: Some(lsp::CompletionOptions {
7883// trigger_characters: Some(vec![".".to_string()]),
7884// resolve_provider: Some(true),
7885// ..Default::default()
7886// }),
7887// ..Default::default()
7888// },
7889// cx,
7890// )
7891// .await;
7892
7893// cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7894// cx.simulate_keystroke(".");
7895// let completion_item = lsp::CompletionItem {
7896// label: "some".into(),
7897// kind: Some(lsp::CompletionItemKind::SNIPPET),
7898// detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7899// documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7900// kind: lsp::MarkupKind::Markdown,
7901// value: "```rust\nSome(2)\n```".to_string(),
7902// })),
7903// deprecated: Some(false),
7904// sort_text: Some("fffffff2".to_string()),
7905// filter_text: Some("some".to_string()),
7906// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7907// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7908// range: lsp::Range {
7909// start: lsp::Position {
7910// line: 0,
7911// character: 22,
7912// },
7913// end: lsp::Position {
7914// line: 0,
7915// character: 22,
7916// },
7917// },
7918// new_text: "Some(2)".to_string(),
7919// })),
7920// additional_text_edits: Some(vec![lsp::TextEdit {
7921// range: lsp::Range {
7922// start: lsp::Position {
7923// line: 0,
7924// character: 20,
7925// },
7926// end: lsp::Position {
7927// line: 0,
7928// character: 22,
7929// },
7930// },
7931// new_text: "".to_string(),
7932// }]),
7933// ..Default::default()
7934// };
7935
7936// let closure_completion_item = completion_item.clone();
7937// let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7938// let task_completion_item = closure_completion_item.clone();
7939// async move {
7940// Ok(Some(lsp::CompletionResponse::Array(vec![
7941// task_completion_item,
7942// ])))
7943// }
7944// });
7945
7946// request.next().await;
7947
7948// cx.condition(|editor, _| editor.context_menu_visible())
7949// .await;
7950// let apply_additional_edits = cx.update_editor(|editor, cx| {
7951// editor
7952// .confirm_completion(&ConfirmCompletion::default(), cx)
7953// .unwrap()
7954// });
7955// cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7956
7957// cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7958// let task_completion_item = completion_item.clone();
7959// async move { Ok(task_completion_item) }
7960// })
7961// .next()
7962// .await
7963// .unwrap();
7964// apply_additional_edits.await.unwrap();
7965// cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7966// }
7967
7968// #[gpui::test]
7969// async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
7970// init_test(cx, |_| {});
7971
7972// let mut cx = EditorLspTestContext::new(
7973// Language::new(
7974// LanguageConfig {
7975// path_suffixes: vec!["jsx".into()],
7976// overrides: [(
7977// "element".into(),
7978// LanguageConfigOverride {
7979// word_characters: Override::Set(['-'].into_iter().collect()),
7980// ..Default::default()
7981// },
7982// )]
7983// .into_iter()
7984// .collect(),
7985// ..Default::default()
7986// },
7987// Some(tree_sitter_typescript::language_tsx()),
7988// )
7989// .with_override_query("(jsx_self_closing_element) @element")
7990// .unwrap(),
7991// lsp::ServerCapabilities {
7992// completion_provider: Some(lsp::CompletionOptions {
7993// trigger_characters: Some(vec![":".to_string()]),
7994// ..Default::default()
7995// }),
7996// ..Default::default()
7997// },
7998// cx,
7999// )
8000// .await;
8001
8002// cx.lsp
8003// .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8004// Ok(Some(lsp::CompletionResponse::Array(vec![
8005// lsp::CompletionItem {
8006// label: "bg-blue".into(),
8007// ..Default::default()
8008// },
8009// lsp::CompletionItem {
8010// label: "bg-red".into(),
8011// ..Default::default()
8012// },
8013// lsp::CompletionItem {
8014// label: "bg-yellow".into(),
8015// ..Default::default()
8016// },
8017// ])))
8018// });
8019
8020// cx.set_state(r#"<p class="bgˇ" />"#);
8021
8022// // Trigger completion when typing a dash, because the dash is an extra
8023// // word character in the 'element' scope, which contains the cursor.
8024// cx.simulate_keystroke("-");
8025// cx.executor().run_until_parked();
8026// cx.update_editor(|editor, _| {
8027// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8028// assert_eq!(
8029// menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8030// &["bg-red", "bg-blue", "bg-yellow"]
8031// );
8032// } else {
8033// panic!("expected completion menu to be open");
8034// }
8035// });
8036
8037// cx.simulate_keystroke("l");
8038// cx.executor().run_until_parked();
8039// cx.update_editor(|editor, _| {
8040// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8041// assert_eq!(
8042// menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8043// &["bg-blue", "bg-yellow"]
8044// );
8045// } else {
8046// panic!("expected completion menu to be open");
8047// }
8048// });
8049
8050// // When filtering completions, consider the character after the '-' to
8051// // be the start of a subword.
8052// cx.set_state(r#"<p class="yelˇ" />"#);
8053// cx.simulate_keystroke("l");
8054// cx.executor().run_until_parked();
8055// cx.update_editor(|editor, _| {
8056// if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8057// assert_eq!(
8058// menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8059// &["bg-yellow"]
8060// );
8061// } else {
8062// panic!("expected completion menu to be open");
8063// }
8064// });
8065// }
8066
8067#[gpui::test]
8068async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8069 init_test(cx, |settings| {
8070 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8071 });
8072
8073 let mut language = Language::new(
8074 LanguageConfig {
8075 name: "Rust".into(),
8076 path_suffixes: vec!["rs".to_string()],
8077 prettier_parser_name: Some("test_parser".to_string()),
8078 ..Default::default()
8079 },
8080 Some(tree_sitter_rust::language()),
8081 );
8082
8083 let test_plugin = "test_plugin";
8084 let _ = language
8085 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
8086 prettier_plugins: vec![test_plugin],
8087 ..Default::default()
8088 }))
8089 .await;
8090
8091 let fs = FakeFs::new(cx.executor());
8092 fs.insert_file("/file.rs", Default::default()).await;
8093
8094 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8095 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8096 project.update(cx, |project, _| {
8097 project.languages().add(Arc::new(language));
8098 });
8099 let buffer = project
8100 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8101 .await
8102 .unwrap();
8103
8104 let buffer_text = "one\ntwo\nthree\n";
8105 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
8106 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8107 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8108
8109 editor
8110 .update(cx, |editor, cx| {
8111 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8112 })
8113 .unwrap()
8114 .await;
8115 assert_eq!(
8116 editor.update(cx, |editor, cx| editor.text(cx)),
8117 buffer_text.to_string() + prettier_format_suffix,
8118 "Test prettier formatting was not applied to the original buffer text",
8119 );
8120
8121 update_test_language_settings(cx, |settings| {
8122 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8123 });
8124 let format = editor.update(cx, |editor, cx| {
8125 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8126 });
8127 format.await.unwrap();
8128 assert_eq!(
8129 editor.update(cx, |editor, cx| editor.text(cx)),
8130 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8131 "Autoformatting (via test prettier) was not applied to the original buffer text",
8132 );
8133}
8134
8135fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
8136 let point = DisplayPoint::new(row as u32, column as u32);
8137 point..point
8138}
8139
8140fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
8141 let (text, ranges) = marked_text_ranges(marked_text, true);
8142 assert_eq!(view.text(cx), text);
8143 assert_eq!(
8144 view.selections.ranges(cx),
8145 ranges,
8146 "Assert selections are {}",
8147 marked_text
8148 );
8149}
8150
8151/// Handle completion request passing a marked string specifying where the completion
8152/// should be triggered from using '|' character, what range should be replaced, and what completions
8153/// should be returned using '<' and '>' to delimit the range
8154pub fn handle_completion_request<'a>(
8155 cx: &mut EditorLspTestContext<'a>,
8156 marked_string: &str,
8157 completions: Vec<&'static str>,
8158) -> impl Future<Output = ()> {
8159 let complete_from_marker: TextRangeMarker = '|'.into();
8160 let replace_range_marker: TextRangeMarker = ('<', '>').into();
8161 let (_, mut marked_ranges) = marked_text_ranges_by(
8162 marked_string,
8163 vec![complete_from_marker.clone(), replace_range_marker.clone()],
8164 );
8165
8166 let complete_from_position =
8167 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
8168 let replace_range =
8169 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
8170
8171 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
8172 let completions = completions.clone();
8173 async move {
8174 assert_eq!(params.text_document_position.text_document.uri, url.clone());
8175 assert_eq!(
8176 params.text_document_position.position,
8177 complete_from_position
8178 );
8179 Ok(Some(lsp::CompletionResponse::Array(
8180 completions
8181 .iter()
8182 .map(|completion_text| lsp::CompletionItem {
8183 label: completion_text.to_string(),
8184 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8185 range: replace_range,
8186 new_text: completion_text.to_string(),
8187 })),
8188 ..Default::default()
8189 })
8190 .collect(),
8191 )))
8192 }
8193 });
8194
8195 async move {
8196 request.next().await;
8197 }
8198}
8199
8200fn handle_resolve_completion_request<'a>(
8201 cx: &mut EditorLspTestContext<'a>,
8202 edits: Option<Vec<(&'static str, &'static str)>>,
8203) -> impl Future<Output = ()> {
8204 let edits = edits.map(|edits| {
8205 edits
8206 .iter()
8207 .map(|(marked_string, new_text)| {
8208 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
8209 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
8210 lsp::TextEdit::new(replace_range, new_text.to_string())
8211 })
8212 .collect::<Vec<_>>()
8213 });
8214
8215 let mut request =
8216 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8217 let edits = edits.clone();
8218 async move {
8219 Ok(lsp::CompletionItem {
8220 additional_text_edits: edits,
8221 ..Default::default()
8222 })
8223 }
8224 });
8225
8226 async move {
8227 request.next().await;
8228 }
8229}
8230
8231fn handle_copilot_completion_request(
8232 lsp: &lsp::FakeLanguageServer,
8233 completions: Vec<copilot::request::Completion>,
8234 completions_cycling: Vec<copilot::request::Completion>,
8235) {
8236 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
8237 let completions = completions.clone();
8238 async move {
8239 Ok(copilot::request::GetCompletionsResult {
8240 completions: completions.clone(),
8241 })
8242 }
8243 });
8244 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
8245 let completions_cycling = completions_cycling.clone();
8246 async move {
8247 Ok(copilot::request::GetCompletionsResult {
8248 completions: completions_cycling.clone(),
8249 })
8250 }
8251 });
8252}
8253
8254pub(crate) fn update_test_language_settings(
8255 cx: &mut TestAppContext,
8256 f: impl Fn(&mut AllLanguageSettingsContent),
8257) {
8258 cx.update(|cx| {
8259 cx.update_global(|store: &mut SettingsStore, cx| {
8260 store.update_user_settings::<AllLanguageSettings>(cx, f);
8261 });
8262 });
8263}
8264
8265pub(crate) fn update_test_project_settings(
8266 cx: &mut TestAppContext,
8267 f: impl Fn(&mut ProjectSettings),
8268) {
8269 cx.update(|cx| {
8270 cx.update_global(|store: &mut SettingsStore, cx| {
8271 store.update_user_settings::<ProjectSettings>(cx, f);
8272 });
8273 });
8274}
8275
8276pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
8277 cx.update(|cx| {
8278 let store = SettingsStore::test(cx);
8279 cx.set_global(store);
8280 theme::init(theme::LoadThemes::JustBase, cx);
8281 client::init_settings(cx);
8282 language::init(cx);
8283 Project::init_settings(cx);
8284 workspace::init_settings(cx);
8285 crate::init(cx);
8286 });
8287
8288 update_test_language_settings(cx, f);
8289}