improved documentation for the diff generation.

This commit is contained in:
Storm Dragon 2025-06-09 16:53:05 -04:00
parent 6998706934
commit b0ac6e1409
2 changed files with 112 additions and 12 deletions

View File

@ -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

View File

@ -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