improved documentation for the diff generation.
This commit is contained in:
parent
6998706934
commit
b0ac6e1409
@ -233,57 +233,110 @@ class attributeManager():
|
||||
|
||||
return attributeFormatString
|
||||
def trackHighlights(self):
|
||||
"""
|
||||
Detects text with changed attributes (highlighting) between screen updates.
|
||||
|
||||
This is crucial for screen readers to announce when text becomes highlighted,
|
||||
selected, or changes visual emphasis (bold, reverse video, color changes, etc.)
|
||||
|
||||
Returns:
|
||||
tuple: (highlighted_text, cursor_position)
|
||||
- highlighted_text: string of characters that gained highlighting
|
||||
- cursor_position: dict {'x': col, 'y': row} of first highlighted char
|
||||
"""
|
||||
result = ''
|
||||
currCursor = None
|
||||
# screen change
|
||||
|
||||
# Early exit conditions - no attribute comparison possible
|
||||
if self.prevAttributes == None:
|
||||
# First screen load - no previous attributes to compare against
|
||||
return result, currCursor
|
||||
# no change
|
||||
if self.prevAttributes == self.currAttributes:
|
||||
# No attribute changes detected
|
||||
return result, currCursor
|
||||
# error case
|
||||
if self.currAttributes == None:
|
||||
# Error condition - current attributes missing
|
||||
return result, currCursor
|
||||
# special case for pty if not text exists.
|
||||
if len(self.currAttributes) == 0:
|
||||
# Special case for PTY environments with no text content
|
||||
return result, currCursor
|
||||
|
||||
# Get current screen text to correlate with attribute changes
|
||||
text = self.env['runtime']['screenManager'].getScreenText()
|
||||
textLines = text.split('\n')
|
||||
|
||||
# Sanity check: text lines must match attribute array dimensions
|
||||
if len(textLines) != len(self.currAttributes):
|
||||
return result, currCursor
|
||||
|
||||
# Compare attributes line by line, character by character
|
||||
for line in range(len(self.prevAttributes)):
|
||||
if self.prevAttributes[line] != self.currAttributes[line]:
|
||||
# This line has attribute changes - examine each character position
|
||||
for column in range(len(self.prevAttributes[line])):
|
||||
if self.prevAttributes[line][column] == self.currAttributes[line][column]:
|
||||
# No change at this position
|
||||
continue
|
||||
# Attribute changed at this position - check if it's worth announcing
|
||||
if self.isUsefulForTracking(line, column, self.currAttributes, self.prevAttributes):
|
||||
# First highlighted character becomes cursor position for navigation
|
||||
if not currCursor:
|
||||
currCursor = {'x': column, 'y': line}
|
||||
# Accumulate highlighted characters
|
||||
result += textLines[line][column]
|
||||
# Add space between lines of highlighted text for speech clarity
|
||||
result += ' '
|
||||
return result, currCursor
|
||||
def isUsefulForTracking(self, line, column, currAttributes, prevAttributes, attribute=1 , mode = 'zaxe'):
|
||||
"""
|
||||
Determines if an attribute change at a specific position is worth announcing.
|
||||
|
||||
This prevents announcing every minor attribute change and focuses on meaningful
|
||||
highlighting that indicates user selections, focus changes, or important emphasis.
|
||||
|
||||
Args:
|
||||
line, column: Position of the attribute change
|
||||
currAttributes, prevAttributes: Current and previous attribute arrays
|
||||
attribute: Which attribute to examine (1=background color by default)
|
||||
mode: Detection algorithm ('zaxe', 'default', 'barrier')
|
||||
|
||||
Returns:
|
||||
bool: True if this attribute change should be announced to user
|
||||
"""
|
||||
# Sanity checks for valid position and sufficient screen content
|
||||
if len(currAttributes) <= 3:
|
||||
return False
|
||||
if line < 0:
|
||||
return False
|
||||
if line > len(currAttributes):
|
||||
return False
|
||||
|
||||
useful = False
|
||||
|
||||
if mode == 'default':
|
||||
# non default tracking
|
||||
# Simple mode: announce any non-default attribute
|
||||
useful = not self.isDefaultAttribute(currAttributes[line][column])
|
||||
|
||||
elif (mode == 'zaxe') or (mode == ''):
|
||||
# arround me tracking for bg
|
||||
# Context-aware mode: only announce attributes that stand out from surroundings
|
||||
# This prevents announcing entire blocks of highlighted text character by character
|
||||
# by checking if the attribute differs from adjacent lines
|
||||
|
||||
if line == 0:
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and (currAttributes[line][column][attribute] != currAttributes[line + 2][column][attribute])
|
||||
# Top line: compare against lines below
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and \
|
||||
(currAttributes[line][column][attribute] != currAttributes[line + 2][column][attribute])
|
||||
elif line >= len(prevAttributes):
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute]) and (currAttributes[line][column][attribute] != currAttributes[line - 2][column][attribute])
|
||||
# Bottom line: compare against lines above
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute]) and \
|
||||
(currAttributes[line][column][attribute] != currAttributes[line - 2][column][attribute])
|
||||
else:
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and (currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute])
|
||||
# Middle lines: compare against both directions
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and \
|
||||
(currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute])
|
||||
|
||||
elif mode == 'barrier':
|
||||
# to be implement
|
||||
# Barrier mode: future enhancement for detecting screen boundaries/separators
|
||||
useful = True
|
||||
|
||||
return useful
|
||||
|
@ -120,47 +120,94 @@ class screenManager():
|
||||
self.env['screen']['newDelta'] = ''
|
||||
self.env['runtime']['attributeManager'].resetAttributeDelta()
|
||||
|
||||
# changes on the screen
|
||||
# Diff generation - critical for screen reader functionality
|
||||
# This code detects and categorizes screen content changes to provide appropriate
|
||||
# speech feedback (typing echo vs incoming text vs screen updates)
|
||||
|
||||
# Pre-process screen text for comparison - collapse multiple spaces to single space
|
||||
# This normalization prevents spurious diffs from spacing inconsistencies
|
||||
oldScreenText = re.sub(' +',' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['oldContentText']))
|
||||
newScreenText = re.sub(' +',' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['newContentText']))
|
||||
|
||||
# Track whether this appears to be typing (user input) vs other screen changes
|
||||
typing = False
|
||||
diffList = []
|
||||
|
||||
if (self.env['screen']['oldContentText'] != self.env['screen']['newContentText']):
|
||||
# Special case: Initial screen content (going from empty to populated)
|
||||
# This handles first screen load or TTY switch scenarios
|
||||
if self.env['screen']['newContentText'] != '' and self.env['screen']['oldContentText'] == '':
|
||||
if oldScreenText == '' and\
|
||||
newScreenText != '':
|
||||
self.env['screen']['newDelta'] = newScreenText
|
||||
else:
|
||||
# Calculate byte positions for the current cursor's line in the flat text buffer
|
||||
# Formula: (line_number * columns) + line_number accounts for newlines
|
||||
# Each line contributes 'columns' chars + 1 newline char
|
||||
cursorLineStart = self.env['screen']['newCursor']['y'] * self.env['screen']['columns'] + self.env['screen']['newCursor']['y']
|
||||
cursorLineEnd = cursorLineStart + self.env['screen']['columns']
|
||||
|
||||
# TYPING DETECTION ALGORITHM
|
||||
# Determines if this screen change is likely user typing vs other content changes
|
||||
# All conditions must be met for typing detection:
|
||||
if abs(self.env['screen']['oldCursor']['x'] - self.env['screen']['newCursor']['x']) >= 1 and \
|
||||
self.env['screen']['oldCursor']['y'] == self.env['screen']['newCursor']['y'] and \
|
||||
self.env['screen']['newContentText'][:cursorLineStart] == self.env['screen']['oldContentText'][:cursorLineStart] and \
|
||||
self.env['screen']['newContentText'][cursorLineEnd:] == self.env['screen']['oldContentText'][cursorLineEnd:]:
|
||||
# Condition 1: Cursor moved horizontally by at least 1 position (typical of typing)
|
||||
# Condition 2: Cursor stayed on same line (typing doesn't usually change lines)
|
||||
# Condition 3: Content BEFORE cursor line is unchanged (text above typing area)
|
||||
# Condition 4: Content AFTER cursor line is unchanged (text below typing area)
|
||||
# Together: only the current line changed, cursor moved horizontally = likely typing
|
||||
|
||||
# Optimize diff calculation for typing by focusing on a small window around cursor
|
||||
cursorLineStartOffset = cursorLineStart
|
||||
cursorLineEndOffset = cursorLineEnd
|
||||
|
||||
# Limit analysis window to avoid processing entire long lines
|
||||
# +3 provides safety buffer beyond cursor position to catch edge cases
|
||||
if cursorLineEnd > cursorLineStart + self.env['screen']['newCursor']['x'] + 3:
|
||||
cursorLineEndOffset = cursorLineStart + self.env['screen']['newCursor']['x'] + 3
|
||||
|
||||
# Extract just the relevant text sections for character-level diff
|
||||
oldScreenText = self.env['screen']['oldContentText'][cursorLineStartOffset:cursorLineEndOffset]
|
||||
newScreenText = self.env['screen']['newContentText'][cursorLineStartOffset:cursorLineEndOffset]
|
||||
|
||||
# Character-level diff for typing detection (not line-level)
|
||||
diff = self.differ.compare(oldScreenText, newScreenText)
|
||||
diffList = list(diff)
|
||||
typing = True
|
||||
|
||||
# Validate typing assumption by checking if detected changes match cursor movement
|
||||
tempNewDelta = ''.join(x[2:] for x in diffList if x[0] == '+')
|
||||
if tempNewDelta.strip() != '':
|
||||
if tempNewDelta != ''.join(newScreenText[self.env['screen']['oldCursor']['x']:self.env['screen']['newCursor']['x']].rstrip()):
|
||||
# Compare diff result against expected typing pattern
|
||||
# Expected: characters between old and new cursor positions
|
||||
expectedTyping = ''.join(newScreenText[self.env['screen']['oldCursor']['x']:self.env['screen']['newCursor']['x']].rstrip())
|
||||
|
||||
# If diff doesn't match expected typing pattern, treat as general screen change
|
||||
if tempNewDelta != expectedTyping:
|
||||
# Fallback: treat entire current line as new content
|
||||
diffList = ['+ ' + self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']] +'\n']
|
||||
typing = False
|
||||
else:
|
||||
# GENERAL SCREEN CHANGE DETECTION
|
||||
# Not typing - handle as line-by-line content change
|
||||
# This catches: incoming messages, screen updates, application output, etc.
|
||||
diff = self.differ.compare(oldScreenText.split('\n'),\
|
||||
newScreenText.split('\n'))
|
||||
diffList = list(diff)
|
||||
|
||||
# Extract added and removed content from diff results
|
||||
# Output format depends on whether this was detected as typing or general change
|
||||
if not typing:
|
||||
# Line-based changes: join with newlines for proper speech cadence
|
||||
self.env['screen']['newDelta'] = '\n'.join(x[2:] for x in diffList if x[0] == '+')
|
||||
else:
|
||||
# Character-based changes: no newlines for smooth typing echo
|
||||
self.env['screen']['newDelta'] = ''.join(x[2:] for x in diffList if x[0] == '+')
|
||||
|
||||
# Negative delta (removed content) - used for backspace/delete detection
|
||||
self.env['screen']['newNegativeDelta'] = ''.join(x[2:] for x in diffList if x[0] == '-')
|
||||
|
||||
# track highlighted
|
||||
|
Loading…
x
Reference in New Issue
Block a user