test_shape_line_numbers

Piotr Osiewicz created

Change summary

crates/editor2/src/element.rs | 893 ++++++++++++++++++------------------
1 file changed, 451 insertions(+), 442 deletions(-)

Detailed changes

crates/editor2/src/element.rs 🔗

@@ -3227,448 +3227,457 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
     (delta.pow(1.2) / 300.0).into()
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use super::*;
-//     use crate::{
-//         display_map::{BlockDisposition, BlockProperties},
-//         editor_tests::{init_test, update_test_language_settings},
-//         Editor, MultiBuffer,
-//     };
-//     use gpui::TestAppContext;
-//     use language::language_settings;
-//     use log::info;
-//     use std::{num::NonZeroU32, sync::Arc};
-//     use util::test::sample_text;
-
-//     #[gpui::test]
-//     fn test_layout_line_numbers(cx: &mut TestAppContext) {
-//         init_test(cx, |_| {});
-//         let editor = cx
-//             .add_window(|cx| {
-//                 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
-//                 Editor::new(EditorMode::Full, buffer, None, None, cx)
-//             })
-//             .root(cx);
-//         let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
-
-//         let layouts = editor.update(cx, |editor, cx| {
-//             let snapshot = editor.snapshot(cx);
-//             element
-//                 .layout_line_numbers(
-//                     0..6,
-//                     &Default::default(),
-//                     DisplayPoint::new(0, 0),
-//                     false,
-//                     &snapshot,
-//                     cx,
-//                 )
-//                 .0
-//         });
-//         assert_eq!(layouts.len(), 6);
-
-//         let relative_rows = editor.update(cx, |editor, cx| {
-//             let snapshot = editor.snapshot(cx);
-//             element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
-//         });
-//         assert_eq!(relative_rows[&0], 3);
-//         assert_eq!(relative_rows[&1], 2);
-//         assert_eq!(relative_rows[&2], 1);
-//         // current line has no relative number
-//         assert_eq!(relative_rows[&4], 1);
-//         assert_eq!(relative_rows[&5], 2);
-
-//         // works if cursor is before screen
-//         let relative_rows = editor.update(cx, |editor, cx| {
-//             let snapshot = editor.snapshot(cx);
-
-//             element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
-//         });
-//         assert_eq!(relative_rows.len(), 3);
-//         assert_eq!(relative_rows[&3], 2);
-//         assert_eq!(relative_rows[&4], 3);
-//         assert_eq!(relative_rows[&5], 4);
-
-//         // works if cursor is after screen
-//         let relative_rows = editor.update(cx, |editor, cx| {
-//             let snapshot = editor.snapshot(cx);
-
-//             element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
-//         });
-//         assert_eq!(relative_rows.len(), 3);
-//         assert_eq!(relative_rows[&0], 5);
-//         assert_eq!(relative_rows[&1], 4);
-//         assert_eq!(relative_rows[&2], 3);
-//     }
-
-//     #[gpui::test]
-//     async fn test_vim_visual_selections(cx: &mut TestAppContext) {
-//         init_test(cx, |_| {});
-
-//         let editor = cx
-//             .add_window(|cx| {
-//                 let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
-//                 Editor::new(EditorMode::Full, buffer, None, None, cx)
-//             })
-//             .root(cx);
-//         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
-//         let (_, state) = editor.update(cx, |editor, cx| {
-//             editor.cursor_shape = CursorShape::Block;
-//             editor.change_selections(None, cx, |s| {
-//                 s.select_ranges([
-//                     Point::new(0, 0)..Point::new(1, 0),
-//                     Point::new(3, 2)..Point::new(3, 3),
-//                     Point::new(5, 6)..Point::new(6, 0),
-//                 ]);
-//             });
-//             element.layout(
-//                 SizeConstraint::new(point(500., 500.), point(500., 500.)),
-//                 editor,
-//                 cx,
-//             )
-//         });
-//         assert_eq!(state.selections.len(), 1);
-//         let local_selections = &state.selections[0].1;
-//         assert_eq!(local_selections.len(), 3);
-//         // moves cursor back one line
-//         assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
-//         assert_eq!(
-//             local_selections[0].range,
-//             DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
-//         );
-
-//         // moves cursor back one column
-//         assert_eq!(
-//             local_selections[1].range,
-//             DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
-//         );
-//         assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
-
-//         // leaves cursor on the max point
-//         assert_eq!(
-//             local_selections[2].range,
-//             DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
-//         );
-//         assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
-
-//         // active lines does not include 1 (even though the range of the selection does)
-//         assert_eq!(
-//             state.active_rows.keys().cloned().collect::<Vec<u32>>(),
-//             vec![0, 3, 5, 6]
-//         );
-
-//         // multi-buffer support
-//         // in DisplayPoint co-ordinates, this is what we're dealing with:
-//         //  0: [[file
-//         //  1:   header]]
-//         //  2: aaaaaa
-//         //  3: bbbbbb
-//         //  4: cccccc
-//         //  5:
-//         //  6: ...
-//         //  7: ffffff
-//         //  8: gggggg
-//         //  9: hhhhhh
-//         // 10:
-//         // 11: [[file
-//         // 12:   header]]
-//         // 13: bbbbbb
-//         // 14: cccccc
-//         // 15: dddddd
-//         let editor = cx
-//             .add_window(|cx| {
-//                 let buffer = MultiBuffer::build_multi(
-//                     [
-//                         (
-//                             &(sample_text(8, 6, 'a') + "\n"),
-//                             vec![
-//                                 Point::new(0, 0)..Point::new(3, 0),
-//                                 Point::new(4, 0)..Point::new(7, 0),
-//                             ],
-//                         ),
-//                         (
-//                             &(sample_text(8, 6, 'a') + "\n"),
-//                             vec![Point::new(1, 0)..Point::new(3, 0)],
-//                         ),
-//                     ],
-//                     cx,
-//                 );
-//                 Editor::new(EditorMode::Full, buffer, None, None, cx)
-//             })
-//             .root(cx);
-//         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
-//         let (_, state) = editor.update(cx, |editor, cx| {
-//             editor.cursor_shape = CursorShape::Block;
-//             editor.change_selections(None, cx, |s| {
-//                 s.select_display_ranges([
-//                     DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
-//                     DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
-//                 ]);
-//             });
-//             element.layout(
-//                 SizeConstraint::new(point(500., 500.), point(500., 500.)),
-//                 editor,
-//                 cx,
-//             )
-//         });
-
-//         assert_eq!(state.selections.len(), 1);
-//         let local_selections = &state.selections[0].1;
-//         assert_eq!(local_selections.len(), 2);
-
-//         // moves cursor on excerpt boundary back a line
-//         // and doesn't allow selection to bleed through
-//         assert_eq!(
-//             local_selections[0].range,
-//             DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
-//         );
-//         assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
-
-//         // moves cursor on buffer boundary back two lines
-//         // and doesn't allow selection to bleed through
-//         assert_eq!(
-//             local_selections[1].range,
-//             DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
-//         );
-//         assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
-//     }
-
-//     #[gpui::test]
-//     fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
-//         init_test(cx, |_| {});
-
-//         let editor = cx
-//             .add_window(|cx| {
-//                 let buffer = MultiBuffer::build_simple("", cx);
-//                 Editor::new(EditorMode::Full, buffer, None, None, cx)
-//             })
-//             .root(cx);
-
-//         editor.update(cx, |editor, cx| {
-//             editor.set_placeholder_text("hello", cx);
-//             editor.insert_blocks(
-//                 [BlockProperties {
-//                     style: BlockStyle::Fixed,
-//                     disposition: BlockDisposition::Above,
-//                     height: 3,
-//                     position: Anchor::min(),
-//                     render: Arc::new(|_| Empty::new().into_any),
-//                 }],
-//                 None,
-//                 cx,
-//             );
-
-//             // Blur the editor so that it displays placeholder text.
-//             cx.blur();
-//         });
-
-//         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
-//         let (size, mut state) = editor.update(cx, |editor, cx| {
-//             element.layout(
-//                 SizeConstraint::new(point(500., 500.), point(500., 500.)),
-//                 editor,
-//                 cx,
-//             )
-//         });
-
-//         assert_eq!(state.position_map.line_layouts.len(), 4);
-//         assert_eq!(
-//             state
-//                 .line_number_layouts
-//                 .iter()
-//                 .map(Option::is_some)
-//                 .collect::<Vec<_>>(),
-//             &[false, false, false, true]
-//         );
-
-//         // Don't panic.
-//         let bounds = Bounds::<Pixels>::new(Default::default(), size);
-//         editor.update(cx, |editor, cx| {
-//             element.paint(bounds, bounds, &mut state, editor, cx);
-//         });
-//     }
-
-//     #[gpui::test]
-//     fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
-//         const TAB_SIZE: u32 = 4;
-
-//         let input_text = "\t \t|\t| a b";
-//         let expected_invisibles = vec![
-//             Invisible::Tab {
-//                 line_start_offset: 0,
-//             },
-//             Invisible::Whitespace {
-//                 line_offset: TAB_SIZE as usize,
-//             },
-//             Invisible::Tab {
-//                 line_start_offset: TAB_SIZE as usize + 1,
-//             },
-//             Invisible::Tab {
-//                 line_start_offset: TAB_SIZE as usize * 2 + 1,
-//             },
-//             Invisible::Whitespace {
-//                 line_offset: TAB_SIZE as usize * 3 + 1,
-//             },
-//             Invisible::Whitespace {
-//                 line_offset: TAB_SIZE as usize * 3 + 3,
-//             },
-//         ];
-//         assert_eq!(
-//             expected_invisibles.len(),
-//             input_text
-//                 .chars()
-//                 .filter(|initial_char| initial_char.is_whitespace())
-//                 .count(),
-//             "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
-//         );
-
-//         init_test(cx, |s| {
-//             s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
-//             s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
-//         });
-
-//         let actual_invisibles =
-//             collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
-
-//         assert_eq!(expected_invisibles, actual_invisibles);
-//     }
-
-//     #[gpui::test]
-//     fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
-//         init_test(cx, |s| {
-//             s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
-//             s.defaults.tab_size = NonZeroU32::new(4);
-//         });
-
-//         for editor_mode_without_invisibles in [
-//             EditorMode::SingleLine,
-//             EditorMode::AutoHeight { max_lines: 100 },
-//         ] {
-//             let invisibles = collect_invisibles_from_new_editor(
-//                 cx,
-//                 editor_mode_without_invisibles,
-//                 "\t\t\t| | a b",
-//                 500.0,
-//             );
-//             assert!(invisibles.is_empty,
-//                 "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
-//         }
-//     }
-
-//     #[gpui::test]
-//     fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
-//         let tab_size = 4;
-//         let input_text = "a\tbcd   ".repeat(9);
-//         let repeated_invisibles = [
-//             Invisible::Tab {
-//                 line_start_offset: 1,
-//             },
-//             Invisible::Whitespace {
-//                 line_offset: tab_size as usize + 3,
-//             },
-//             Invisible::Whitespace {
-//                 line_offset: tab_size as usize + 4,
-//             },
-//             Invisible::Whitespace {
-//                 line_offset: tab_size as usize + 5,
-//             },
-//         ];
-//         let expected_invisibles = std::iter::once(repeated_invisibles)
-//             .cycle()
-//             .take(9)
-//             .flatten()
-//             .collect::<Vec<_>>();
-//         assert_eq!(
-//             expected_invisibles.len(),
-//             input_text
-//                 .chars()
-//                 .filter(|initial_char| initial_char.is_whitespace())
-//                 .count(),
-//             "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
-//         );
-//         info!("Expected invisibles: {expected_invisibles:?}");
-
-//         init_test(cx, |_| {});
-
-//         // Put the same string with repeating whitespace pattern into editors of various size,
-//         // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
-//         let resize_step = 10.0;
-//         let mut editor_width = 200.0;
-//         while editor_width <= 1000.0 {
-//             update_test_language_settings(cx, |s| {
-//                 s.defaults.tab_size = NonZeroU32::new(tab_size);
-//                 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
-//                 s.defaults.preferred_line_length = Some(editor_width as u32);
-//                 s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
-//             });
-
-//             let actual_invisibles =
-//                 collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, editor_width);
-
-//             // Whatever the editor size is, ensure it has the same invisible kinds in the same order
-//             // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
-//             let mut i = 0;
-//             for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
-//                 i = actual_index;
-//                 match expected_invisibles.get(i) {
-//                     Some(expected_invisible) => match (expected_invisible, actual_invisible) {
-//                         (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
-//                         | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
-//                         _ => {
-//                             panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
-//                         }
-//                     },
-//                     None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
-//                 }
-//             }
-//             let missing_expected_invisibles = &expected_invisibles[i + 1..];
-//             assert!(
-//                 missing_expected_invisibles.is_empty,
-//                 "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
-//             );
-
-//             editor_width += resize_step;
-//         }
-//     }
-
-//     fn collect_invisibles_from_new_editor(
-//         cx: &mut TestAppContext,
-//         editor_mode: EditorMode,
-//         input_text: &str,
-//         editor_width: f32,
-//     ) -> Vec<Invisible> {
-//         info!(
-//             "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
-//         );
-//         let editor = cx
-//             .add_window(|cx| {
-//                 let buffer = MultiBuffer::build_simple(&input_text, cx);
-//                 Editor::new(editor_mode, buffer, None, None, cx)
-//             })
-//             .root(cx);
-
-//         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
-//         let (_, layout_state) = editor.update(cx, |editor, cx| {
-//             editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
-//             editor.set_wrap_width(Some(editor_width), cx);
-
-//             element.layout(
-//                 SizeConstraint::new(point(editor_width, 500.), point(editor_width, 500.)),
-//                 editor,
-//                 cx,
-//             )
-//         });
-
-//         layout_state
-//             .position_map
-//             .line_layouts
-//             .iter()
-//             .map(|line_with_invisibles| &line_with_invisibles.invisibles)
-//             .flatten()
-//             .cloned()
-//             .collect()
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{
+        display_map::{BlockDisposition, BlockProperties},
+        editor_tests::{init_test, update_test_language_settings},
+        Editor, MultiBuffer,
+    };
+    use gpui::TestAppContext;
+    use language::language_settings;
+    use log::info;
+    use std::{num::NonZeroU32, sync::Arc};
+    use util::test::sample_text;
+
+    #[gpui::test]
+    fn test_shape_line_numbers(cx: &mut TestAppContext) {
+        init_test(cx, |_| {});
+        let window = cx.add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
+            Editor::new(EditorMode::Full, buffer, None, cx)
+        });
+
+        let editor = window.root(cx).unwrap();
+        let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
+        let element = EditorElement::new(&editor, style);
+
+        let layouts = window
+            .update(cx, |editor, cx| {
+                let snapshot = editor.snapshot(cx);
+                element
+                    .shape_line_numbers(
+                        0..6,
+                        &Default::default(),
+                        DisplayPoint::new(0, 0),
+                        false,
+                        &snapshot,
+                        cx,
+                    )
+                    .0
+            })
+            .unwrap();
+        assert_eq!(layouts.len(), 6);
+
+        let relative_rows = window
+            .update(cx, |editor, cx| {
+                let snapshot = editor.snapshot(cx);
+                element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
+            })
+            .unwrap();
+        assert_eq!(relative_rows[&0], 3);
+        assert_eq!(relative_rows[&1], 2);
+        assert_eq!(relative_rows[&2], 1);
+        // current line has no relative number
+        assert_eq!(relative_rows[&4], 1);
+        assert_eq!(relative_rows[&5], 2);
+
+        // works if cursor is before screen
+        let relative_rows = window
+            .update(cx, |editor, cx| {
+                let snapshot = editor.snapshot(cx);
+
+                element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
+            })
+            .unwrap();
+        assert_eq!(relative_rows.len(), 3);
+        assert_eq!(relative_rows[&3], 2);
+        assert_eq!(relative_rows[&4], 3);
+        assert_eq!(relative_rows[&5], 4);
+
+        // works if cursor is after screen
+        let relative_rows = window
+            .update(cx, |editor, cx| {
+                let snapshot = editor.snapshot(cx);
+
+                element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
+            })
+            .unwrap();
+        assert_eq!(relative_rows.len(), 3);
+        assert_eq!(relative_rows[&0], 5);
+        assert_eq!(relative_rows[&1], 4);
+        assert_eq!(relative_rows[&2], 3);
+    }
+
+    //     #[gpui::test]
+    //     async fn test_vim_visual_selections(cx: &mut TestAppContext) {
+    //         init_test(cx, |_| {});
+
+    //         let editor = cx
+    //             .add_window(|cx| {
+    //                 let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
+    //                 Editor::new(EditorMode::Full, buffer, None, None, cx)
+    //             })
+    //             .root(cx);
+    //         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
+    //         let (_, state) = editor.update(cx, |editor, cx| {
+    //             editor.cursor_shape = CursorShape::Block;
+    //             editor.change_selections(None, cx, |s| {
+    //                 s.select_ranges([
+    //                     Point::new(0, 0)..Point::new(1, 0),
+    //                     Point::new(3, 2)..Point::new(3, 3),
+    //                     Point::new(5, 6)..Point::new(6, 0),
+    //                 ]);
+    //             });
+    //             element.layout(
+    //                 SizeConstraint::new(point(500., 500.), point(500., 500.)),
+    //                 editor,
+    //                 cx,
+    //             )
+    //         });
+    //         assert_eq!(state.selections.len(), 1);
+    //         let local_selections = &state.selections[0].1;
+    //         assert_eq!(local_selections.len(), 3);
+    //         // moves cursor back one line
+    //         assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
+    //         assert_eq!(
+    //             local_selections[0].range,
+    //             DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
+    //         );
+
+    //         // moves cursor back one column
+    //         assert_eq!(
+    //             local_selections[1].range,
+    //             DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
+    //         );
+    //         assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
+
+    //         // leaves cursor on the max point
+    //         assert_eq!(
+    //             local_selections[2].range,
+    //             DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
+    //         );
+    //         assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
+
+    //         // active lines does not include 1 (even though the range of the selection does)
+    //         assert_eq!(
+    //             state.active_rows.keys().cloned().collect::<Vec<u32>>(),
+    //             vec![0, 3, 5, 6]
+    //         );
+
+    //         // multi-buffer support
+    //         // in DisplayPoint co-ordinates, this is what we're dealing with:
+    //         //  0: [[file
+    //         //  1:   header]]
+    //         //  2: aaaaaa
+    //         //  3: bbbbbb
+    //         //  4: cccccc
+    //         //  5:
+    //         //  6: ...
+    //         //  7: ffffff
+    //         //  8: gggggg
+    //         //  9: hhhhhh
+    //         // 10:
+    //         // 11: [[file
+    //         // 12:   header]]
+    //         // 13: bbbbbb
+    //         // 14: cccccc
+    //         // 15: dddddd
+    //         let editor = cx
+    //             .add_window(|cx| {
+    //                 let buffer = MultiBuffer::build_multi(
+    //                     [
+    //                         (
+    //                             &(sample_text(8, 6, 'a') + "\n"),
+    //                             vec![
+    //                                 Point::new(0, 0)..Point::new(3, 0),
+    //                                 Point::new(4, 0)..Point::new(7, 0),
+    //                             ],
+    //                         ),
+    //                         (
+    //                             &(sample_text(8, 6, 'a') + "\n"),
+    //                             vec![Point::new(1, 0)..Point::new(3, 0)],
+    //                         ),
+    //                     ],
+    //                     cx,
+    //                 );
+    //                 Editor::new(EditorMode::Full, buffer, None, None, cx)
+    //             })
+    //             .root(cx);
+    //         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
+    //         let (_, state) = editor.update(cx, |editor, cx| {
+    //             editor.cursor_shape = CursorShape::Block;
+    //             editor.change_selections(None, cx, |s| {
+    //                 s.select_display_ranges([
+    //                     DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
+    //                     DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
+    //                 ]);
+    //             });
+    //             element.layout(
+    //                 SizeConstraint::new(point(500., 500.), point(500., 500.)),
+    //                 editor,
+    //                 cx,
+    //             )
+    //         });
+
+    //         assert_eq!(state.selections.len(), 1);
+    //         let local_selections = &state.selections[0].1;
+    //         assert_eq!(local_selections.len(), 2);
+
+    //         // moves cursor on excerpt boundary back a line
+    //         // and doesn't allow selection to bleed through
+    //         assert_eq!(
+    //             local_selections[0].range,
+    //             DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
+    //         );
+    //         assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
+
+    //         // moves cursor on buffer boundary back two lines
+    //         // and doesn't allow selection to bleed through
+    //         assert_eq!(
+    //             local_selections[1].range,
+    //             DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
+    //         );
+    //         assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
+    //     }
+
+    //     #[gpui::test]
+    //     fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
+    //         init_test(cx, |_| {});
+
+    //         let editor = cx
+    //             .add_window(|cx| {
+    //                 let buffer = MultiBuffer::build_simple("", cx);
+    //                 Editor::new(EditorMode::Full, buffer, None, None, cx)
+    //             })
+    //             .root(cx);
+
+    //         editor.update(cx, |editor, cx| {
+    //             editor.set_placeholder_text("hello", cx);
+    //             editor.insert_blocks(
+    //                 [BlockProperties {
+    //                     style: BlockStyle::Fixed,
+    //                     disposition: BlockDisposition::Above,
+    //                     height: 3,
+    //                     position: Anchor::min(),
+    //                     render: Arc::new(|_| Empty::new().into_any),
+    //                 }],
+    //                 None,
+    //                 cx,
+    //             );
+
+    //             // Blur the editor so that it displays placeholder text.
+    //             cx.blur();
+    //         });
+
+    //         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
+    //         let (size, mut state) = editor.update(cx, |editor, cx| {
+    //             element.layout(
+    //                 SizeConstraint::new(point(500., 500.), point(500., 500.)),
+    //                 editor,
+    //                 cx,
+    //             )
+    //         });
+
+    //         assert_eq!(state.position_map.line_layouts.len(), 4);
+    //         assert_eq!(
+    //             state
+    //                 .line_number_layouts
+    //                 .iter()
+    //                 .map(Option::is_some)
+    //                 .collect::<Vec<_>>(),
+    //             &[false, false, false, true]
+    //         );
+
+    //         // Don't panic.
+    //         let bounds = Bounds::<Pixels>::new(Default::default(), size);
+    //         editor.update(cx, |editor, cx| {
+    //             element.paint(bounds, bounds, &mut state, editor, cx);
+    //         });
+    //     }
+
+    //     #[gpui::test]
+    //     fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
+    //         const TAB_SIZE: u32 = 4;
+
+    //         let input_text = "\t \t|\t| a b";
+    //         let expected_invisibles = vec![
+    //             Invisible::Tab {
+    //                 line_start_offset: 0,
+    //             },
+    //             Invisible::Whitespace {
+    //                 line_offset: TAB_SIZE as usize,
+    //             },
+    //             Invisible::Tab {
+    //                 line_start_offset: TAB_SIZE as usize + 1,
+    //             },
+    //             Invisible::Tab {
+    //                 line_start_offset: TAB_SIZE as usize * 2 + 1,
+    //             },
+    //             Invisible::Whitespace {
+    //                 line_offset: TAB_SIZE as usize * 3 + 1,
+    //             },
+    //             Invisible::Whitespace {
+    //                 line_offset: TAB_SIZE as usize * 3 + 3,
+    //             },
+    //         ];
+    //         assert_eq!(
+    //             expected_invisibles.len(),
+    //             input_text
+    //                 .chars()
+    //                 .filter(|initial_char| initial_char.is_whitespace())
+    //                 .count(),
+    //             "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
+    //         );
+
+    //         init_test(cx, |s| {
+    //             s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
+    //             s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
+    //         });
+
+    //         let actual_invisibles =
+    //             collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
+
+    //         assert_eq!(expected_invisibles, actual_invisibles);
+    //     }
+
+    //     #[gpui::test]
+    //     fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
+    //         init_test(cx, |s| {
+    //             s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
+    //             s.defaults.tab_size = NonZeroU32::new(4);
+    //         });
+
+    //         for editor_mode_without_invisibles in [
+    //             EditorMode::SingleLine,
+    //             EditorMode::AutoHeight { max_lines: 100 },
+    //         ] {
+    //             let invisibles = collect_invisibles_from_new_editor(
+    //                 cx,
+    //                 editor_mode_without_invisibles,
+    //                 "\t\t\t| | a b",
+    //                 500.0,
+    //             );
+    //             assert!(invisibles.is_empty,
+    //                 "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
+    //         }
+    //     }
+
+    //     #[gpui::test]
+    //     fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
+    //         let tab_size = 4;
+    //         let input_text = "a\tbcd   ".repeat(9);
+    //         let repeated_invisibles = [
+    //             Invisible::Tab {
+    //                 line_start_offset: 1,
+    //             },
+    //             Invisible::Whitespace {
+    //                 line_offset: tab_size as usize + 3,
+    //             },
+    //             Invisible::Whitespace {
+    //                 line_offset: tab_size as usize + 4,
+    //             },
+    //             Invisible::Whitespace {
+    //                 line_offset: tab_size as usize + 5,
+    //             },
+    //         ];
+    //         let expected_invisibles = std::iter::once(repeated_invisibles)
+    //             .cycle()
+    //             .take(9)
+    //             .flatten()
+    //             .collect::<Vec<_>>();
+    //         assert_eq!(
+    //             expected_invisibles.len(),
+    //             input_text
+    //                 .chars()
+    //                 .filter(|initial_char| initial_char.is_whitespace())
+    //                 .count(),
+    //             "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
+    //         );
+    //         info!("Expected invisibles: {expected_invisibles:?}");
+
+    //         init_test(cx, |_| {});
+
+    //         // Put the same string with repeating whitespace pattern into editors of various size,
+    //         // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
+    //         let resize_step = 10.0;
+    //         let mut editor_width = 200.0;
+    //         while editor_width <= 1000.0 {
+    //             update_test_language_settings(cx, |s| {
+    //                 s.defaults.tab_size = NonZeroU32::new(tab_size);
+    //                 s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
+    //                 s.defaults.preferred_line_length = Some(editor_width as u32);
+    //                 s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
+    //             });
+
+    //             let actual_invisibles =
+    //                 collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, editor_width);
+
+    //             // Whatever the editor size is, ensure it has the same invisible kinds in the same order
+    //             // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
+    //             let mut i = 0;
+    //             for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
+    //                 i = actual_index;
+    //                 match expected_invisibles.get(i) {
+    //                     Some(expected_invisible) => match (expected_invisible, actual_invisible) {
+    //                         (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
+    //                         | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
+    //                         _ => {
+    //                             panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
+    //                         }
+    //                     },
+    //                     None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
+    //                 }
+    //             }
+    //             let missing_expected_invisibles = &expected_invisibles[i + 1..];
+    //             assert!(
+    //                 missing_expected_invisibles.is_empty,
+    //                 "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
+    //             );
+
+    //             editor_width += resize_step;
+    //         }
+    //     }
+
+    //     fn collect_invisibles_from_new_editor(
+    //         cx: &mut TestAppContext,
+    //         editor_mode: EditorMode,
+    //         input_text: &str,
+    //         editor_width: f32,
+    //     ) -> Vec<Invisible> {
+    //         info!(
+    //             "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
+    //         );
+    //         let editor = cx
+    //             .add_window(|cx| {
+    //                 let buffer = MultiBuffer::build_simple(&input_text, cx);
+    //                 Editor::new(editor_mode, buffer, None, None, cx)
+    //             })
+    //             .root(cx);
+
+    //         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
+    //         let (_, layout_state) = editor.update(cx, |editor, cx| {
+    //             editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
+    //             editor.set_wrap_width(Some(editor_width), cx);
+
+    //             element.layout(
+    //                 SizeConstraint::new(point(editor_width, 500.), point(editor_width, 500.)),
+    //                 editor,
+    //                 cx,
+    //             )
+    //         });
+
+    //         layout_state
+    //             .position_map
+    //             .line_layouts
+    //             .iter()
+    //             .map(|line_with_invisibles| &line_with_invisibles.invisibles)
+    //             .flatten()
+    //             .cloned()
+    //             .collect()
+    //     }
+}
 
 pub fn register_action<T: Action>(
     view: &View<Editor>,