Skip to content

Commit

Permalink
Prefer Difflib in scrolling LiveText objects (nvaccess#12974)
Browse files Browse the repository at this point in the history
In LiveText objects that are constrained to onscreen text (i.e. where text scrolls off the screen), DMP is sometimes unable to correctly calculate new text, leading to choppy and inconsistent new text reporting.

Description of how this pull request fixes the issue:
Prefer Difflib in pre-FORMATTED UIA console (including legacy mode) and DisplayModelLiveText objects. Users can override this choice in advanced settings for testing or if DMP really provides a supperior experience in their situation.
  • Loading branch information
codeofdusk authored Nov 4, 2021
1 parent d126545 commit fd9ae8c
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 32 deletions.
10 changes: 1 addition & 9 deletions source/NVDAObjects/IAccessible/winConsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,7 @@ class LegacyWinConsole(winConsole.WinConsole, IAccessible):
NVDA's original console support, used by default on Windows versions
before 1607.
"""

def _get_diffAlgo(self):
# Non-enhanced legacy consoles use caret proximity to detect
# typed/deleted text.
# Single-character changes are not reported as
# they are confused for typed characters.
# Force difflib to keep meaningful edit reporting in these consoles.
from diffHandler import get_difflib_algo
return get_difflib_algo()
pass


def findExtraOverlayClasses(obj, clsList):
Expand Down
9 changes: 9 additions & 0 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import UIAHandler

from comtypes import COMError
from diffHandler import prefer_difflib
from logHandler import log
from UIAUtils import _getConhostAPILevel
from _UIAConstants import WinConsoleAPILevel
Expand Down Expand Up @@ -379,6 +380,14 @@ def _get_devInfo(self):
info.append(f"API level: {self.apiLevel} ({self.apiLevel.name})")
return info

def _get_diffAlgo(self):
if self.apiLevel < WinConsoleAPILevel.FORMATTED:
# #12974: These consoles are constrained to onscreen text.
# Use Difflib to reduce choppiness in reading.
return prefer_difflib()
else:
return super().diffAlgo

def detectPossibleSelectionChange(self):
try:
return super().detectPossibleSelectionChange()
Expand Down
19 changes: 11 additions & 8 deletions source/NVDAObjects/behaviors.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import braille
import nvwave
import globalVars
from typing import List
from typing import List, Union
import diffHandler


Expand Down Expand Up @@ -266,17 +266,20 @@ def event_textChange(self):
"""
self._event.set()

def _get_diffAlgo(self):
def _get_diffAlgo(self) -> Union[diffHandler.prefer_difflib, diffHandler.prefer_dmp]:
"""
This property controls which diffing algorithm should be used by
this object. Most subclasses should simply use the base
implementation, which returns DMP (character-based diffing).
this object. If the object contains a strictly contiguous
span of text (i.e. textInfos.POSITION_ALL refers to the entire
contents of the object and not just one visible screen of text),
then diffHandler.prefer_dmp (character-based diffing) is suitable.
Otherwise, use diffHandler.prefer_difflib.
@Note: DMP is experimental, and can be disallowed via user
preference. In this case, the prior stable implementation, Difflib
(line-based diffing), will be used.
@Note: Return either diffHandler.prefer_dmp() or
diffHandler.prefer_difflib() so that the diffAlgo user
preference can override this choice.
"""
return diffHandler.get_dmp_algo()
return diffHandler.prefer_dmp()

def _get_devInfo(self):
info = super().devInfo
Expand Down
7 changes: 7 additions & 0 deletions source/NVDAObjects/window/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from NVDAObjects.behaviors import EditableText, EditableTextWithoutAutoSelectDetection, LiveText
import watchdog
from locationHelper import RectLTWH
from diffHandler import prefer_difflib

re_WindowsForms=re.compile(r'^WindowsForms[0-9]*\.(.*)\.app\..*$')
re_ATL=re.compile(r'^ATL:(.*)$')
Expand Down Expand Up @@ -413,6 +414,12 @@ def stopMonitoring(self):
super(DisplayModelLiveText, self).stopMonitoring()
displayModel.requestTextChangeNotifications(self, False)

def _get_diffAlgo(self):
# #12974: The display model gives us only one screen of text at a time.
# Use Difflib to reduce choppiness in reading.
return prefer_difflib()


windowClassMap={
"EDIT":"Edit",
"TTntEdit.UnicodeClass":"Edit",
Expand Down
6 changes: 6 additions & 0 deletions source/NVDAObjects/window/winConsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import core
from scriptHandler import script
import speech
from diffHandler import prefer_difflib

class WinConsole(Terminal, EditableTextWithoutAutoSelectDetection, Window):
"""
Expand All @@ -36,6 +37,11 @@ def _get_TextInfo(self):
return winConsoleHandler.WinConsoleTextInfo
return super(WinConsole,self).TextInfo

def _get_diffAlgo(self):
# #12974: Legacy consoles contain only one screen of text at a time.
# Use Difflib to reduce choppiness in reading.
return prefer_difflib()

def event_becomeNavigatorObject(self, isFocus=False):
if winConsoleHandler.consoleObject is not self:
if winConsoleHandler.consoleObject:
Expand Down
16 changes: 12 additions & 4 deletions source/diffHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def _getText(self, ti: TextInfo) -> str:
return "\n".join(ti.getTextInChunks(UNIT_LINE))


def get_dmp_algo():
def prefer_dmp():
"""
This function returns a Diff Match Patch object if allowed by the user.
DMP is new and can be explicitly disabled by a user setting. If config
Expand All @@ -189,9 +189,17 @@ def get_dmp_algo():
)


def get_difflib_algo():
"Returns an instance of the difflib diffAlgo."
return _difflib
def prefer_difflib():
"""
This function returns a Difflib object if allowed by the user.
Difflib can be explicitly disabled by a user setting. If config
does not allow Difflib, this function returns a DMP instance instead.
"""
return (
_dmp
if config.conf["terminals"]["diffAlgo"] == "dmp"
else _difflib
)


_difflib = Difflib()
Expand Down
10 changes: 5 additions & 5 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2730,21 +2730,21 @@ def __init__(self, parent):
# Translators: This is the label for a combo box for selecting a
# method of detecting changed content in terminals in the advanced
# settings panel.
# Choices are automatic, allow Diff Match Patch, and force Difflib.
# Choices are automatic, Diff Match Patch, and Difflib.
diffAlgoComboText = _("&Diff algorithm:")
diffAlgoChoices = [
# Translators: A choice in a combo box in the advanced settings
# panel to have NVDA determine the method of detecting changed
# content in terminals automatically.
_("Automatic (Diff Match Patch)"),
_("Automatic (prefer Diff Match Patch)"),
# Translators: A choice in a combo box in the advanced settings
# panel to have NVDA detect changes in terminals
# by character when supported, using the diff match patch algorithm.
_("allow Diff Match Patch"),
# by character, using the diff match patch algorithm.
_("Diff Match Patch"),
# Translators: A choice in a combo box in the advanced settings
# panel to have NVDA detect changes in terminals
# by line, using the difflib algorithm.
_("force Difflib")
_("Difflib")
]
#: The possible diffAlgo config values, in the order they appear
#: in the combo box.
Expand Down
4 changes: 4 additions & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ What's New in NVDA
- On a system where the user has chosen to swap the primary mouse button from the left to the right, NVDA will no longer accidentally bring up a context menu instead of activating an item, in applications such as web browsers. (#12642)
- When moving the review cursor past the end of text controls, such as in Microsoft Word with UI Automation, "bottom" is correctly reported in more situations. (#12808)
- NVDA can report the application name and version for binaries placed in system32 when running under 64-bit version of Windows. (#12943)
- Improved consistency of output reading in terminal programs. (#12974)
- Note that in some situations, when inserting or deleting characters in the middle of a line, the characters after the caret may again be read out.
-
-

== Changes for Developers ==
Expand Down Expand Up @@ -49,6 +52,7 @@ This ensures code will honor the Windows user setting for swapping the primary m
- ``config.getSystemConfigPath`` has been removed - there is no replacement. (#12943)
- ``shlobj.SHGetFolderPath`` has been removed - please use ``shlobj.SHGetKnownFolderPath`` instead. (#12943)
- ``shlobj`` constants have been removed. A new enum has been created, ``shlobj.FolderId`` for usage with ``SHGetKnownFolderPath``. (#12943)
- ``diffHandler.get_dmp_algo`` and ``diffHandler.get_difflib_algo`` have been replaced with ``diffHandler.prefer_dmp`` and ``diffHandler.prefer_difflib`` respectively. (#12974)
-


Expand Down
12 changes: 6 additions & 6 deletions user_docs/en/userGuide.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -1901,14 +1901,14 @@ In untrusted environments, you may temporarily disable [speak typed characters #
==== Diff algorithm ====[DiffAlgo]
This setting controls how NVDA determines the new text to speak in terminals.
The diff algorithm combo box has three options:
- Automatic: as of NVDA 2021.2, this option is equivalent to "allow Diff Match Patch".
- allow Diff Match Patch: This option causes NVDA to calculate changes to terminal text by character.
- Automatic: This option causes NVDA to prefer Diff Match Patch in most situations, but fall back to Difflib in problematic applications, such as older versions of the Windows Console and Mintty.
- Diff Match Patch: This option causes NVDA to calculate changes to terminal text by character, even in situations where it is not recommended.
It may improve performance when large volumes of text are written to the console and allow more accurate reporting of changes made in the middle of lines.
However, it may be incompatible with some applications, so Diff Match Patch is not always used.
This feature is supported in Windows Console on Windows 10 versions 1607 and later.
Additionally, it may be available in other terminals on earlier Windows releases.
- force Difflib: this option causes NVDA to calculate changes to terminal text by line.
However, in some applications, reading of new text may be choppy or inconsistent.
- Difflib: this option causes NVDA to calculate changes to terminal text by line, even in situations where it is not recommended.
It is identical to NVDA's behaviour in versions 2020.4 and earlier.
This setting may stabilize reading of incoming text in some applications.
However, in terminals, when inserting or deleting a character in the middle of a line, the text after the caret will be read out.
-

==== Attempt to cancel speech for expired focus events ====[CancelExpiredFocusSpeech]
Expand Down

0 comments on commit fd9ae8c

Please sign in to comment.