Do not print invisibles in non-full mode editors

Kirill Bulatov created

Change summary

crates/editor/src/element.rs | 105 ++++++++++++++++++++++++++++++-------
1 file changed, 83 insertions(+), 22 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -1382,6 +1382,7 @@ impl EditorElement {
                 MAX_LINE_LEN,
                 rows.len() as usize,
                 line_number_layouts,
+                snapshot.mode,
             )
         }
     }
@@ -1728,6 +1729,7 @@ fn layout_highlighted_chunks<'a>(
     max_line_len: usize,
     max_line_count: usize,
     line_number_layouts: &[Option<Line>],
+    editor_mode: EditorMode,
 ) -> Vec<LineWithInvisibles> {
     let mut layouts = Vec::with_capacity(max_line_count);
     let mut line = String::new();
@@ -1787,30 +1789,37 @@ fn layout_highlighted_chunks<'a>(
                     },
                 ));
 
-                // Line wrap pads its contents with fake whitespaces,
-                // avoid printing them
-                let inside_wrapped_string = line_number_layouts[row].is_none();
-                if highlighted_chunk.is_tab {
-                    if non_whitespace_added || !inside_wrapped_string {
-                        invisibles.push(Invisible::Tab {
-                            line_start_offset: line.len(),
-                        });
+                if editor_mode == EditorMode::Full {
+                    // Line wrap pads its contents with fake whitespaces,
+                    // avoid printing them
+                    let inside_wrapped_string = line_number_layouts
+                        .get(row)
+                        .and_then(|layout| layout.as_ref())
+                        .is_none();
+                    if highlighted_chunk.is_tab {
+                        if non_whitespace_added || !inside_wrapped_string {
+                            invisibles.push(Invisible::Tab {
+                                line_start_offset: line.len(),
+                            });
+                        }
+                    } else {
+                        invisibles.extend(
+                            line_chunk
+                                .chars()
+                                .enumerate()
+                                .filter(|(_, line_char)| {
+                                    let is_whitespace = line_char.is_whitespace();
+                                    non_whitespace_added |= !is_whitespace;
+                                    is_whitespace
+                                        && (non_whitespace_added || !inside_wrapped_string)
+                                })
+                                .map(|(whitespace_index, _)| Invisible::Whitespace {
+                                    line_offset: line.len() + whitespace_index,
+                                }),
+                        )
                     }
-                } else {
-                    invisibles.extend(
-                        line_chunk
-                            .chars()
-                            .enumerate()
-                            .filter(|(_, line_char)| {
-                                let is_whitespace = line_char.is_whitespace();
-                                non_whitespace_added |= !is_whitespace;
-                                is_whitespace && (non_whitespace_added || !inside_wrapped_string)
-                            })
-                            .map(|(whitespace_index, _)| Invisible::Whitespace {
-                                line_offset: line.len() + whitespace_index,
-                            }),
-                    )
                 }
+
                 line.push_str(line_chunk);
             }
         }
@@ -2925,4 +2934,56 @@ mod tests {
 
         assert_eq!(expected_invisibles, actual_invisibles);
     }
+
+    #[gpui::test]
+    fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let mut test_settings = Settings::test(cx);
+            test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
+            test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(4).unwrap());
+            cx.set_global(test_settings);
+        });
+
+        for editor_mode_without_invisibles in [
+            EditorMode::SingleLine,
+            EditorMode::AutoHeight { max_lines: 100 },
+        ] {
+            let (_, editor) = cx.add_window(|cx| {
+                let buffer = MultiBuffer::build_simple("\t\t\t| | a b", cx);
+                Editor::new(editor_mode_without_invisibles, buffer, None, None, cx)
+            });
+
+            let mut element =
+                EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
+            let (_, layout_state) = editor.update(cx, |editor, cx| {
+                let mut new_parents = Default::default();
+                let mut notify_views_if_parents_change = Default::default();
+                let mut layout_cx = LayoutContext::new(
+                    cx,
+                    &mut new_parents,
+                    &mut notify_views_if_parents_change,
+                    false,
+                );
+                element.layout(
+                    SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
+                    editor,
+                    &mut layout_cx,
+                )
+            });
+
+            let line_layouts = &layout_state.position_map.line_layouts;
+            let invisibles = line_layouts
+                .iter()
+                .map(|line_with_invisibles| &line_with_invisibles.invisibles)
+                .flatten()
+                .sorted_by(|invisible_1, invisible_2| {
+                    invisible_1.offset().cmp(&invisible_2.offset())
+                })
+                .cloned()
+                .collect::<Vec<_>>();
+
+            assert!(invisibles.is_empty(),
+                "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
+        }
+    }
 }