1212import os
1313import time
1414import threading
15- import difflib
15+ from diff_match_patch import diff_match_patch
1616import tones
1717import queueHandler
1818import eventHandler
@@ -228,6 +228,7 @@ def initOverlayClass(self):
228228 self ._event = threading .Event ()
229229 self ._monitorThread = None
230230 self ._keepMonitoring = False
231+ self ._dmp = diff_match_patch ()
231232
232233 def startMonitoring (self ):
233234 """Start monitoring for new text.
@@ -263,15 +264,18 @@ def event_textChange(self):
263264 """
264265 self ._event .set ()
265266
266- def _getTextLines (self ):
267- """Retrieve the text of this object in lines .
267+ def _getText (self ):
268+ """Retrieve the text of this object.
268269 This will be used to determine the new text to speak.
269270 The base implementation uses the L{TextInfo}.
270271 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
272+ @return: The current text ob the object .
273+ @rtype: str
273274 """
274- return list (self .makeTextInfo (textInfos .POSITION_ALL ).getTextInChunks (textInfos .UNIT_LINE ))
275+ if hasattr (self , "_getTextLines" ):
276+ log .warning ("LiveText._getTextLines is deprecated, please override _getText instead." )
277+ return '\n ' .join (self ._getTextLines ())
278+ return self .makeTextInfo (textInfos .POSITION_ALL ).text
275279
276280 def _reportNewLines (self , lines ):
277281 """
@@ -289,10 +293,10 @@ def _reportNewText(self, line):
289293
290294 def _monitor (self ):
291295 try :
292- oldLines = self ._getTextLines ()
296+ oldText = self ._getText ()
293297 except :
294- log .exception ("Error getting initial lines " )
295- oldLines = []
298+ log .exception ("Error getting initial text " )
299+ oldText = ""
296300
297301 while self ._keepMonitoring :
298302 self ._event .wait ()
@@ -307,59 +311,61 @@ def _monitor(self):
307311 self ._event .clear ()
308312
309313 try :
310- newLines = self ._getTextLines ()
314+ newText = self ._getText ()
311315 if config .conf ["presentation" ]["reportDynamicContentChanges" ]:
312- outLines = self ._calculateNewText (newLines , oldLines )
316+ outLines = self ._calculateNewText (newText , oldText )
313317 if len (outLines ) == 1 and len (outLines [0 ].strip ()) == 1 :
314318 # This is only a single character,
315319 # which probably means it is just a typed character,
316320 # so ignore it.
317321 del outLines [0 ]
318322 if outLines :
319323 queueHandler .queueFunction (queueHandler .eventQueue , self ._reportNewLines , outLines )
320- oldLines = newLines
324+ oldText = newText
321325 except :
322- log .exception ("Error getting lines or calculating new text" )
326+ log .exception ("Error getting or calculating new text" )
323327
324- def _calculateNewText (self , newLines , oldLines ):
325- outLines = []
328+ def _calculateNewText (self , newText , oldText ):
329+ (oldText , newText , linearray ) = self ._dmp .diff_linesToChars (oldText , newText )
330+ diffs = self ._dmp .diff_main (oldText , newText , checklines = False )
331+ # Convert the diff back to original text.
332+ self ._dmp .diff_charsToLines (diffs , linearray )
333+ # Eliminate freak matches (e.g. blank lines)
334+ self ._dmp .diff_cleanupSemantic (diffs )
326335
336+ outLines = []
327337 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 ] != "+" :
338+ prevChange = None
339+ for change , text in diffs :
340+ if change != self ._dmp .DIFF_INSERT :
333341 # We're only interested in new lines.
334- prevLine = line
342+ prevChange , prevLine = change , text
335343 continue
336- text = line [2 :]
337344 if not text or text .isspace ():
338- prevLine = line
345+ prevChange , prevLine = change , text
339346 continue
340347
341- if prevLine and prevLine [ 0 ] == "-" and len ( prevLine ) > 2 :
348+ if prevLine and prevChange == self . _dmp . DIFF_DELETE and not prevLine . isspace () :
342349 # It's possible that only a few characters have changed in this line.
343350 # If so, we want to speak just the changed section, rather than the entire line.
344- prevText = prevLine [2 :]
345351 textLen = len (text )
346- prevTextLen = len (prevText )
352+ prevLineLen = len (prevLine )
347353 # Find the first character that differs between the two lines.
348- for pos in range (min (textLen , prevTextLen )):
349- if text [pos ] != prevText [pos ]:
354+ for pos in range (min (textLen , prevLineLen )):
355+ if text [pos ] != prevLine [pos ]:
350356 start = pos
351357 break
352358 else :
353359 # We haven't found a differing character so far and we've hit the end of one of the lines.
354360 # This means that the differing text starts here.
355361 start = pos + 1
356362 # Find the end of the differing text.
357- if textLen != prevTextLen :
363+ if textLen != prevLineLen :
358364 # The lines are different lengths, so assume the rest of the line changed.
359365 end = textLen
360366 else :
361367 for pos in range (textLen - 1 , start - 1 , - 1 ):
362- if text [pos ] != prevText [pos ]:
368+ if text [pos ] != prevLine [pos ]:
363369 end = pos + 1
364370 break
365371
@@ -369,9 +375,10 @@ def _calculateNewText(self, newLines, oldLines):
369375
370376 if text and not text .isspace ():
371377 outLines .append (text )
372- prevLine = line
378+ prevChange , prevLine = change , text
379+
380+ return [line for outLine in outLines for line in outLine .splitlines () if line and not line .isspace ()]
373381
374- return outLines
375382
376383class Terminal (LiveText , EditableText ):
377384 """An object which both accepts text input and outputs text which should be reported automatically.
0 commit comments