Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 of refactor LiveText to use diff-match-patch #11639

Merged
merged 62 commits into from
Dec 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8b1ff14
Refactor LiveText to use diff-match-patch via IPC with a C++ process.
codeofdusk Sep 19, 2020
bddf453
Explain magic numbers.
codeofdusk Sep 21, 2020
59cfbb5
switch to a Python implementation of nvda_dmp. This commit definitely…
codeofdusk Sep 24, 2020
816ea00
Compile nvda_dmp as an exe.
codeofdusk Sep 26, 2020
74ac3b3
Move import.
codeofdusk Sep 26, 2020
7bc466e
Uniformize ends of inserted chunks.
codeofdusk Sep 26, 2020
1e4b3f4
s/available/supported in config, for consistency with rest of code an…
codeofdusk Sep 26, 2020
917a903
Add diffing algorithm to devInfo.
codeofdusk Sep 27, 2020
5fbe33d
Fix docs/comments
codeofdusk Sep 27, 2020
998c851
Switch to a submodule.
codeofdusk Sep 29, 2020
a7abf02
Fix source copies.
codeofdusk Sep 29, 2020
f12e2ba
Use absolute path to nvda_dmp.
codeofdusk Sep 29, 2020
ba42885
Allow NVDA to still function if a component changes the current direc…
michaelDCurran Sep 29, 2020
430f24d
Merge branch 'master' into livetext-dmp-cpp
codeofdusk Sep 30, 2020
6850cb1
Merge branch 'master' of https://github.com/nvaccess/nvda
codeofdusk Sep 30, 2020
fec7cdf
Merge branch 'master' into livetext-dmp-cpp
codeofdusk Sep 30, 2020
03b4eb9
Add comment.
codeofdusk Sep 30, 2020
5f18189
Update build script pythonpath.
codeofdusk Sep 30, 2020
834e698
Move import
codeofdusk Sep 30, 2020
74c418f
Address review actions.
codeofdusk Oct 2, 2020
114ab85
Don't restrict DMP setting to 1607+.
codeofdusk Oct 2, 2020
e58a0d3
Update user guide to clarify availability.
codeofdusk Oct 2, 2020
4140003
Move to a class-based approach.
codeofdusk Oct 8, 2020
46eccea
Change the DMP setting to a three-way option.
codeofdusk Oct 8, 2020
0b3c59f
Add logging, update comment.
codeofdusk Oct 8, 2020
ec879e6
Fix one last comment.
codeofdusk Oct 8, 2020
6fd1fbe
Address review actions.
codeofdusk Oct 8, 2020
9b6761d
Review actions.
codeofdusk Oct 8, 2020
561ef04
Add docstrings.
codeofdusk Oct 8, 2020
efba5d1
Change UI to show actual diff algorithms.
codeofdusk Oct 8, 2020
139d345
Review actions.
codeofdusk Oct 9, 2020
250841e
Update source/diffHandler.py
codeofdusk Oct 9, 2020
abfb69f
Remove prefer.
codeofdusk Oct 9, 2020
63aae02
Fix accelerator.
codeofdusk Oct 12, 2020
9d52372
Merge branch 'master' into livetext-dmp-cpp
codeofdusk Oct 12, 2020
eb32b26
Centralize config check.
codeofdusk Oct 12, 2020
8438518
Update user guide.
codeofdusk Oct 12, 2020
3fa0b39
Re-centralize _get_diffAlgo on the LiveText object, but add a compreh…
codeofdusk Oct 13, 2020
f119840
Update nvda_dmp.
codeofdusk Oct 13, 2020
a4a6ec5
Cleanup.
codeofdusk Oct 13, 2020
ecf2967
s/prefer/allow
codeofdusk Oct 13, 2020
4e1aceb
Move config check.
codeofdusk Oct 13, 2020
094244c
Merge branch 'master' into livetext-dmp-cpp
codeofdusk Oct 14, 2020
25e2810
Review actions.
codeofdusk Oct 14, 2020
4190423
Merge branch 'master' into livetext-dmp-cpp
codeofdusk Oct 16, 2020
f0a0141
Review actions.
codeofdusk Oct 19, 2020
9b53399
Sync.
codeofdusk Oct 19, 2020
a89a4fe
Update user_docs/en/userGuide.t2t
codeofdusk Oct 21, 2020
279cbd4
Remove unneeded type checks in legacy console.
codeofdusk Oct 21, 2020
bfc85e7
Fix comments.
codeofdusk Oct 21, 2020
afb4a76
Review actions.
codeofdusk Oct 29, 2020
71da66a
Review actions.
codeofdusk Oct 30, 2020
2174191
Review actions.
codeofdusk Nov 5, 2020
1eecdfb
Wait for NVDA_dmp to exit, and throw an exception if it doesn't withi…
codeofdusk Nov 17, 2020
578ab7d
Don't set _proc to None on termination, so that _initialize cannot be…
codeofdusk Nov 17, 2020
47b0c13
Review action.
codeofdusk Nov 30, 2020
b4f65fe
Remove _getTextLines entirely, as this PR has been retargetted for 20…
codeofdusk Dec 8, 2020
dbd8ad8
Merge branch 'master' into livetext-dmp-cpp
codeofdusk Dec 8, 2020
16db1a9
Merge branch 'master' into livetext-dmp-cpp
codeofdusk Dec 19, 2020
7a8d579
Update user guide (s/2020.4/2021.1) as PR was held back.
codeofdusk Dec 19, 2020
d890ac9
Merge remote-tracking branch 'origin/master' into livetext-dmp-cpp
feerrenrut Dec 31, 2020
78e4012
update changes file for PR #11639
feerrenrut Dec 31, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
[submodule "include/javaAccessBridge32"]
path = include/javaAccessBridge32
url = https://github.com/nvaccess/javaAccessBridge32-bin.git
[submodule "include/nvda_dmp"]
path = include/nvda_dmp
url = https://github.com/codeofdusk/nvda_dmp
[submodule "include/w3c-aria-practices"]
path = include/w3c-aria-practices
url = https://github.com/w3c/aria-practices
Expand Down
1 change: 1 addition & 0 deletions include/nvda_dmp
Submodule nvda_dmp added at b2ccf2
45 changes: 34 additions & 11 deletions source/NVDAObjects/IAccessible/winConsole.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
#NVDAObjects/IAccessible/WinConsole.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2007-2019 NV Access Limited, Bill Dengler
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2007-2020 NV Access Limited, Bill Dengler

import config

from NVDAObjects.behaviors import KeyboardHandlerBasedTypedCharSupport
from winVersion import isWin10

from . import IAccessible
from ..window import winConsole

class WinConsole(winConsole.WinConsole, IAccessible):
"The legacy console implementation for situations where UIA isn't supported."
pass

class EnhancedLegacyWinConsole(KeyboardHandlerBasedTypedCharSupport, winConsole.WinConsole, IAccessible):
"""
A hybrid approach to console access, using legacy APIs to read output
and KeyboardHandlerBasedTypedCharSupport for input.
"""
#: Legacy consoles take quite a while to send textChange events.
#: This significantly impacts typing performance, so don't queue chars.
_supportsTextChange = False


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()


def findExtraOverlayClasses(obj, clsList):
if isWin10(1607) and config.conf['terminals']['keyboardSupportInLegacy']:
from NVDAObjects.behaviors import KeyboardHandlerBasedTypedCharSupport
clsList.append(KeyboardHandlerBasedTypedCharSupport)
clsList.append(WinConsole)
clsList.append(EnhancedLegacyWinConsole)
else:
clsList.append(LegacyWinConsole)
15 changes: 0 additions & 15 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# NVDAObjects/UIA/winConsoleUIA.py
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
Expand Down Expand Up @@ -363,20 +362,6 @@ def _get_TextInfo(self):
movement."""
return consoleUIATextInfo if self.is21H1Plus else consoleUIATextInfoPre21H1

def _getTextLines(self):
if self.is21H1Plus:
# #11760: the 21H1 UIA console wraps across lines.
# When text wraps, NVDA starts reading from the beginning of the visible text for every new line of output.
# Use the superclass _getTextLines instead.
return super()._getTextLines()
# This override of _getTextLines takes advantage of the fact that
# the console text contains linefeeds for every line
# Thus a simple string splitlines is much faster than splitting by unit line.
ti = self.makeTextInfo(textInfos.POSITION_ALL)
text = ti.text or ""
return text.splitlines()


def detectPossibleSelectionChange(self):
try:
return super().detectPossibleSelectionChange()
Expand Down
96 changes: 32 additions & 64 deletions source/NVDAObjects/behaviors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: UTF-8 -*-
# NVDAObjects/behaviors.py
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
Expand All @@ -12,7 +11,6 @@
import os
import time
import threading
import difflib
import tones
import queueHandler
import eventHandler
Expand All @@ -30,6 +28,8 @@
import braille
import nvwave
import globalVars
from typing import List
import diffHandler


class ProgressBar(NVDAObject):
Expand Down Expand Up @@ -265,15 +265,31 @@ def event_textChange(self):
"""
self._event.set()

def _getTextLines(self):
"""Retrieve the text of this object in lines.
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).
@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.
"""
return diffHandler.get_dmp_algo()

def _get_devInfo(self):
info = super().devInfo
info.append(f"diffing algorithm: {self.diffAlgo}")
return info

def _getText(self) -> str:
"""Retrieve the text of this object.
This will be used to determine the new text to speak.
The base implementation uses the L{TextInfo}.
However, subclasses should override this if there is a better way to retrieve the text.
@return: The current lines of text.
@rtype: list of str
"""
return list(self.makeTextInfo(textInfos.POSITION_ALL).getTextInChunks(textInfos.UNIT_LINE))
ti = self.makeTextInfo(textInfos.POSITION_ALL)
return self.diffAlgo._getText(ti)

def _reportNewLines(self, lines):
"""
Expand All @@ -291,10 +307,10 @@ def _reportNewText(self, line):

def _monitor(self):
try:
oldLines = self._getTextLines()
oldText = self._getText()
except:
log.exception("Error getting initial lines")
oldLines = []
log.exception("Error getting initial text")
oldText = ""

while self._keepMonitoring:
self._event.wait()
Expand All @@ -309,71 +325,23 @@ def _monitor(self):
self._event.clear()

try:
newLines = self._getTextLines()
newText = self._getText()
if config.conf["presentation"]["reportDynamicContentChanges"]:
outLines = self._calculateNewText(newLines, oldLines)
outLines = self._calculateNewText(newText, oldText)
if len(outLines) == 1 and len(outLines[0].strip()) == 1:
# This is only a single character,
# which probably means it is just a typed character,
# so ignore it.
del outLines[0]
if outLines:
queueHandler.queueFunction(queueHandler.eventQueue, self._reportNewLines, outLines)
oldLines = newLines
oldText = newText
except:
log.exception("Error getting lines or calculating new text")

def _calculateNewText(self, newLines, oldLines):
outLines = []

prevLine = None
for line in difflib.ndiff(oldLines, newLines):
if line[0] == "?":
# We're never interested in these.
continue
if line[0] != "+":
# We're only interested in new lines.
prevLine = line
continue
text = line[2:]
if not text or text.isspace():
prevLine = line
continue

if prevLine and prevLine[0] == "-" and len(prevLine) > 2:
# It's possible that only a few characters have changed in this line.
# If so, we want to speak just the changed section, rather than the entire line.
prevText = prevLine[2:]
textLen = len(text)
prevTextLen = len(prevText)
# Find the first character that differs between the two lines.
for pos in range(min(textLen, prevTextLen)):
if text[pos] != prevText[pos]:
start = pos
break
else:
# We haven't found a differing character so far and we've hit the end of one of the lines.
# This means that the differing text starts here.
start = pos + 1
# Find the end of the differing text.
if textLen != prevTextLen:
# The lines are different lengths, so assume the rest of the line changed.
end = textLen
else:
for pos in range(textLen - 1, start - 1, -1):
if text[pos] != prevText[pos]:
end = pos + 1
break

if end - start < 15:
# Less than 15 characters have changed, so only speak the changed chunk.
text = text[start:end]
log.exception("Error getting or calculating new text")

if text and not text.isspace():
outLines.append(text)
prevLine = line
def _calculateNewText(self, newText: str, oldText: str) -> List[str]:
return self.diffAlgo.diff(newText, oldText)

return outLines

class Terminal(LiveText, EditableText):
"""An object which both accepts text input and outputs text which should be reported automatically.
Expand Down
21 changes: 7 additions & 14 deletions source/NVDAObjects/window/winConsole.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#NVDAObjects/WinConsole.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2007-2019 NV Access Limited, Bill Dengler
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2007-2020 NV Access Limited, Bill Dengler

import winConsoleHandler
from . import Window
Expand All @@ -14,18 +13,12 @@

class WinConsole(Terminal, EditableTextWithoutAutoSelectDetection, Window):
"""
NVDA's legacy Windows Console support.
Base class for NVDA's legacy Windows Console support.
This is used in situations where UIA isn't available.
Please consider using NVDAObjects.UIA.winConsoleUIA instead.
"""
STABILIZE_DELAY = 0.03

def initOverlayClass(self):
# Legacy consoles take quite a while to send textChange events.
# This significantly impacts typing performance, so don't queue chars.
if isinstance(self, KeyboardHandlerBasedTypedCharSupport):
self._supportsTextChange = False

def _get_windowThreadID(self):
# #10113: Windows forces the thread of console windows to match the thread of the first attached process.
# However, To correctly handle speaking of typed characters,
Expand Down Expand Up @@ -69,8 +62,8 @@ def event_loseFocus(self):
def event_nameChange(self):
pass

def _getTextLines(self):
return winConsoleHandler.getConsoleVisibleLines()
def _getText(self):
return '\n'.join(winConsoleHandler.getConsoleVisibleLines())

def script_caret_backspaceCharacter(self, gesture):
super(WinConsole, self).script_caret_backspaceCharacter(gesture)
Expand Down
1 change: 1 addition & 0 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
[terminals]
speakPasswords = boolean(default=false)
keyboardSupportInLegacy = boolean(default=True)
diffAlgo = option("auto", "dmp", "difflib", default="auto")
[update]
autoCheck = boolean(default=true)
Expand Down
8 changes: 8 additions & 0 deletions source/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,14 @@ def run(self):
_terminate(speech)
_terminate(addonHandler)
_terminate(garbageHandler)
# DMP is only started if needed.
# Terminate manually (and let it write to the log if necessary)
# as core._terminate always writes an entry.
try:
import diffHandler
diffHandler._dmp._terminate()
except Exception:
log.exception("Exception while terminating DMP")

if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
try:
Expand Down
Loading