Expirmental improvements to highlight tracking.
This commit is contained in:
@@ -24,6 +24,27 @@ class command:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# Check if we're in table mode first
|
||||||
|
is_table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
|
if is_table_mode:
|
||||||
|
table_info = self.env["runtime"]["TableManager"].move_to_first_cell()
|
||||||
|
if table_info:
|
||||||
|
output_text = (
|
||||||
|
f"{table_info['cell_content']} {table_info['column_header']}"
|
||||||
|
)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
output_text, interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("first cell"), interrupt=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("no table data"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Regular line begin navigation (when not in table mode)
|
||||||
cursor_pos = self.env["runtime"][
|
cursor_pos = self.env["runtime"][
|
||||||
"CursorManager"
|
"CursorManager"
|
||||||
].get_review_or_text_cursor()
|
].get_review_or_text_cursor()
|
||||||
|
|||||||
@@ -24,6 +24,27 @@ class command:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# Check if we're in table mode first
|
||||||
|
is_table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
|
if is_table_mode:
|
||||||
|
table_info = self.env["runtime"]["TableManager"].move_to_last_cell()
|
||||||
|
if table_info:
|
||||||
|
output_text = (
|
||||||
|
f"{table_info['cell_content']} {table_info['column_header']}"
|
||||||
|
)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
output_text, interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("last cell"), interrupt=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("no table data"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Regular line end navigation (when not in table mode)
|
||||||
cursor_pos = self.env["runtime"][
|
cursor_pos = self.env["runtime"][
|
||||||
"CursorManager"
|
"CursorManager"
|
||||||
].get_review_or_text_cursor()
|
].get_review_or_text_cursor()
|
||||||
|
|||||||
@@ -23,6 +23,29 @@ class command:
|
|||||||
return _("Move Review to the first character on the line")
|
return _("Move Review to the first character on the line")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# Check if we're in table mode first
|
||||||
|
is_table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
|
if is_table_mode:
|
||||||
|
table_info = self.env["runtime"]["TableManager"].move_to_first_char_in_cell()
|
||||||
|
if table_info:
|
||||||
|
char_utils.present_char_for_review(
|
||||||
|
self.env,
|
||||||
|
table_info['character'],
|
||||||
|
interrupt=True,
|
||||||
|
announce_capital=True,
|
||||||
|
flush=False,
|
||||||
|
)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("first character in cell {0}").format(table_info['column_header']),
|
||||||
|
interrupt=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("no table data"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Regular line first character navigation (when not in table mode)
|
||||||
cursor_pos = self.env["runtime"][
|
cursor_pos = self.env["runtime"][
|
||||||
"CursorManager"
|
"CursorManager"
|
||||||
].get_review_or_text_cursor()
|
].get_review_or_text_cursor()
|
||||||
|
|||||||
@@ -22,6 +22,29 @@ class command:
|
|||||||
return _("Move Review to the last character on the line")
|
return _("Move Review to the last character on the line")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# Check if we're in table mode first
|
||||||
|
is_table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
|
if is_table_mode:
|
||||||
|
table_info = self.env["runtime"]["TableManager"].move_to_last_char_in_cell()
|
||||||
|
if table_info:
|
||||||
|
char_utils.present_char_for_review(
|
||||||
|
self.env,
|
||||||
|
table_info['character'],
|
||||||
|
interrupt=True,
|
||||||
|
announce_capital=True,
|
||||||
|
flush=False,
|
||||||
|
)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("last character in cell {0}").format(table_info['column_header']),
|
||||||
|
interrupt=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("no table data"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Regular line last character navigation (when not in table mode)
|
||||||
cursor_pos = self.env["runtime"][
|
cursor_pos = self.env["runtime"][
|
||||||
"CursorManager"
|
"CursorManager"
|
||||||
].get_review_or_text_cursor()
|
].get_review_or_text_cursor()
|
||||||
|
|||||||
@@ -322,6 +322,9 @@ class AttributeManager:
|
|||||||
This is crucial for screen readers to announce when text becomes highlighted,
|
This is crucial for screen readers to announce when text becomes highlighted,
|
||||||
selected, or changes visual emphasis (bold, reverse video, color changes, etc.)
|
selected, or changes visual emphasis (bold, reverse video, color changes, etc.)
|
||||||
|
|
||||||
|
Enhanced version includes bracket pattern detection for better context in applications
|
||||||
|
like ninjam that use patterns like [X]mute or [ ]mute.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (highlighted_text, cursor_position)
|
tuple: (highlighted_text, cursor_position)
|
||||||
- highlighted_text: string of characters that gained highlighting
|
- highlighted_text: string of characters that gained highlighting
|
||||||
@@ -352,6 +355,9 @@ class AttributeManager:
|
|||||||
if len(text_lines) != len(self.currAttributes):
|
if len(text_lines) != len(self.currAttributes):
|
||||||
return result, curr_cursor
|
return result, curr_cursor
|
||||||
|
|
||||||
|
# Track highlighted positions for context analysis
|
||||||
|
highlighted_positions = []
|
||||||
|
|
||||||
# Compare attributes line by line, character by character
|
# Compare attributes line by line, character by character
|
||||||
for line in range(len(self.prevAttributes)):
|
for line in range(len(self.prevAttributes)):
|
||||||
if self.prevAttributes[line] != self.currAttributes[line]:
|
if self.prevAttributes[line] != self.currAttributes[line]:
|
||||||
@@ -373,13 +379,316 @@ class AttributeManager:
|
|||||||
# for navigation
|
# for navigation
|
||||||
if not curr_cursor:
|
if not curr_cursor:
|
||||||
curr_cursor = {"x": column, "y": line}
|
curr_cursor = {"x": column, "y": line}
|
||||||
|
# Store position for context analysis
|
||||||
|
highlighted_positions.append((line, column))
|
||||||
# Accumulate highlighted characters
|
# Accumulate highlighted characters
|
||||||
result += text_lines[line][column]
|
result += text_lines[line][column]
|
||||||
# Add space between lines of highlighted text for speech
|
# Add space between lines of highlighted text for speech
|
||||||
# clarity
|
# clarity
|
||||||
result += " "
|
result += " "
|
||||||
|
|
||||||
|
# Enhanced bracket pattern detection for better context
|
||||||
|
if highlighted_positions:
|
||||||
|
try:
|
||||||
|
enhanced_result = self._detect_bracket_context(text_lines, highlighted_positions, result)
|
||||||
|
if enhanced_result and enhanced_result != result:
|
||||||
|
# Debug logging for bracket detection
|
||||||
|
try:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"AttributeManager bracket detection: Original='{result}' Enhanced='{enhanced_result}'",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass # Don't let debug logging break functionality
|
||||||
|
result = enhanced_result
|
||||||
|
except Exception as e:
|
||||||
|
# If bracket detection fails, fall back to original result
|
||||||
|
try:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"AttributeManager bracket detection error: {e}",
|
||||||
|
debug.DebugLevel.ERROR
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass # Don't let debug logging break functionality
|
||||||
|
|
||||||
return result, curr_cursor
|
return result, curr_cursor
|
||||||
|
|
||||||
|
def _detect_bracket_context(self, text_lines, highlighted_positions, original_result):
|
||||||
|
"""
|
||||||
|
Analyzes highlighted positions to detect bracket patterns and provide better context.
|
||||||
|
|
||||||
|
This method specifically looks for patterns like [X]mute, [ ]mute, [X]xmit, etc.
|
||||||
|
that are common in applications like ninjam where the bracket content indicates
|
||||||
|
state but the meaningful context is the surrounding text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text_lines: List of text lines from the screen
|
||||||
|
highlighted_positions: List of (line, column) tuples of highlighted characters
|
||||||
|
original_result: The original highlighted text result
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Enhanced result with context, or None if no bracket pattern detected
|
||||||
|
"""
|
||||||
|
if not highlighted_positions:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Group positions by line for easier analysis
|
||||||
|
positions_by_line = {}
|
||||||
|
for line, column in highlighted_positions:
|
||||||
|
if line not in positions_by_line:
|
||||||
|
positions_by_line[line] = []
|
||||||
|
positions_by_line[line].append(column)
|
||||||
|
|
||||||
|
enhanced_results = []
|
||||||
|
|
||||||
|
for line_num, columns in positions_by_line.items():
|
||||||
|
if line_num >= len(text_lines):
|
||||||
|
continue
|
||||||
|
|
||||||
|
line_text = text_lines[line_num]
|
||||||
|
columns.sort() # Process columns in order
|
||||||
|
|
||||||
|
# Look for bracket patterns in this line
|
||||||
|
bracket_context = self._analyze_bracket_pattern(line_text, columns)
|
||||||
|
if bracket_context:
|
||||||
|
enhanced_results.append(bracket_context)
|
||||||
|
|
||||||
|
# If we found enhanced context, return it; otherwise return None to use original
|
||||||
|
if enhanced_results:
|
||||||
|
# Remove duplicates while preserving order
|
||||||
|
unique_results = []
|
||||||
|
seen = set()
|
||||||
|
for result in enhanced_results:
|
||||||
|
if result not in seen:
|
||||||
|
unique_results.append(result)
|
||||||
|
seen.add(result)
|
||||||
|
return " ".join(unique_results)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _analyze_bracket_pattern(self, line_text, highlighted_columns):
|
||||||
|
"""
|
||||||
|
Analyzes a single line for bracket patterns around highlighted positions.
|
||||||
|
|
||||||
|
Looks for patterns like:
|
||||||
|
- master: [X]mute -> "master mute on"
|
||||||
|
- metronome: [ ]mute -> "metronome mute off"
|
||||||
|
- [Left] [X]xmit -> "Left xmit on"
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line_text: The text of the line
|
||||||
|
highlighted_columns: List of column positions that are highlighted
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Context-aware description, or None if no pattern found
|
||||||
|
"""
|
||||||
|
if not line_text or not highlighted_columns:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Look for bracket patterns around highlighted positions
|
||||||
|
# Process columns in order and only return the first meaningful match
|
||||||
|
for col in highlighted_columns:
|
||||||
|
bracket_info = self._find_bracket_at_position(line_text, col)
|
||||||
|
if bracket_info:
|
||||||
|
bracket_start, bracket_end, bracket_content = bracket_info
|
||||||
|
|
||||||
|
# Get context before and after the bracket
|
||||||
|
context_before = self._get_context_before(line_text, bracket_start)
|
||||||
|
context_after = self._get_context_after(line_text, bracket_end)
|
||||||
|
|
||||||
|
# Build meaningful description
|
||||||
|
description = self._build_bracket_description(
|
||||||
|
context_before, bracket_content, context_after
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only return if we have meaningful context (not just state)
|
||||||
|
if description and (context_before or context_after):
|
||||||
|
return description
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _find_bracket_at_position(self, line_text, position):
|
||||||
|
"""
|
||||||
|
Determines if a position is within a bracket pattern like [X] or [ ].
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line_text: The text line
|
||||||
|
position: Column position to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (bracket_start, bracket_end, bracket_content) or None
|
||||||
|
"""
|
||||||
|
if position >= len(line_text):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Look for opening bracket before or at position
|
||||||
|
bracket_start = None
|
||||||
|
for i in range(max(0, position - 2), min(len(line_text), position + 3)):
|
||||||
|
if line_text[i] == '[':
|
||||||
|
bracket_start = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if bracket_start is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Look for closing bracket after bracket_start
|
||||||
|
bracket_end = None
|
||||||
|
for i in range(bracket_start + 1, min(len(line_text), bracket_start + 5)):
|
||||||
|
if line_text[i] == ']':
|
||||||
|
bracket_end = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if bracket_end is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check if our position is within the bracket
|
||||||
|
if bracket_start <= position <= bracket_end:
|
||||||
|
bracket_content = line_text[bracket_start + 1:bracket_end]
|
||||||
|
|
||||||
|
# Filter out brackets that are likely not controls
|
||||||
|
# Skip brackets that contain complex content like "[-10.5dB center]"
|
||||||
|
if len(bracket_content) > 3 and ('dB' in bracket_content or 'Hz' in bracket_content):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return bracket_start, bracket_end, bracket_content
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_context_before(self, line_text, bracket_start):
|
||||||
|
"""
|
||||||
|
Gets meaningful context words before a bracket.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line_text: The text line
|
||||||
|
bracket_start: Position of opening bracket
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Context words before bracket, or empty string
|
||||||
|
"""
|
||||||
|
if bracket_start <= 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Get text before bracket
|
||||||
|
before_text = line_text[:bracket_start].rstrip()
|
||||||
|
|
||||||
|
# Special handling for common ninjam patterns
|
||||||
|
# Look for patterns like "master: " or "metronome: " or bracketed labels
|
||||||
|
|
||||||
|
# Check if we have a colon-separated label immediately before
|
||||||
|
if ':' in before_text:
|
||||||
|
# Get the last colon-separated part
|
||||||
|
parts = before_text.split('|') # Split by pipe first
|
||||||
|
last_part = parts[-1].strip()
|
||||||
|
|
||||||
|
if ':' in last_part:
|
||||||
|
# Extract the label before the colon
|
||||||
|
label_parts = last_part.split(':')
|
||||||
|
if len(label_parts) >= 2:
|
||||||
|
return label_parts[-2].strip()
|
||||||
|
|
||||||
|
# Look for bracketed content that might be a label
|
||||||
|
# Pattern: [Something] [X]target -> "Something"
|
||||||
|
bracket_matches = []
|
||||||
|
i = 0
|
||||||
|
while i < len(before_text):
|
||||||
|
if before_text[i] == '[':
|
||||||
|
start = i
|
||||||
|
i += 1
|
||||||
|
while i < len(before_text) and before_text[i] != ']':
|
||||||
|
i += 1
|
||||||
|
if i < len(before_text): # Found closing bracket
|
||||||
|
content = before_text[start+1:i]
|
||||||
|
if content.strip():
|
||||||
|
bracket_matches.append(content.strip())
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# If we found bracketed content, use the last one
|
||||||
|
if bracket_matches:
|
||||||
|
return bracket_matches[-1]
|
||||||
|
|
||||||
|
# Fall back to removing bracketed content and getting words
|
||||||
|
cleaned_text = ""
|
||||||
|
bracket_level = 0
|
||||||
|
for char in before_text:
|
||||||
|
if char == '[':
|
||||||
|
bracket_level += 1
|
||||||
|
elif char == ']':
|
||||||
|
bracket_level -= 1
|
||||||
|
elif bracket_level == 0:
|
||||||
|
cleaned_text += char
|
||||||
|
|
||||||
|
# Clean up separators and get meaningful words
|
||||||
|
words = []
|
||||||
|
for word in cleaned_text.replace(':', '').replace('|', '').strip().split():
|
||||||
|
if word and not word.startswith('[') and not word.endswith(']'):
|
||||||
|
words.append(word)
|
||||||
|
|
||||||
|
# Return last word as context
|
||||||
|
if words:
|
||||||
|
return words[-1]
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _get_context_after(self, line_text, bracket_end):
|
||||||
|
"""
|
||||||
|
Gets meaningful context words after a bracket.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line_text: The text line
|
||||||
|
bracket_end: Position of closing bracket
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Context words after bracket, or empty string
|
||||||
|
"""
|
||||||
|
if bracket_end >= len(line_text) - 1:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Get text after bracket and find first meaningful word
|
||||||
|
after_text = line_text[bracket_end + 1:].lstrip()
|
||||||
|
|
||||||
|
# Get first word, removing common separators
|
||||||
|
words = after_text.replace(':', '').replace('|', '').strip().split()
|
||||||
|
|
||||||
|
if words:
|
||||||
|
return words[0]
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _build_bracket_description(self, context_before, bracket_content, context_after):
|
||||||
|
"""
|
||||||
|
Builds a human-readable description from bracket context.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
context_before: Words before the bracket
|
||||||
|
bracket_content: Content inside brackets (X, space, etc.)
|
||||||
|
context_after: Words after the bracket
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Human-readable description
|
||||||
|
"""
|
||||||
|
# Clean up bracket content
|
||||||
|
bracket_content = bracket_content.strip()
|
||||||
|
|
||||||
|
# Determine state based on bracket content
|
||||||
|
if bracket_content == 'X':
|
||||||
|
state = "on"
|
||||||
|
elif bracket_content == '' or bracket_content == ' ':
|
||||||
|
state = "off"
|
||||||
|
else:
|
||||||
|
state = bracket_content # For other patterns
|
||||||
|
|
||||||
|
# Build description prioritizing context_before, then context_after
|
||||||
|
# Add spaces between components for better speech clarity
|
||||||
|
components = []
|
||||||
|
|
||||||
|
if context_before:
|
||||||
|
components.append(context_before)
|
||||||
|
if context_after:
|
||||||
|
components.append(context_after)
|
||||||
|
components.append(state)
|
||||||
|
|
||||||
|
# Join with spaces for better speech flow
|
||||||
|
return " ".join(components)
|
||||||
|
|
||||||
def is_useful_for_tracking(
|
def is_useful_for_tracking(
|
||||||
self,
|
self,
|
||||||
line,
|
line,
|
||||||
|
|||||||
@@ -382,6 +382,151 @@ class TableManager:
|
|||||||
# Check if cursor is within the column bounds
|
# Check if cursor is within the column bounds
|
||||||
return column_start <= cursor_x < column_end
|
return column_start <= cursor_x < column_end
|
||||||
|
|
||||||
|
def move_to_first_cell(self):
|
||||||
|
"""Move to first cell in current row"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Set current column to first column
|
||||||
|
self.currentColumn = 0
|
||||||
|
|
||||||
|
# Return info for the first column
|
||||||
|
return self.get_table_cell_info_by_indices(cursor_pos["y"], 0)
|
||||||
|
|
||||||
|
def move_to_last_cell(self):
|
||||||
|
"""Move to last cell in current row"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Set current column to last column
|
||||||
|
self.currentColumn = len(columns) - 1
|
||||||
|
|
||||||
|
# Return info for the last column
|
||||||
|
return self.get_table_cell_info_by_indices(cursor_pos["y"], self.currentColumn)
|
||||||
|
|
||||||
|
def move_to_first_char_in_cell(self):
|
||||||
|
"""Move to first character in current cell"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns or self.currentColumn < 0 or self.currentColumn >= len(columns):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get column start position
|
||||||
|
column_start = self.get_column_start_position(line_text, self.currentColumn)
|
||||||
|
|
||||||
|
# Find first non-space character in the column
|
||||||
|
column_text = columns[self.currentColumn]
|
||||||
|
first_char_offset = len(column_text) - len(column_text.lstrip())
|
||||||
|
|
||||||
|
# Set cursor position to first character in cell
|
||||||
|
new_x = column_start + first_char_offset
|
||||||
|
self.env["runtime"]["CursorManager"].set_review_cursor_position(new_x, cursor_pos["y"])
|
||||||
|
|
||||||
|
# Get the character at the new position
|
||||||
|
from fenrirscreenreader.utils import char_utils
|
||||||
|
(
|
||||||
|
self.env["screen"]["newCursorReview"]["x"],
|
||||||
|
self.env["screen"]["newCursorReview"]["y"],
|
||||||
|
curr_char,
|
||||||
|
) = char_utils.get_current_char(
|
||||||
|
new_x,
|
||||||
|
cursor_pos["y"],
|
||||||
|
self.env["screen"]["new_content_text"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'cell_content': column_text.strip(),
|
||||||
|
'column_header': self.get_column_header(self.currentColumn),
|
||||||
|
'character': curr_char,
|
||||||
|
'position': 'first'
|
||||||
|
}
|
||||||
|
|
||||||
|
def move_to_last_char_in_cell(self):
|
||||||
|
"""Move to last character in current cell"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns or self.currentColumn < 0 or self.currentColumn >= len(columns):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get column start position
|
||||||
|
column_start = self.get_column_start_position(line_text, self.currentColumn)
|
||||||
|
column_text = columns[self.currentColumn]
|
||||||
|
|
||||||
|
# Find last non-space character in the column
|
||||||
|
trimmed_text = column_text.rstrip()
|
||||||
|
if not trimmed_text:
|
||||||
|
# If empty cell, go to column start
|
||||||
|
new_x = column_start
|
||||||
|
else:
|
||||||
|
# Find the position of the last character
|
||||||
|
new_x = column_start + len(trimmed_text) - 1
|
||||||
|
|
||||||
|
# Set cursor position to last character in cell
|
||||||
|
self.env["runtime"]["CursorManager"].set_review_cursor_position(new_x, cursor_pos["y"])
|
||||||
|
|
||||||
|
# Get the character at the new position
|
||||||
|
from fenrirscreenreader.utils import char_utils
|
||||||
|
(
|
||||||
|
self.env["screen"]["newCursorReview"]["x"],
|
||||||
|
self.env["screen"]["newCursorReview"]["y"],
|
||||||
|
curr_char,
|
||||||
|
) = char_utils.get_current_char(
|
||||||
|
new_x,
|
||||||
|
cursor_pos["y"],
|
||||||
|
self.env["screen"]["new_content_text"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'cell_content': column_text.strip(),
|
||||||
|
'column_header': self.get_column_header(self.currentColumn),
|
||||||
|
'character': curr_char,
|
||||||
|
'position': 'last'
|
||||||
|
}
|
||||||
|
|
||||||
def reset_table_mode(self):
|
def reset_table_mode(self):
|
||||||
self.set_head_line()
|
self.set_head_line()
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2025.07.08"
|
version = "2025.07.09"
|
||||||
codeName = "testing"
|
codeName = "testing"
|
||||||
code_name = "testing"
|
code_name = "testing"
|
||||||
|
|||||||
Reference in New Issue
Block a user