diff --git a/src/fenrirscreenreader/core/tabCompletionManager.py b/src/fenrirscreenreader/core/tabCompletionManager.py index 54d86b06..27fc503f 100644 --- a/src/fenrirscreenreader/core/tabCompletionManager.py +++ b/src/fenrirscreenreader/core/tabCompletionManager.py @@ -132,13 +132,6 @@ class TabCompletionManager: if candidate_text: return self._clean_text(candidate_text) - delta_text = self.env["screen"]["new_delta"] - if ( - delta_text - and not self.env["screen"].get("new_delta_is_typing", False) - ): - return self._clean_text(delta_text) - return "" def _get_cursor_line_inserted_text( @@ -184,26 +177,19 @@ class TabCompletionManager: return "".join(inserted_parts) def _get_candidate_text(self, old_lines, new_lines, cursor_y): - if len(old_lines) != len(new_lines): - return self._get_inserted_lines(old_lines, new_lines, cursor_y) - - changed_lines = [] old_cursor_line = ( old_lines[cursor_y].strip() if cursor_y < len(old_lines) else "" ) - for index, old_line in enumerate(old_lines): - if index == cursor_y: - continue - if index < len(new_lines) and old_line != new_lines[index]: - if new_lines[index].strip() == old_cursor_line: - continue - changed_lines.append(new_lines[index]) - - return "\n".join( - line.rstrip() for line in changed_lines if line.strip() + return self._get_inserted_lines( + old_lines, + new_lines, + self.env["screen"]["new_cursor"]["y"], + old_cursor_line, ) - def _get_inserted_lines(self, old_lines, new_lines, cursor_y): + def _get_inserted_lines( + self, old_lines, new_lines, new_cursor_y, old_cursor_line + ): matcher = difflib.SequenceMatcher( None, old_lines, new_lines, autojunk=False ) @@ -217,10 +203,15 @@ class TabCompletionManager: ) in matcher.get_opcodes(): if tag not in ["insert", "replace"]: continue - if new_end <= cursor_y: + if new_start > new_cursor_y: + continue + if tag == "replace" and any( + line.strip() for line in old_lines[old_start:old_end] + ): continue for line in new_lines[new_start:new_end]: - if line.strip(): + stripped_line = line.strip() + if stripped_line and stripped_line != old_cursor_line: inserted_lines.append(line.rstrip()) return "\n".join(inserted_lines) diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 2ec05ee3..7528725b 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2026.05.29" +version = "2026.05.30" code_name = "testing" diff --git a/tests/unit/test_tab_completion_manager.py b/tests/unit/test_tab_completion_manager.py index 10072f6a..60d11518 100644 --- a/tests/unit/test_tab_completion_manager.py +++ b/tests/unit/test_tab_completion_manager.py @@ -140,6 +140,93 @@ def test_candidate_list_speaks_visible_list_without_cursor_advance(): assert manager.process_update() == "Documents/ Downloads/" +@pytest.mark.unit +def test_full_screen_scroll_speaks_only_inserted_candidate_list(): + old_text = "\n".join( + [ + "old top".ljust(30), + "old middle".ljust(30), + "old lower".ljust(30), + "old bottom".ljust(30), + "$ ./Toby".ljust(30), + ] + ) + manager, env, _input_manager = _build_env(old_text, {"x": 8, "y": 4}) + + manager.capture_if_tab() + _set_screen_update( + env, + "\n".join( + [ + "old middle".ljust(30), + "old lower".ljust(30), + "old bottom".ljust(30), + "TobyAccMod_V10-0.pk3 TobyConfig.ini".ljust(30), + "$ ./Toby".ljust(30), + ] + ), + {"x": 8, "y": 4}, + delta="\n".join( + [ + "old middle", + "old lower", + "old bottom", + "TobyAccMod_V10-0.pk3 TobyConfig.ini", + "$ ./Toby", + ] + ), + ) + + assert manager.process_update() == "TobyAccMod_V10-0.pk3 TobyConfig.ini" + + +@pytest.mark.unit +def test_same_height_repaint_without_inserted_candidates_stays_silent(): + old_text = "\n".join( + [ + "old top".ljust(30), + "old middle".ljust(30), + "old lower".ljust(30), + "$ ./Toby".ljust(30), + ] + ) + manager, env, _input_manager = _build_env(old_text, {"x": 8, "y": 3}) + + manager.capture_if_tab() + _set_screen_update( + env, + "\n".join( + [ + "status line".ljust(30), + "old prompt history".ljust(30), + "unrelated output".ljust(30), + "$ ./Toby".ljust(30), + ] + ), + {"x": 8, "y": 3}, + delta="status line\nold prompt history\nunrelated output\n$ ./Toby", + ) + + assert manager.process_update() == "" + + +@pytest.mark.unit +def test_recent_tab_does_not_speak_delta_without_detected_completion(): + old_text = "\n".join(["$ ./Toby".ljust(20), "".ljust(20)]) + manager, env, _input_manager = _build_env(old_text, {"x": 8, "y": 0}) + + manager.capture_if_tab() + _set_screen_update( + env, + old_text, + {"x": 8, "y": 0}, + delta="old unrelated screen content\n$ ./Toby", + ) + + assert manager.process_update() == "" + assert env["commandBuffer"]["tabCompletion"]["pending"] is not None + + @pytest.mark.unit def test_no_screen_change_stays_silent_and_keeps_pending_briefly(): manager, env, _input_manager = _build_env(