From 21b7ee5dc83e53553ae0f6dc39d11fbdee1db350 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 21 Oct 2021 15:55:12 -0400 Subject: [PATCH] Prefer Difflib in scrolling LiveText objects and allow users to forceably override this choice. --- source/NVDAObjects/IAccessible/winConsole.py | 10 +--------- source/NVDAObjects/UIA/winConsoleUIA.py | 9 +++++++++ source/NVDAObjects/behaviors.py | 15 +++++++++------ source/NVDAObjects/window/__init__.py | 7 +++++++ source/NVDAObjects/window/winConsole.py | 6 ++++++ source/diffHandler.py | 16 ++++++++++++---- source/gui/settingsDialogs.py | 6 +++--- user_docs/en/userGuide.t2t | 12 ++++++------ 8 files changed, 53 insertions(+), 28 deletions(-) diff --git a/source/NVDAObjects/IAccessible/winConsole.py b/source/NVDAObjects/IAccessible/winConsole.py index 9eb85cc8b29..cb4ed6bc6ed 100644 --- a/source/NVDAObjects/IAccessible/winConsole.py +++ b/source/NVDAObjects/IAccessible/winConsole.py @@ -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): diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index 0b51ba249c8..bf420d9c378 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -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 @@ -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: + # 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() diff --git a/source/NVDAObjects/behaviors.py b/source/NVDAObjects/behaviors.py index bdccf4eb244..b5ac631a8fa 100755 --- a/source/NVDAObjects/behaviors.py +++ b/source/NVDAObjects/behaviors.py @@ -269,14 +269,17 @@ def event_textChange(self): def _get_diffAlgo(self): """ 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: Barring a very good reason to do otherwise, please return + either diffHandler.prefer_dmp() or diffHandler.prefer_difflib() + so that user preference can override this choice. """ - return diffHandler.get_dmp_algo() + return diffHandler.prefer_dmp() def _get_devInfo(self): info = super().devInfo diff --git a/source/NVDAObjects/window/__init__.py b/source/NVDAObjects/window/__init__.py index 44d20d49bb9..7c1b00b3c4d 100644 --- a/source/NVDAObjects/window/__init__.py +++ b/source/NVDAObjects/window/__init__.py @@ -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:(.*)$') @@ -413,6 +414,12 @@ def stopMonitoring(self): super(DisplayModelLiveText, self).stopMonitoring() displayModel.requestTextChangeNotifications(self, False) + def _get_diffAlgo(self): + # 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", diff --git a/source/NVDAObjects/window/winConsole.py b/source/NVDAObjects/window/winConsole.py index 01ecec4e75e..72b29865951 100644 --- a/source/NVDAObjects/window/winConsole.py +++ b/source/NVDAObjects/window/winConsole.py @@ -10,6 +10,7 @@ import core from scriptHandler import script import speech +from diffHandler import prefer_difflib class WinConsole(Terminal, EditableTextWithoutAutoSelectDetection, Window): """ @@ -36,6 +37,11 @@ def _get_TextInfo(self): return winConsoleHandler.WinConsoleTextInfo return super(WinConsole,self).TextInfo + def _get_diffAlgo(self): + # 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: diff --git a/source/diffHandler.py b/source/diffHandler.py index aa17e9deced..d40cfa8e1e8 100644 --- a/source/diffHandler.py +++ b/source/diffHandler.py @@ -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 @@ -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() diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 21a59472aa2..4d35d9c46f0 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -2730,7 +2730,7 @@ 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, force Diff Match Patch, and force Difflib. diffAlgoComboText = _("&Diff algorithm:") diffAlgoChoices = [ # Translators: A choice in a combo box in the advanced settings @@ -2740,11 +2740,11 @@ def __init__(self, parent): # 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"), + _("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. diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 67b20718537..531c7337394 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1895,14 +1895,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: as of NVDA 2021.2, this option is equivalent to "Diff Match Patch". +- 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]