Restore sentence as the default say all reading unit, with a setting to read by paragraph or line#20467
Restore sentence as the default say all reading unit, with a setting to read by paragraph or line#20467LeonarddeR wants to merge 9 commits into
Conversation
Add a SayAllReadingUnitFlag feature flag controlling the text unit
say all advances by (the reading chunk), with an explicit DEFAULT
whose behavior is sentence, plus the [speech] configSpec entry.
Enum values are inlined string literals ('sentence'/'line') equal to
textInfos.UNIT_SENTENCE/UNIT_LINE. A module-level 'import textInfos'
was avoided: featureFlagEnums is imported during config package init,
before languageHandler installs the '_' gettext builtin, and textInfos
pulls in controlTypes.formatFields which calls _() at module load,
which would crash startup.
…le TextInfos Make each sentence-capable TextInfo resolve UNIT_READINGCHUNK to the effective value of the sayAllReadingUnit feature flag (sentence or line), keeping line as the automatic fallback where sentence is unsupported: - textInfos/offsets.py: _getReadingChunkOffsets tries sentence offsets when the flag resolves to sentence, falling back to line on NotImplementedError. - NVDAObjects/window/winword.py and edit.py: substitute the calculated unit for UNIT_READINGCHUNK before the static unit-map lookup in expand/move. - NVDAObjects/IAccessible/MSHTML.py: replace the READINGCHUNK->SENTENCE remaps in expand/move with the calculated unit; preserve the existing LINE->SENTENCE behaviour in move. Add unit tests for _getReadingChunkOffsets covering sentence, line, and the NotImplementedError line fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a FeatureFlagCombo for the speech.sayAllReadingUnit feature flag to SpeechSettingsPanel, with context help binding and onSave wiring. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a userGuide entry (anchored SpeechSettingsSayAllReadingUnit for context help) and a changes.md New Features entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ies, guard enum - MSHTML: revert the READINGCHUNK resolution. IHTMLTxtRange::move cannot move by line (the original mapped both READINGCHUNK and LINE to sentence for that reason), so honouring a "line" flag value there made say all's move soft-fail and stop after the first chunk. Legacy MSHTML keeps reading by sentence. - winword.py / edit.py: remove the now-unreachable UNIT_READINGCHUNK entries from the static unit maps (READINGCHUNK is resolved to sentence/line before the map lookup in expand/move). - Add a unit test guarding that SayAllReadingUnitFlag's inlined "sentence"/"line" values stay equal to textInfos.UNIT_SENTENCE/UNIT_LINE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Also mentioning @cary-rowen here, since he was particularly involved with #13420 debugging. |
- new SayAllReadingUnitFlag.PARAGRAPH ("Prefer paragraph"), resolved
via unit_readingChunk with line fallback where unsupported
- hoist duplicated UNIT_READINGCHUNK resolution from edit/winword
expand/move into TextInfo._resolveReadingChunkUnit
- _getReadingChunkOffsets reuses _getUnitOffsets dispatch
- cache unit_readingChunk per core pump to avoid config reads in the
say all hot loop
"Prefer sentence" etc. read oddly after the "Say all reads by" label; options now complete the label as a sentence. Paragraph and line need no qualifier since they are always supported.
There was a problem hiding this comment.
Pull request overview
This PR reintroduces sentence-based “Say All” as the default reading unit (where supported) and makes the reading-chunk unit user-configurable via a new Speech setting, including a paragraph option to allow larger chunks to be sent to synthesizers.
Changes:
- Added a new “Say all reads by” Speech setting (feature flag-backed) with options for sentence (where possible), paragraph, and line.
- Updated TextInfo implementations to resolve
UNIT_READINGCHUNKdynamically based on the configured reading unit, including support in legacy Word (winword) and Rich Edit (ITextDocument). - Added unit tests for reading-chunk offsets resolution and fallback-to-line behavior when a unit isn’t implemented.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| user_docs/en/userGuide.md | Documents the new Speech setting in the User Guide. |
| user_docs/en/changes.md | Adds a changelog entry for the new setting / default behavior change. |
| tests/unit/test_textInfos.py | Adds unit tests for reading-chunk offset behavior across configured units. |
| source/textInfos/offsets.py | Resolves reading-chunk offsets via the configured unit with fallback to line when unsupported. |
| source/textInfos/init.py | Introduces unit_readingChunk and unit resolution helper for UNIT_READINGCHUNK. |
| source/NVDAObjects/window/winword.py | Resolves UNIT_READINGCHUNK before mapping units to Word units. |
| source/NVDAObjects/window/edit.py | Resolves UNIT_READINGCHUNK before mapping units to TOM units. |
| source/config/configSpec.py | Adds the new [speech] sayAllReadingUnit feature-flag config entry. |
| try: | ||
| return self._getUnitOffsets(self.unit_readingChunk, offset) | ||
| except NotImplementedError: | ||
| return self._getLineOffsets(offset) |
|
|
||
| This combo box lets you choose the unit of text that say all (continuous reading) advances by. | ||
| When set to "Sentence where possible", NVDA reads sentence by sentence in controls and documents that support sentence boundaries, and automatically falls back to reading by line where sentence boundaries are not supported. | ||
| When set to "Paragraph", NVDA reads paragraph by paragraph. |
| * In modes that show a continuation mark, when a word is cut across rows, the last cell of the row now shows a continuation mark (braille dots 7-8) so it is clear that the word continues on the next row. | ||
| * The "At word or syllable boundaries" option uses hyphenation dictionaries to split long words at syllable boundaries when they do not fit on the display. | ||
| * Magnifier: A new unassigned command has been added to move the mouse cursor to the center of the magnified view. (#20127, @CyrilleB79) | ||
| * A new "Say all reads by" speech setting lets you choose whether say all reads by sentence, paragraph or line; say all now reads by sentence by default where supported. (#13420, #9179, #13971, @LeonarddeR) |
Link to issue number:
Related to #13420, #9179 and #13971
Fixes #13829 by making
paragraphavailable as an additional unit, so say all can send whole paragraphs to the synthesizer.Summary of the issue:
For rich text controls where
UNIT_READINGCHUNKwas previously mapped to line (ITextDocumentTextInfo, e.g. WordPad, NVDA's log viewer, and third-party apps such as Paperback), say all's caret/review position hardly updates when word wrap is off and a paragraph spans many visual lines — the position only advances once the whole (very long) paragraph line is read. This was introduced by #13971, which changed the reading unit from sentence to line for these controls to fix caret tracking in other apps (#13420, #9179).This PR effectively reverts the default introduced by #13971: say all now reads by sentence again by default wherever sentence boundaries are supported, restoring smoother caret tracking in rich edit controls with long paragraphs. To avoid regressing the caret-tracking fix #13971 provided for apps/languages without usable sentence boundaries, the choice is no longer hardcoded: a new speech setting lets users switch back to line-based reading, re-enabling the behavior @lukaszgo1 introduced in #13971 where they need it. A paragraph unit is also available, which sends larger chunks of text to the synthesizer so it can determine sentence boundaries (and e.g. abbreviations like "Mr.") itself (#13829).
Description of user facing changes:
Description of developer facing changes:
SayAllReadingUnitFlagfeature flag (config.featureFlagEnums) with a[speech] sayAllReadingUnitconfigSpec entry,behaviorOfDefault="sentence".TextInfo.unit_readingChunk, resolvingtextInfos.UNIT_READINGCHUNKtoUNIT_SENTENCE/UNIT_PARAGRAPH/UNIT_LINEbased on the flag's calculated value; cached per core pump (_cache_unit_readingChunk) to avoid repeated config lookups in the say all loop.TextInfo._resolveReadingChunkUnit, used bywinword.py/edit.pyexpand/moveto substitute the resolved unit forUNIT_READINGCHUNKbefore their static unit-map lookup; the now-unreachableUNIT_READINGCHUNKentries were removed from those maps.OffsetsTextInfo._getReadingChunkOffsetsdispatchesself.unit_readingChunkthrough the existing_getUnitOffsets, falling back to line offsets if the resolved unit raisesNotImplementedError.UIAHandler.NVDAUnitsToUIAUnitshardcodesUNIT_READINGCHUNKtoTextUnit_Line, since UIA's TextPattern has no sentence text unit. This means modern UIA Word (NVDAObjects/UIA/wordDocument.py) already reads by line for say all regardless of this flag (the paragraph option is likewise ignored there for now); only legacy Word Object Model Word (winword.py,wdSentence) is driven by the flag.Description of development approach:
Introduced the reading unit as a feature flag rather than hardcoding a default, so the sentence/line choice from #13971 remains available per user preference instead of being an all-or-nothing global default.
SayAllReadingUnitFlagavoids a module-levelimport textInfosinfeatureFlagEnums.py, since that module is imported duringconfigpackage init, beforelanguageHandlerinstalls the_gettext builtin, andtextInfostransitively calls_()at import time.Testing strategy:
_getReadingChunkOffsetscovering the sentence, paragraph, line, and unsupported-unit-falls-back-to-line cases.Known issues with pull request:
This PR defaults the flag to sentence, effectively reverting #13971's default. If reviewers decide line should remain the default (per #13971's rationale for #13420/#9179), the fix is a one-line change: flip
behaviorOfDefaultin thesayAllReadingUnitconfigSpec entry (source/config/configSpec.py) from"sentence"to"line". Note this would also move Word Object Model Word (winword.py, legacy Word) to line-based reading — it currently reads by sentence unconditionally on master (wdSentence, unaffected by #13971) and is now driven by the same flag as WordPad/log viewer. I consider this acceptable: a single uniform default across allUNIT_READINGCHUNK-capable TextInfos is preferable to Word Object Model Word and rich-edit controls disagreeing on reading unit by default. Modern UIA Word is unaffected by the default either way — it already reads by line unconditionally (see developer facing changes).Code Review Checklist: