Skip to content

Commit adb46d3

Browse files
authored
Merge d6b4280 into 89c84b8
2 parents 89c84b8 + d6b4280 commit adb46d3

4 files changed

Lines changed: 45 additions & 85 deletions

File tree

source/NVDAObjects/UIA/winConsoleUIA.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -366,13 +366,6 @@ def _get_TextInfo(self):
366366
movement."""
367367
return consoleUIATextInfo if self.is21H1Plus else consoleUIATextInfoPre21H1
368368

369-
def _getTextLines(self):
370-
# This override of _getTextLines takes advantage of the fact that
371-
# the console text contains linefeeds for every line
372-
# Thus a simple string splitlines is much faster than splitting by unit line.
373-
ti = self.makeTextInfo(textInfos.POSITION_ALL)
374-
text = ti.text or ""
375-
return text.splitlines()
376369

377370
def findExtraOverlayClasses(obj, clsList):
378371
if obj.UIAElement.cachedAutomationId == "Text Area":

source/NVDAObjects/behaviors.py

Lines changed: 30 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import os
1313
import time
1414
import threading
15-
import difflib
15+
from diff_match_patch import diff_match_patch
1616
import tones
1717
import queueHandler
1818
import eventHandler
@@ -219,6 +219,10 @@ class LiveText(NVDAObject):
219219
"""
220220
#: The time to wait before fetching text after a change event.
221221
STABILIZE_DELAY = 0
222+
#: The maximum amount of time (in seconds) to wait for a diff to complete,
223+
#: 0 for infinity.
224+
#: Longer timeouts increase diff quality at the cost of performance.
225+
DIFF_DEADLINE = 3
222226
# If the text is live, this is definitely content.
223227
presentationType = NVDAObject.presType_content
224228

@@ -228,6 +232,7 @@ def initOverlayClass(self):
228232
self._event = threading.Event()
229233
self._monitorThread = None
230234
self._keepMonitoring = False
235+
self._dmp = diff_match_patch()
231236

232237
def startMonitoring(self):
233238
"""Start monitoring for new text.
@@ -263,15 +268,18 @@ def event_textChange(self):
263268
"""
264269
self._event.set()
265270

266-
def _getTextLines(self):
267-
"""Retrieve the text of this object in lines.
271+
def _getText(self):
272+
"""Retrieve the text of this object.
268273
This will be used to determine the new text to speak.
269274
The base implementation uses the L{TextInfo}.
270275
However, subclasses should override this if there is a better way to retrieve the text.
271-
@return: The current lines of text.
272-
@rtype: list of str
276+
@return: The current text ob the object.
277+
@rtype: str
273278
"""
274-
return list(self.makeTextInfo(textInfos.POSITION_ALL).getTextInChunks(textInfos.UNIT_LINE))
279+
if hasattr(self, "_getTextLines"):
280+
log.warning("LiveText._getTextLines is deprecated, please override _getText instead.")
281+
return '\n'.join(self._getTextLines())
282+
return self.makeTextInfo(textInfos.POSITION_ALL).text
275283

276284
def _reportNewLines(self, lines):
277285
"""
@@ -289,10 +297,10 @@ def _reportNewText(self, line):
289297

290298
def _monitor(self):
291299
try:
292-
oldLines = self._getTextLines()
300+
oldText = self._getText()
293301
except:
294-
log.exception("Error getting initial lines")
295-
oldLines = []
302+
log.exception("Error getting initial text")
303+
oldText = ""
296304

297305
while self._keepMonitoring:
298306
self._event.wait()
@@ -307,71 +315,30 @@ def _monitor(self):
307315
self._event.clear()
308316

309317
try:
310-
newLines = self._getTextLines()
318+
newText = self._getText()
311319
if config.conf["presentation"]["reportDynamicContentChanges"]:
312-
outLines = self._calculateNewText(newLines, oldLines)
320+
outLines = self._calculateNewText(newText, oldText)
313321
if len(outLines) == 1 and len(outLines[0].strip()) == 1:
314322
# This is only a single character,
315323
# which probably means it is just a typed character,
316324
# so ignore it.
317325
del outLines[0]
318326
if outLines:
319327
queueHandler.queueFunction(queueHandler.eventQueue, self._reportNewLines, outLines)
320-
oldLines = newLines
328+
oldText = newText
321329
except:
322-
log.exception("Error getting lines or calculating new text")
323-
324-
def _calculateNewText(self, newLines, oldLines):
325-
outLines = []
326-
327-
prevLine = None
328-
for line in difflib.ndiff(oldLines, newLines):
329-
if line[0] == "?":
330-
# We're never interested in these.
331-
continue
332-
if line[0] != "+":
333-
# We're only interested in new lines.
334-
prevLine = line
335-
continue
336-
text = line[2:]
337-
if not text or text.isspace():
338-
prevLine = line
339-
continue
330+
log.exception("Error getting or calculating new text")
340331

341-
if prevLine and prevLine[0] == "-" and len(prevLine) > 2:
342-
# It's possible that only a few characters have changed in this line.
343-
# If so, we want to speak just the changed section, rather than the entire line.
344-
prevText = prevLine[2:]
345-
textLen = len(text)
346-
prevTextLen = len(prevText)
347-
# Find the first character that differs between the two lines.
348-
for pos in range(min(textLen, prevTextLen)):
349-
if text[pos] != prevText[pos]:
350-
start = pos
351-
break
352-
else:
353-
# We haven't found a differing character so far and we've hit the end of one of the lines.
354-
# This means that the differing text starts here.
355-
start = pos + 1
356-
# Find the end of the differing text.
357-
if textLen != prevTextLen:
358-
# The lines are different lengths, so assume the rest of the line changed.
359-
end = textLen
360-
else:
361-
for pos in range(textLen - 1, start - 1, -1):
362-
if text[pos] != prevText[pos]:
363-
end = pos + 1
364-
break
365-
366-
if end - start < 15:
367-
# Less than 15 characters have changed, so only speak the changed chunk.
368-
text = text[start:end]
369-
370-
if text and not text.isspace():
371-
outLines.append(text)
372-
prevLine = line
332+
def _calculateNewText(self, newText, oldText):
333+
return [
334+
line
335+
for change, line in self._dmp.diff_lineMode(
336+
oldText, newText, self.DIFF_DEADLINE
337+
)
338+
if change == self._dmp.DIFF_INSERT
339+
and not line.isspace()
340+
]
373341

374-
return outLines
375342

376343
class Terminal(LiveText, EditableText):
377344
"""An object which both accepts text input and outputs text which should be reported automatically.

source/NVDAObjects/window/winConsole.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#NVDAObjects/WinConsole.py
2-
#A part of NonVisual Desktop Access (NVDA)
3-
#This file is covered by the GNU General Public License.
4-
#See the file COPYING for more details.
5-
#Copyright (C) 2007-2019 NV Access Limited, Bill Dengler
1+
# NVDAObjects/WinConsole.py
2+
# A part of NonVisual Desktop Access (NVDA)
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
5+
# Copyright (C) 2007-2020 NV Access Limited, Bill Dengler
66

77
import winConsoleHandler
88
from . import Window
@@ -69,8 +69,8 @@ def event_loseFocus(self):
6969
def event_nameChange(self):
7070
pass
7171

72-
def _getTextLines(self):
73-
return winConsoleHandler.getConsoleVisibleLines()
72+
def _getText(self):
73+
return winConsoleHandler.getConsoleVisibleText()
7474

7575
def script_caret_backspaceCharacter(self, gesture):
7676
super(WinConsole, self).script_caret_backspaceCharacter(gesture)

source/winConsoleHandler.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#winConsoleHandler.py
2-
#A part of NonVisual Desktop Access (NVDA)
3-
#This file is covered by the GNU General Public License.
4-
#See the file COPYING for more details.
5-
#Copyright (C) 2009-2018 NV Access Limited, Babbage B.V.
1+
# winConsoleHandler.py
2+
# A part of NonVisual Desktop Access (NVDA)
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
5+
# Copyright (C) 2009-2020 NV Access Limited, Babbage B.V., Bill Dengler
66

77
import gui
88
import winUser
@@ -123,14 +123,14 @@ def _checkDead():
123123
except:
124124
log.exception()
125125

126-
def getConsoleVisibleLines():
126+
127+
def getConsoleVisibleText():
127128
consoleScreenBufferInfo=wincon.GetConsoleScreenBufferInfo(consoleOutputHandle)
128129
topLine=consoleScreenBufferInfo.srWindow.Top
129130
lineCount=(consoleScreenBufferInfo.srWindow.Bottom-topLine)+1
130131
lineLength=consoleScreenBufferInfo.dwSize.x
131132
text=wincon.ReadConsoleOutputCharacter(consoleOutputHandle,lineCount*lineLength,0,topLine)
132-
newLines=[text[x:x+lineLength] for x in range(0,len(text),lineLength)]
133-
return newLines
133+
return text
134134

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

0 commit comments

Comments
 (0)