Repository: godotengine/godot (language: cpp)

Issue:
TextEdit Scroll Repeats Last Line and Text Pops In
### Tested versions

- Reproducible in 4.4 Stable, 4.3 Stable, 4.2.2 Stable, 4.2 Stable
- Not Reproducible in 4.1.4 Stable

### System information

Godot v4.2.stable - macOS 14.5.0 - Vulkan (Forward+) - integrated Apple M1 Pro - Apple M1 Pro (10 Threads)

### Issue description

For certain heights, the `TextEdit` displays text only displays when the line is fully visible, like a popping into existence effect. This also causes the last line to appear to repeat twice when scrolling down.

For one line; the following heights have issues: 15-21, 23-24, 26-29, 31-32, 34 px

https://github.com/user-attachments/assets/82f8544a-3491-44d5-9da7-ca0a70ed694b

### Steps to reproduce

1. Create a scene with a TextEdit Node
2. Set the text to "0"
3. Set the size to (100, 32)
4. Run the scene, and scroll down on the TextEdit

### Minimal reproduction project (MRP)

[MRP.zip](https://github.com/user-attachments/files/19400545/MRP.zip)


Discussion / hints:

Fix patch:
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index f6f700de2a6a..8c891d08ce97 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -852,7 +852,7 @@ void TextEdit::_notification(int p_what) {
 			}
 
 			int first_vis_line = get_first_visible_line() - 1;
-			int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
+			int draw_amount = visible_rows + 1;
 			draw_amount += draw_placeholder ? placeholder_wrapped_rows.size() - 1 : get_line_wrap_count(first_vis_line + 1);
 
 			// Draw minimap.
@@ -4639,7 +4639,7 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const {
 	int minimap_visible_lines = get_minimap_visible_lines();
 	int visible_rows = get_visible_line_count() + 1;
 	int first_vis_line = get_first_visible_line() - 1;
-	int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
+	int draw_amount = visible_rows + 1;
 	draw_amount += get_line_wrap_count(first_vis_line + 1);
 	int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
 
@@ -8053,7 +8053,7 @@ void TextEdit::_update_scrollbars() {
 
 	int visible_rows = get_visible_line_count();
 	int total_rows = draw_placeholder ? placeholder_wrapped_rows.size() : get_total_visible_line_count();
-	if (scroll_past_end_of_file_enabled && !fit_content_height) {
+	if ((scroll_past_end_of_file_enabled && !fit_content_height) || visible_rows == 0) {
 		total_rows += visible_rows - 1;
 	}
 
@@ -8076,7 +8076,6 @@ void TextEdit::_update_scrollbars() {
 		v_scroll->set_max(total_rows + _get_visible_lines_offset());
 		v_scroll->set_page(visible_rows + _get_visible_lines_offset());
 		set_v_scroll(get_v_scroll());
-
 	} else {
 		first_visible_line = 0;
 		first_visible_line_wrap_ofs = 0;


Test patch:
diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h
index 8a10c0b6ee59..19db4f19e17e 100644
--- a/tests/scene/test_text_edit.h
+++ b/tests/scene/test_text_edit.h
@@ -7920,6 +7920,20 @@ TEST_CASE("[SceneTree][TextEdit] viewport") {
 	memdelete(text_edit);
 }
 
+TEST_CASE("[SceneTree][TextEdit] small height value") {
+	TextEdit *text_edit = memnew(TextEdit);
+	SceneTree::get_singleton()->get_root()->add_child(text_edit);
+
+	text_edit->set_size(Size2(800, 32));
+	text_edit->set_text("0\n1\n2");
+	MessageQueue::get_singleton()->flush();
+
+	text_edit->set_v_scroll(100);
+	CHECK(text_edit->get_v_scroll() < 3);
+
+	memdelete(text_edit);
+}
+
 TEST_CASE("[SceneTree][TextEdit] setter getters") {
 	TextEdit *text_edit = memnew(TextEdit);
 	SceneTree::get_singleton()->get_root()->add_child(text_edit);