Skip to content

Commit faa2c28

Browse files
committed
Fix multiline structural navigation targets
1 parent 5bc8217 commit faa2c28

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

bookworm/gui/book_viewer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ def navigate_to_structural_element(self, element_type, forward):
675675
actual_element_type,
676676
)
677677
target_position = (
678-
start if not move_to_start_of_line else self.get_containing_line(stop - 1)[0]
678+
start if not move_to_start_of_line else self.get_containing_line(start + 1)[0]
679679
)
680680
self.set_insertion_point(target_position)
681681
speech.announce(msg, True)

tests/test_navigation.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from types import SimpleNamespace
2+
3+
import pytest
4+
5+
from bookworm.gui import book_viewer
6+
from bookworm.gui.book_viewer import BookViewerWindow
7+
from bookworm.structured_text import SemanticElementType
8+
9+
10+
class StructuralNavigationHarness:
11+
navigate_to_structural_element = BookViewerWindow.navigate_to_structural_element
12+
13+
14+
def line_bounds(text, pos):
15+
pos = max(0, min(pos, len(text)))
16+
start = text.rfind("\n", 0, pos) + 1
17+
end = text.find("\n", pos)
18+
if end == -1:
19+
end = len(text)
20+
return start, end
21+
22+
23+
def noop(*_args, **_kwargs):
24+
pass
25+
26+
27+
@pytest.mark.parametrize(
28+
"element_type",
29+
[
30+
SemanticElementType.LIST,
31+
SemanticElementType.TABLE,
32+
SemanticElementType.QUOTE,
33+
],
34+
)
35+
def test_structural_navigation_moves_multiline_elements_to_first_line(monkeypatch, element_type):
36+
text = "intro\nfirst element line\nsecond element line\noutro\n"
37+
start = text.index("first element line")
38+
stop = text.index("\noutro")
39+
viewer = StructuralNavigationHarness()
40+
viewer._BookViewerWindow__latest_structured_navigation_position = None
41+
viewer.insertion_point = 0
42+
43+
def get_semantic_element(_requested_type, _forward, _anchor):
44+
return (start, stop), element_type
45+
46+
viewer.reader = SimpleNamespace(
47+
ready=True,
48+
get_semantic_element=get_semantic_element,
49+
)
50+
viewer.get_insertion_point = lambda: viewer.insertion_point
51+
viewer.get_containing_line = lambda pos: line_bounds(text, pos)
52+
viewer.get_text_by_range = lambda range_start, range_stop: text[range_start:range_stop]
53+
viewer.set_insertion_point = lambda pos: setattr(viewer, "insertion_point", pos)
54+
55+
monkeypatch.setattr(book_viewer.speech, "announce", noop)
56+
monkeypatch.setattr(
57+
book_viewer.sounds,
58+
"structured_navigation",
59+
SimpleNamespace(play=noop),
60+
)
61+
monkeypatch.setattr(
62+
book_viewer.reading_position_change,
63+
"send",
64+
noop,
65+
)
66+
67+
BookViewerWindow.navigate_to_structural_element(viewer, element_type, True)
68+
69+
assert viewer.insertion_point == start

0 commit comments

Comments
 (0)