Most of the pep8 changes finished. Be careful, things may be horribly broken.
This commit is contained in:
@ -19,7 +19,7 @@ class command():
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
def get_description(self):
|
||||
return 'Detects progress patterns for progress bar monitoring'
|
||||
|
||||
def run(self):
|
||||
@ -28,65 +28,65 @@ class command():
|
||||
if 'progressMonitoring' in self.env['commandBuffer'] and self.env['commandBuffer']['progressMonitoring']:
|
||||
# Check if current line is a prompt - if so, reset progress
|
||||
# state
|
||||
if self.isCurrentLinePrompt():
|
||||
self.resetProgressState()
|
||||
# Only check new incoming text (newDelta), but filter out
|
||||
if self.is_current_line_prompt():
|
||||
self.reset_progress_state()
|
||||
# Only check new incoming text (new_delta), but filter out
|
||||
# screen changes
|
||||
elif self.env['screen']['newDelta'] and self.isRealProgressUpdate():
|
||||
self.detectProgress(self.env['screen']['newDelta'])
|
||||
elif self.env['screen']['new_delta'] and self.is_real_progress_update():
|
||||
self.detect_progress(self.env['screen']['new_delta'])
|
||||
except Exception as e:
|
||||
# Silently ignore errors to avoid disrupting normal operation
|
||||
pass
|
||||
|
||||
def isRealProgressUpdate(self):
|
||||
def is_real_progress_update(self):
|
||||
"""Check if this is a real progress update vs screen change/window switch"""
|
||||
# If the screen/application changed, it's not a progress update
|
||||
if self.env['runtime']['screenManager'].isScreenChange():
|
||||
if self.env['runtime']['ScreenManager'].is_screen_change():
|
||||
return False
|
||||
|
||||
# If there was a large cursor movement, it's likely navigation, not
|
||||
# progress
|
||||
if self.env['runtime']['cursorManager'].isCursorVerticalMove():
|
||||
xMove = abs(
|
||||
self.env['screen']['newCursor']['x'] -
|
||||
self.env['screen']['oldCursor']['x'])
|
||||
yMove = abs(
|
||||
self.env['screen']['newCursor']['y'] -
|
||||
self.env['screen']['oldCursor']['y'])
|
||||
if self.env['runtime']['CursorManager'].is_cursor_vertical_move():
|
||||
x_move = abs(
|
||||
self.env['screen']['new_cursor']['x'] -
|
||||
self.env['screen']['old_cursor']['x'])
|
||||
y_move = abs(
|
||||
self.env['screen']['new_cursor']['y'] -
|
||||
self.env['screen']['old_cursor']['y'])
|
||||
# Large movements suggest navigation, not progress output
|
||||
if yMove > 2 or xMove > 20:
|
||||
if y_move > 2 or x_move > 20:
|
||||
return False
|
||||
|
||||
# Check if delta is too large (screen change) vs small incremental
|
||||
# updates
|
||||
deltaLength = len(self.env['screen']['newDelta'])
|
||||
if deltaLength > 200: # Allow longer progress lines like Claude Code's status
|
||||
delta_length = len(self.env['screen']['new_delta'])
|
||||
if delta_length > 200: # Allow longer progress lines like Claude Code's status
|
||||
return False
|
||||
|
||||
# Check if current line looks like a prompt - progress unlikely during
|
||||
# prompts
|
||||
if self.isCurrentLinePrompt():
|
||||
if self.is_current_line_prompt():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def resetProgressState(self):
|
||||
def reset_progress_state(self):
|
||||
"""Reset progress state when a prompt is detected, allowing new progress operations to start fresh"""
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
self.env['runtime']['DebugManager'].write_debug_out(
|
||||
"Resetting progress state due to prompt detection",
|
||||
debug.debugLevel.INFO)
|
||||
debug.DebugLevel.INFO)
|
||||
self.env['commandBuffer']['lastProgressValue'] = -1
|
||||
self.env['commandBuffer']['lastProgressTime'] = 0
|
||||
|
||||
def detectProgress(self, text):
|
||||
def detect_progress(self, text):
|
||||
import re
|
||||
import time
|
||||
|
||||
currentTime = time.time()
|
||||
current_time = time.time()
|
||||
|
||||
# Debug: Print what we're checking
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
"Progress detector checking: '" + text + "'", debug.debugLevel.INFO)
|
||||
self.env['runtime']['DebugManager'].write_debug_out(
|
||||
"Progress detector checking: '" + text + "'", debug.DebugLevel.INFO)
|
||||
|
||||
# Note: Auto-disable on 100% completion removed to respect user
|
||||
# settings
|
||||
@ -94,9 +94,9 @@ class command():
|
||||
# Pattern 1: Percentage (50%, 25.5%, etc.)
|
||||
# Filter out common non-progress percentages (weather, system stats,
|
||||
# etc.)
|
||||
percentMatch = re.search(r'(\d+(?:\.\d+)?)\s*%', text)
|
||||
if percentMatch:
|
||||
percentage = float(percentMatch.group(1))
|
||||
percent_match = re.search(r'(\d+(?:\.\d+)?)\s*%', text)
|
||||
if percent_match:
|
||||
percentage = float(percent_match.group(1))
|
||||
# Only trigger on realistic progress percentages (0-100%)
|
||||
if 0 <= percentage <= 100:
|
||||
# Filter out weather/system stats that contain percentages
|
||||
@ -104,96 +104,96 @@ class command():
|
||||
r'\b(?:humidity|cpu|memory|disk|usage|temp|weather|forecast)\b',
|
||||
text,
|
||||
re.IGNORECASE):
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
"Found percentage: " + str(percentage), debug.debugLevel.INFO)
|
||||
self.env['runtime']['DebugManager'].write_debug_out(
|
||||
"found percentage: " + str(percentage), debug.DebugLevel.INFO)
|
||||
if percentage != self.env['commandBuffer']['lastProgressValue']:
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
"Playing tone for: " + str(percentage), debug.debugLevel.INFO)
|
||||
self.playProgressTone(percentage)
|
||||
self.env['runtime']['DebugManager'].write_debug_out(
|
||||
"Playing tone for: " + str(percentage), debug.DebugLevel.INFO)
|
||||
self.play_progress_tone(percentage)
|
||||
self.env['commandBuffer']['lastProgressValue'] = percentage
|
||||
self.env['commandBuffer']['lastProgressTime'] = currentTime
|
||||
self.env['commandBuffer']['lastProgressTime'] = current_time
|
||||
return
|
||||
|
||||
# Pattern 1b: Time/token activity (not percentage-based, so use single
|
||||
# beep)
|
||||
timeMatch = re.search(r'(\d+)s\s', text)
|
||||
tokenMatch = re.search(r'(\d+)\s+tokens', text)
|
||||
time_match = re.search(r'(\d+)s\s', text)
|
||||
token_match = re.search(r'(\d+)\s+tokens', text)
|
||||
# Pattern 1c: dd command output (bytes copied with transfer rate)
|
||||
ddMatch = re.search(r'\d+\s+bytes.*copied.*\d+\s+s.*[kMGT]?B/s', text)
|
||||
dd_match = re.search(r'\d+\s+bytes.*copied.*\d+\s+s.*[kMGT]?B/s', text)
|
||||
# Pattern 1d: Curl-style transfer data (bytes, speed indicators)
|
||||
curlMatch = re.search(
|
||||
curl_match = re.search(
|
||||
r'(\d+\s+\d+\s+\d+\s+\d+.*?(?:k|M|G)?.*?--:--:--|Speed)', text)
|
||||
|
||||
if timeMatch or tokenMatch or ddMatch or curlMatch:
|
||||
if time_match or token_match or dd_match or curl_match:
|
||||
# For non-percentage progress, use a single activity beep every 2
|
||||
# seconds
|
||||
if currentTime - \
|
||||
if current_time - \
|
||||
self.env['commandBuffer']['lastProgressTime'] >= 2.0:
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
"Playing activity beep for transfer progress", debug.debugLevel.INFO)
|
||||
self.playActivityBeep()
|
||||
self.env['commandBuffer']['lastProgressTime'] = currentTime
|
||||
self.env['runtime']['DebugManager'].write_debug_out(
|
||||
"Playing activity beep for transfer progress", debug.DebugLevel.INFO)
|
||||
self.play_activity_beep()
|
||||
self.env['commandBuffer']['lastProgressTime'] = current_time
|
||||
return
|
||||
|
||||
# Pattern 2: Fraction (15/100, 3 of 10, etc.)
|
||||
fractionMatch = re.search(r'(\d+)\s*(?:of|/)\s*(\d+)', text)
|
||||
if fractionMatch:
|
||||
current = int(fractionMatch.group(1))
|
||||
total = int(fractionMatch.group(2))
|
||||
fraction_match = re.search(r'(\d+)\s*(?:of|/)\s*(\d+)', text)
|
||||
if fraction_match:
|
||||
current = int(fraction_match.group(1))
|
||||
total = int(fraction_match.group(2))
|
||||
if total > 0:
|
||||
percentage = (current / total) * 100
|
||||
if percentage != self.env['commandBuffer']['lastProgressValue']:
|
||||
self.playProgressTone(percentage)
|
||||
self.play_progress_tone(percentage)
|
||||
self.env['commandBuffer']['lastProgressValue'] = percentage
|
||||
self.env['commandBuffer']['lastProgressTime'] = currentTime
|
||||
self.env['commandBuffer']['lastProgressTime'] = current_time
|
||||
return
|
||||
|
||||
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
|
||||
# Improved pattern to avoid matching IRC channels like [#channel]
|
||||
barMatch = re.search(r'\[([#=\-\*]+)([\s\.]*)\]', text)
|
||||
if barMatch:
|
||||
filled = len(barMatch.group(1))
|
||||
unfilled = len(barMatch.group(2))
|
||||
bar_match = re.search(r'\[([#=\-\*]+)([\s\.]*)\]', text)
|
||||
if bar_match:
|
||||
filled = len(bar_match.group(1))
|
||||
unfilled = len(bar_match.group(2))
|
||||
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(
|
||||
not bar_match.group(2) or re.match(
|
||||
r'^[\s\.]*$',
|
||||
barMatch.group(2))):
|
||||
bar_match.group(2))):
|
||||
percentage = (filled / total) * 100
|
||||
if percentage != self.env['commandBuffer']['lastProgressValue']:
|
||||
self.playProgressTone(percentage)
|
||||
self.play_progress_tone(percentage)
|
||||
self.env['commandBuffer']['lastProgressValue'] = percentage
|
||||
self.env['commandBuffer']['lastProgressTime'] = currentTime
|
||||
self.env['commandBuffer']['lastProgressTime'] = current_time
|
||||
return
|
||||
|
||||
# Pattern 4: Generic activity indicators (Loading..., Working..., etc.)
|
||||
activityPattern = re.search(
|
||||
activity_pattern = re.search(
|
||||
r'(loading|processing|working|installing|downloading|compiling|building).*\.{2,}',
|
||||
text,
|
||||
re.IGNORECASE)
|
||||
if activityPattern:
|
||||
if activity_pattern:
|
||||
# Play a steady beep every 2 seconds for ongoing activity
|
||||
if currentTime - \
|
||||
if current_time - \
|
||||
self.env['commandBuffer']['lastProgressTime'] >= 2.0:
|
||||
self.playActivityBeep()
|
||||
self.env['commandBuffer']['lastProgressTime'] = currentTime
|
||||
self.play_activity_beep()
|
||||
self.env['commandBuffer']['lastProgressTime'] = current_time
|
||||
|
||||
def playProgressTone(self, percentage):
|
||||
def play_progress_tone(self, percentage):
|
||||
# Map 0-100% to 400-1200Hz frequency range
|
||||
frequency = 400 + (percentage * 8)
|
||||
frequency = max(400, min(1200, frequency)) # Clamp to safe range
|
||||
|
||||
# Use Sox directly for clean quiet tones like: play -qn synth .1 tri
|
||||
# 400 gain -8
|
||||
self.playQuietTone(frequency, 0.1)
|
||||
self.play_quiet_tone(frequency, 0.1)
|
||||
|
||||
def playActivityBeep(self):
|
||||
def play_activity_beep(self):
|
||||
# Single tone for generic activity
|
||||
self.playQuietTone(800, 0.08)
|
||||
self.play_quiet_tone(800, 0.08)
|
||||
|
||||
def playQuietTone(self, frequency, duration):
|
||||
def play_quiet_tone(self, frequency, duration):
|
||||
"""Play a quiet tone using Sox directly"""
|
||||
import subprocess
|
||||
import shlex
|
||||
@ -204,7 +204,7 @@ class command():
|
||||
|
||||
try:
|
||||
# Only play if sound is enabled
|
||||
if self.env['runtime']['settingsManager'].getSettingAsBool(
|
||||
if self.env['runtime']['SettingsManager'].get_setting_as_bool(
|
||||
'sound', 'enabled'):
|
||||
subprocess.Popen(
|
||||
shlex.split(command),
|
||||
@ -213,38 +213,38 @@ class command():
|
||||
stderr=None,
|
||||
shell=False)
|
||||
except Exception as e:
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
"Sox tone error: " + str(e), debug.debugLevel.ERROR)
|
||||
self.env['runtime']['DebugManager'].write_debug_out(
|
||||
"Sox tone error: " + str(e), debug.DebugLevel.ERROR)
|
||||
|
||||
def isCurrentLinePrompt(self):
|
||||
def is_current_line_prompt(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']:
|
||||
if not self.env['screen']['new_content_text']:
|
||||
return False
|
||||
|
||||
lines = self.env['screen']['newContentText'].split('\n')
|
||||
lines = self.env['screen']['new_content_text'].split('\n')
|
||||
if not lines:
|
||||
return False
|
||||
|
||||
# Check the last line (most common) and current cursor line for
|
||||
# prompt patterns
|
||||
linesToCheck = []
|
||||
lines_to_check = []
|
||||
|
||||
# Add last line (most common for prompts)
|
||||
if lines:
|
||||
linesToCheck.append(lines[-1])
|
||||
lines_to_check.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']])
|
||||
if (self.env['screen']['new_cursor']['y'] < len(lines) and
|
||||
self.env['screen']['new_cursor']['y'] != len(lines) - 1):
|
||||
lines_to_check.append(
|
||||
lines[self.env['screen']['new_cursor']['y']])
|
||||
|
||||
# Standalone prompt patterns (no commands mixed in)
|
||||
standalonePromptPatterns = [
|
||||
standalone_prompt_patterns = [
|
||||
r'^\s*\$\s*$', # Just $ (with whitespace)
|
||||
r'^\s*#\s*$', # Just # (with whitespace)
|
||||
r'^\s*>\s*$', # Just > (with whitespace)
|
||||
@ -268,33 +268,33 @@ class command():
|
||||
r'^[Aa]re\s+you\s+sure\?\s*.*$', # Are you sure?
|
||||
]
|
||||
|
||||
for line in linesToCheck:
|
||||
for line in lines_to_check:
|
||||
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(
|
||||
has_prompt_marker = bool(
|
||||
re.search(
|
||||
r'.*@.*[\\\$#>]',
|
||||
line) or re.search(
|
||||
r'^\[.*\]\s*[\\\$#>]',
|
||||
line))
|
||||
if hasPromptMarker:
|
||||
if has_prompt_marker:
|
||||
# If line has prompt marker but also has significant content after it,
|
||||
# it's a command line, not a standalone prompt
|
||||
promptEnd = max(
|
||||
prompt_end = max(
|
||||
line.rfind('$'),
|
||||
line.rfind('#'),
|
||||
line.rfind('>'),
|
||||
line.rfind('\\')
|
||||
)
|
||||
if promptEnd >= 0 and promptEnd < len(
|
||||
if prompt_end >= 0 and prompt_end < len(
|
||||
line) - 5: # More than just whitespace after prompt
|
||||
continue # This is a command line, not a standalone prompt
|
||||
|
||||
for pattern in standalonePromptPatterns:
|
||||
for pattern in standalone_prompt_patterns:
|
||||
try:
|
||||
if re.search(pattern, line):
|
||||
return True
|
||||
@ -307,5 +307,5 @@ class command():
|
||||
# If anything fails, assume it's not a prompt to be safe
|
||||
return False
|
||||
|
||||
def setCallback(self, callback):
|
||||
def set_callback(self, callback):
|
||||
pass
|
||||
|
Reference in New Issue
Block a user