Skip to content
Closed
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@
[submodule "include/javaAccessBridge32"]
path = include/javaAccessBridge32
url = https://github.com/nvaccess/javaAccessBridge32-bin.git
[submodule "include/dmp"]
path = include/dmp
url = https://github.com/diff-match-patch-python/diff-match-patch
1 change: 1 addition & 0 deletions include/dmp
Submodule dmp added at f63dfe
8 changes: 0 additions & 8 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 @@ -366,13 +365,6 @@ def _get_TextInfo(self):
movement."""
return consoleUIATextInfo if self.is21H1Plus else consoleUIATextInfoPre21H1

def _getTextLines(self):
# 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 findExtraOverlayClasses(obj, clsList):
if obj.UIAElement.cachedAutomationId == "Text Area":
Expand Down
72 changes: 39 additions & 33 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,7 @@
import os
import time
import threading
import difflib
from diff_match_patch import diff_match_patch
import tones
import queueHandler
import eventHandler
Expand Down Expand Up @@ -228,6 +227,7 @@ def initOverlayClass(self):
self._event = threading.Event()
self._monitorThread = None
self._keepMonitoring = False
self._dmp = diff_match_patch()

def startMonitoring(self):
"""Start monitoring for new text.
Expand Down Expand Up @@ -263,15 +263,18 @@ def event_textChange(self):
"""
self._event.set()

def _getTextLines(self):
"""Retrieve the text of this object in lines.
def _getText(self):
"""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: The current text ob the object.
@rtype: str
Comment thread
codeofdusk marked this conversation as resolved.
Resolved
"""
return list(self.makeTextInfo(textInfos.POSITION_ALL).getTextInChunks(textInfos.UNIT_LINE))
if hasattr(self, "_getTextLines"):
log.warning("LiveText._getTextLines is deprecated, please override _getText instead.")
return '\n'.join(self._getTextLines())
return self.makeTextInfo(textInfos.POSITION_ALL).text

def _reportNewLines(self, lines):
"""
Expand All @@ -289,10 +292,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 @@ -307,59 +310,61 @@ 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")
log.exception("Error getting or calculating new text")

def _calculateNewText(self, newLines, oldLines):
outLines = []
def _calculateNewText(self, newText, oldText):
Comment thread
codeofdusk marked this conversation as resolved.
Resolved
(oldText, newText, linearray) = self._dmp.diff_linesToChars(oldText, newText)
diffs = self._dmp.diff_main(oldText, newText, checklines=False)
# Convert the diff back to original text.
self._dmp.diff_charsToLines(diffs, linearray)
# Eliminate freak matches (e.g. blank lines)
self._dmp.diff_cleanupSemantic(diffs)

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

if prevLine and prevLine[0] == "-" and len(prevLine) > 2:
if prevLine and prevChange == self._dmp.DIFF_DELETE and not prevLine.isspace():
# 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)
prevLineLen = len(prevLine)
# Find the first character that differs between the two lines.
for pos in range(min(textLen, prevTextLen)):
if text[pos] != prevText[pos]:
for pos in range(min(textLen, prevLineLen)):
if text[pos] != prevLine[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:
if textLen != prevLineLen:
# 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]:
if text[pos] != prevLine[pos]:
end = pos + 1
break

Expand All @@ -369,9 +374,10 @@ def _calculateNewText(self, newLines, oldLines):

if text and not text.isspace():
outLines.append(text)
prevLine = line
prevChange, prevLine = change, text

return [line for outLine in outLines for line in outLine.splitlines() if line and not line.isspace()]

return outLines

class Terminal(LiveText, EditableText):
"""An object which both accepts text input and outputs text which should be reported automatically.
Expand Down
13 changes: 6 additions & 7 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 Down Expand Up @@ -69,8 +68,8 @@ def event_loseFocus(self):
def event_nameChange(self):
pass

def _getTextLines(self):
return winConsoleHandler.getConsoleVisibleLines()
def _getText(self):
return winConsoleHandler.getConsoleVisibleText()

def script_caret_backspaceCharacter(self, gesture):
super(WinConsole, self).script_caret_backspaceCharacter(gesture)
Expand Down
3 changes: 2 additions & 1 deletion source/sourceEnv.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2013-2020 NV Access Limited, Leonard de Ruijter
# Copyright (C) 2013-2020 NV Access Limited, Leonard de Ruijter, Bill Dengler
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

Expand All @@ -18,6 +18,7 @@
os.path.join(TOP_DIR, "include", "configobj", "src"),
os.path.join(TOP_DIR, "include", "wxPython"),
os.path.join(TOP_DIR, "include", "py2exe"),
os.path.join(TOP_DIR, "include", "dmp", "diff_match_patch"),
os.path.join(TOP_DIR, "miscDeps", "python"),
)

Expand Down
15 changes: 7 additions & 8 deletions source/winConsoleHandler.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#winConsoleHandler.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) 2009-2018 NV Access Limited, Babbage B.V.
# 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) 2009-2020 NV Access Limited, Babbage B.V., Bill Dengler

import gui
import winUser
Expand Down Expand Up @@ -123,14 +122,14 @@ def _checkDead():
except:
log.exception()

def getConsoleVisibleLines():

def getConsoleVisibleText():
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
topLine=consoleScreenBufferInfo.srWindow.Top
lineCount=(consoleScreenBufferInfo.srWindow.Bottom-topLine)+1
lineLength=consoleScreenBufferInfo.dwSize.x
text=wincon.ReadConsoleOutputCharacter(consoleOutputHandle,lineCount*lineLength,0,topLine)
newLines=[text[x:x+lineLength] for x in range(0,len(text),lineLength)]
return newLines
return text

@winUser.WINEVENTPROC
def consoleWinEventHook(handle,eventID,window,objectID,childID,threadID,timestamp):
Expand Down