Some modifications to progress bar detection, can revert if needed.
This commit is contained in:
parent
91c97dd1dd
commit
8a223282df
@ -60,6 +60,10 @@ class command():
|
|||||||
if not self.env['runtime']['progressMonitoring']:
|
if not self.env['runtime']['progressMonitoring']:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Skip progress detection if current screen looks like a prompt
|
||||||
|
if self.isCurrentLinePrompt():
|
||||||
|
return
|
||||||
|
|
||||||
currentTime = time.time()
|
currentTime = time.time()
|
||||||
|
|
||||||
# Pattern 1: Percentage (50%, 25.5%, etc.)
|
# Pattern 1: Percentage (50%, 25.5%, etc.)
|
||||||
@ -86,11 +90,14 @@ class command():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
|
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
|
||||||
barMatch = re.search(r'\[([#=\-\*]+)([^\]]*)\]', text)
|
# Improved pattern to avoid matching IRC channels like [#channel]
|
||||||
|
barMatch = re.search(r'\[([#=\-\*]+)([\s\.]*)\]', text)
|
||||||
if barMatch:
|
if barMatch:
|
||||||
filled = len(barMatch.group(1))
|
filled = len(barMatch.group(1))
|
||||||
total = filled + len(barMatch.group(2))
|
unfilled = len(barMatch.group(2))
|
||||||
if total > 0:
|
total = filled + unfilled
|
||||||
|
# Require at least 2 progress chars total and unfilled portion must be spaces/dots
|
||||||
|
if total >= 2 and (not barMatch.group(2) or re.match(r'^[\s\.]*$', barMatch.group(2))):
|
||||||
percentage = (filled / total) * 100
|
percentage = (filled / total) * 100
|
||||||
if percentage != self.env['runtime']['lastProgressValue']:
|
if percentage != self.env['runtime']['lastProgressValue']:
|
||||||
self.playProgressTone(percentage)
|
self.playProgressTone(percentage)
|
||||||
@ -116,5 +123,87 @@ class command():
|
|||||||
# Single tone for generic activity
|
# Single tone for generic activity
|
||||||
self.env['runtime']['outputManager'].playFrequence(800, 0.1, interrupt=False)
|
self.env['runtime']['outputManager'].playFrequence(800, 0.1, interrupt=False)
|
||||||
|
|
||||||
|
def isCurrentLinePrompt(self):
|
||||||
|
"""Check if the current line looks like a standalone prompt (not command with progress)"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the current screen content
|
||||||
|
if not self.env['screen']['newContentText']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
lines = self.env['screen']['newContentText'].split('\n')
|
||||||
|
if not lines:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check the last line (most common) and current cursor line for prompt patterns
|
||||||
|
linesToCheck = []
|
||||||
|
|
||||||
|
# Add last line (most common for prompts)
|
||||||
|
if lines:
|
||||||
|
linesToCheck.append(lines[-1])
|
||||||
|
|
||||||
|
# Add current cursor line if different from last line
|
||||||
|
if (self.env['screen']['newCursor']['y'] < len(lines) and
|
||||||
|
self.env['screen']['newCursor']['y'] != len(lines) - 1):
|
||||||
|
linesToCheck.append(lines[self.env['screen']['newCursor']['y']])
|
||||||
|
|
||||||
|
# Standalone prompt patterns (no commands mixed in)
|
||||||
|
standalonePromptPatterns = [
|
||||||
|
r'^\s*\$\s*$', # Just $ (with whitespace)
|
||||||
|
r'^\s*#\s*$', # Just # (with whitespace)
|
||||||
|
r'^\s*>\s*$', # Just > (with whitespace)
|
||||||
|
r'^\[.*\]\s*[\\\$#>]\s*$', # [path]$ without commands
|
||||||
|
r'^[a-zA-Z0-9._-]+[\\\$#>]\s*$', # bash-5.1$ without commands
|
||||||
|
|
||||||
|
# Interactive prompt patterns (these ARE standalone)
|
||||||
|
r'.*\?\s*\[[YyNn]/[YyNn]\]\s*$', # ? [Y/n] or ? [y/N] style
|
||||||
|
r'.*\?\s*\[[Yy]es/[Nn]o\]\s*$', # ? [Yes/No] style
|
||||||
|
r'.*continue\?\s*\[[YyNn]/[YyNn]\].*$', # "continue? [Y/n]" style
|
||||||
|
r'^::.*\?\s*\[[YyNn]/[YyNn]\].*$', # pacman style prompts
|
||||||
|
|
||||||
|
# Authentication prompts (these ARE standalone)
|
||||||
|
r'^\[[Ss]udo\]\s*[Pp]assword\s*for\s+.*:\s*$', # [sudo] password
|
||||||
|
r'^[Pp]assword\s*:\s*$', # Password:
|
||||||
|
r'.*[Pp]assword\s*:\s*$', # general password prompts
|
||||||
|
|
||||||
|
# Continuation prompts (these ARE standalone)
|
||||||
|
r'^[Pp]ress\s+any\s+key\s+to\s+continue.*$', # Press any key
|
||||||
|
r'^[Aa]re\s+you\s+sure\?\s*.*$', # Are you sure?
|
||||||
|
]
|
||||||
|
|
||||||
|
for line in linesToCheck:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if this line contains both a prompt AND other content (like commands)
|
||||||
|
# If so, don't treat it as a standalone prompt
|
||||||
|
hasPromptMarker = bool(re.search(r'.*@.*[\\\$#>]', line) or re.search(r'^\[.*\]\s*[\\\$#>]', line))
|
||||||
|
if hasPromptMarker:
|
||||||
|
# If line has prompt marker but also has significant content after it,
|
||||||
|
# it's a command line, not a standalone prompt
|
||||||
|
promptEnd = max(
|
||||||
|
line.rfind('$'),
|
||||||
|
line.rfind('#'),
|
||||||
|
line.rfind('>'),
|
||||||
|
line.rfind('\\')
|
||||||
|
)
|
||||||
|
if promptEnd >= 0 and promptEnd < len(line) - 5: # More than just whitespace after prompt
|
||||||
|
continue # This is a command line, not a standalone prompt
|
||||||
|
|
||||||
|
for pattern in standalonePromptPatterns:
|
||||||
|
try:
|
||||||
|
if re.search(pattern, line):
|
||||||
|
return True
|
||||||
|
except re.error:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# If anything fails, assume it's not a prompt to be safe
|
||||||
|
return False
|
||||||
|
|
||||||
def setCallback(self, callback):
|
def setCallback(self, callback):
|
||||||
pass
|
pass
|
@ -49,6 +49,10 @@ class command():
|
|||||||
if deltaLength > 200: # Allow longer progress lines like Claude Code's status
|
if deltaLength > 200: # Allow longer progress lines like Claude Code's status
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Check if current line looks like a prompt - progress unlikely during prompts
|
||||||
|
if self.isCurrentLinePrompt():
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def detectProgress(self, text):
|
def detectProgress(self, text):
|
||||||
@ -106,11 +110,14 @@ class command():
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
|
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
|
||||||
barMatch = re.search(r'\[([#=\-\*]+)([^\]]*)\]', text)
|
# Improved pattern to avoid matching IRC channels like [#channel]
|
||||||
|
barMatch = re.search(r'\[([#=\-\*]+)([\s\.]*)\]', text)
|
||||||
if barMatch:
|
if barMatch:
|
||||||
filled = len(barMatch.group(1))
|
filled = len(barMatch.group(1))
|
||||||
total = filled + len(barMatch.group(2))
|
unfilled = len(barMatch.group(2))
|
||||||
if total > 0:
|
total = filled + unfilled
|
||||||
|
# Require at least 2 progress chars total and unfilled portion must be spaces/dots
|
||||||
|
if total >= 2 and (not barMatch.group(2) or re.match(r'^[\s\.]*$', barMatch.group(2))):
|
||||||
percentage = (filled / total) * 100
|
percentage = (filled / total) * 100
|
||||||
if percentage != self.env['commandBuffer']['lastProgressValue']:
|
if percentage != self.env['commandBuffer']['lastProgressValue']:
|
||||||
self.playProgressTone(percentage)
|
self.playProgressTone(percentage)
|
||||||
@ -153,6 +160,87 @@ class command():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.env['runtime']['debug'].writeDebugOut("Sox tone error: " + str(e), debug.debugLevel.ERROR)
|
self.env['runtime']['debug'].writeDebugOut("Sox tone error: " + str(e), debug.debugLevel.ERROR)
|
||||||
|
|
||||||
|
def isCurrentLinePrompt(self):
|
||||||
|
"""Check if the current line looks like a standalone prompt (not command with progress)"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the current screen content
|
||||||
|
if not self.env['screen']['newContentText']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
lines = self.env['screen']['newContentText'].split('\n')
|
||||||
|
if not lines:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check the last line (most common) and current cursor line for prompt patterns
|
||||||
|
linesToCheck = []
|
||||||
|
|
||||||
|
# Add last line (most common for prompts)
|
||||||
|
if lines:
|
||||||
|
linesToCheck.append(lines[-1])
|
||||||
|
|
||||||
|
# Add current cursor line if different from last line
|
||||||
|
if (self.env['screen']['newCursor']['y'] < len(lines) and
|
||||||
|
self.env['screen']['newCursor']['y'] != len(lines) - 1):
|
||||||
|
linesToCheck.append(lines[self.env['screen']['newCursor']['y']])
|
||||||
|
|
||||||
|
# Standalone prompt patterns (no commands mixed in)
|
||||||
|
standalonePromptPatterns = [
|
||||||
|
r'^\s*\$\s*$', # Just $ (with whitespace)
|
||||||
|
r'^\s*#\s*$', # Just # (with whitespace)
|
||||||
|
r'^\s*>\s*$', # Just > (with whitespace)
|
||||||
|
r'^\[.*\]\s*[\\\$#>]\s*$', # [path]$ without commands
|
||||||
|
r'^[a-zA-Z0-9._-]+[\\\$#>]\s*$', # bash-5.1$ without commands
|
||||||
|
|
||||||
|
# Interactive prompt patterns (these ARE standalone)
|
||||||
|
r'.*\?\s*\[[YyNn]/[YyNn]\]\s*$', # ? [Y/n] or ? [y/N] style
|
||||||
|
r'.*\?\s*\[[Yy]es/[Nn]o\]\s*$', # ? [Yes/No] style
|
||||||
|
r'.*continue\?\s*\[[YyNn]/[YyNn]\].*$', # "continue? [Y/n]" style
|
||||||
|
r'^::.*\?\s*\[[YyNn]/[YyNn]\].*$', # pacman style prompts
|
||||||
|
|
||||||
|
# Authentication prompts (these ARE standalone)
|
||||||
|
r'^\[[Ss]udo\]\s*[Pp]assword\s*for\s+.*:\s*$', # [sudo] password
|
||||||
|
r'^[Pp]assword\s*:\s*$', # Password:
|
||||||
|
r'.*[Pp]assword\s*:\s*$', # general password prompts
|
||||||
|
|
||||||
|
# Continuation prompts (these ARE standalone)
|
||||||
|
r'^[Pp]ress\s+any\s+key\s+to\s+continue.*$', # Press any key
|
||||||
|
r'^[Aa]re\s+you\s+sure\?\s*.*$', # Are you sure?
|
||||||
|
]
|
||||||
|
|
||||||
|
for line in linesToCheck:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if this line contains both a prompt AND other content (like commands)
|
||||||
|
# If so, don't treat it as a standalone prompt
|
||||||
|
hasPromptMarker = bool(re.search(r'.*@.*[\\\$#>]', line) or re.search(r'^\[.*\]\s*[\\\$#>]', line))
|
||||||
|
if hasPromptMarker:
|
||||||
|
# If line has prompt marker but also has significant content after it,
|
||||||
|
# it's a command line, not a standalone prompt
|
||||||
|
promptEnd = max(
|
||||||
|
line.rfind('$'),
|
||||||
|
line.rfind('#'),
|
||||||
|
line.rfind('>'),
|
||||||
|
line.rfind('\\')
|
||||||
|
)
|
||||||
|
if promptEnd >= 0 and promptEnd < len(line) - 5: # More than just whitespace after prompt
|
||||||
|
continue # This is a command line, not a standalone prompt
|
||||||
|
|
||||||
|
for pattern in standalonePromptPatterns:
|
||||||
|
try:
|
||||||
|
if re.search(pattern, line):
|
||||||
|
return True
|
||||||
|
except re.error:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# If anything fails, assume it's not a prompt to be safe
|
||||||
|
return False
|
||||||
|
|
||||||
def setCallback(self, callback):
|
def setCallback(self, callback):
|
||||||
pass
|
pass
|
@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributers.
|
# By Chrys, Storm Dragon, and contributers.
|
||||||
|
|
||||||
version = "2025.06.08"
|
version = "2025.06.09"
|
||||||
codeName = "testing"
|
codeName = "testing"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user