saving before having gemini take a crack at fixing things.

This commit is contained in:
edschuy95 2026-01-21 22:06:07 -05:00
parent a9ac39357f
commit aa782f3080
3 changed files with 224 additions and 12 deletions

View file

@ -1,4 +1,5 @@
from pathlib import Path from pathlib import Path
from textmenu import TextMenu
import threading import threading
import time import time
import os import os
@ -92,19 +93,23 @@ ANSI_ESCAPE_RE = re.compile(
# ========================================================== # ==========================================================
# ===================== UTILITY FUNCTIONS ================= # ===================== UTILITY FUNCTIONS =================
# def apply_backspaces(s): def find_pk3_subfolders(base_path):
# # Ensure we are working with str """
# if isinstance(s, bytes): Returns a list of subfolder names under base_path that contain
# s = s.decode(errors="ignore") at least one .pk3 file.
"""
base = Path(base_path)
matches = []
# buf = [] if not base.is_dir():
# for c in s: return matches
# if c == "\x08":
# if buf: for entry in base.iterdir():
# buf.pop() if entry.is_dir():
# else: if any(f.is_file() and f.suffix.lower() == ".pk3" for f in entry.iterdir()):
# buf.append(c) matches.append(entry.name)
# return "".join(buf)
return matches
def acquire_single_instance(lockfile_base: str): def acquire_single_instance(lockfile_base: str):
""" """
@ -583,6 +588,24 @@ def main():
# bufsize=0, # unbuffered # bufsize=0, # unbuffered
# universal_newlines=False) # universal_newlines=False)
items = ["Pee pee","Poo poo","Stinky caca peepee poopoo pants.","a","b","c","d"]
menu = TextMenu(
items,
width=30,
height=5,
title="Choose your stink",
border_fg="dark gray",
border_bg="dark red",
inside_fg="dark red",
inside_bg="black",
selected_fg="black",
selected_bg="dark red",
timeout=10
)
choice=menu.show()
print(f"You selected: {choice}")
if os.name == "nt": if os.name == "nt":
pty_proc = subprocess.Popen( pty_proc = subprocess.Popen(
[game_exe, "+set", "ttycon", "1"] + sys.argv[1:], [game_exe, "+set", "ttycon", "1"] + sys.argv[1:],

View file

@ -25,6 +25,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="RA3MP3Playback.py" /> <Compile Include="RA3MP3Playback.py" />
<Compile Include="textmenu.py" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" /> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
<!-- Uncomment the CoreCompile target to enable the Build command in <!-- Uncomment the CoreCompile target to enable the Build command in

188
textmenu.py Normal file
View file

@ -0,0 +1,188 @@
import urwid
import sys
import os
import time
import threading
import queue
import math
# Detect if running in a real terminal
def is_real_terminal():
if not sys.stdout.isatty():
return False
if os.name == 'nt':
vs_vars = ["PYCHARM_HOSTED", "VSCODE_PID", "TERM_PROGRAM"]
for var in vs_vars:
if var in os.environ:
return False
return True
# Fallback menu for non-terminal consoles with timeout note in input prompt
def fallback_menu(choices, title="Menu", timeout=None):
print(f"=== {title} ===")
for i, c in enumerate(choices, 1):
print(f"{i}. {c}")
prompt = "Enter choice number"
if timeout is not None:
prompt += f" (times out in {timeout}s)"
prompt += ": "
if timeout is None:
while True:
try:
sel = int(input(prompt))
if 1 <= sel <= len(choices):
return choices[sel - 1]
except ValueError:
pass
print("Invalid input. Try again.")
else:
q = queue.Queue()
def get_input():
try:
val = input(prompt)
q.put(val)
except Exception:
q.put(None)
t = threading.Thread(target=get_input, daemon=True)
t.start()
start_time = time.time()
while True:
try:
val = q.get_nowait()
if val is None:
return None
sel = int(val)
if 1 <= sel <= len(choices):
return choices[sel - 1]
except queue.Empty:
if time.time() - start_time > timeout:
return None
time.sleep(0.05)
except ValueError:
print("Invalid input. Try again.")
t = threading.Thread(target=get_input, daemon=True)
t.start()
class TextMenu:
def __init__(self, choices, width=40, height=10, title="Menu",
border_fg="white", border_bg="dark blue",
inside_fg="white", inside_bg="black",
selected_fg="black", selected_bg="light gray",
timeout=None):
self.choices = choices
self.width = width
self.height = height
self.title = title
self.selected = None
self.timeout = timeout
self._start_time = None
self._timeout_alarm = None
self.last_click_time = 0
self.double_click_threshold = 0.5 # seconds
# Buttons with double-click support
self.menu_widgets = []
for c in choices:
btn = urwid.Button(c)
urwid.connect_signal(btn, 'click', self._double_click, c)
self.menu_widgets.append(urwid.AttrMap(btn, None, focus_map='selected'))
# Scrollable ListBox
self.list_walker = urwid.SimpleFocusListWalker(self.menu_widgets)
self.listbox = urwid.ListBox(self.list_walker)
self.listbox = urwid.AttrMap(self.listbox, 'inside')
# Fix height
self.box_adapter = urwid.BoxAdapter(self.listbox, self.height)
# LineBox with border and title
self.linebox = urwid.LineBox(self.box_adapter, title=self.title)
linebox_colored = urwid.AttrMap(self.linebox, 'border')
# Fix width and center
self.frame = urwid.Filler(
urwid.Padding(linebox_colored, align='center', width=self.width),
valign='middle'
)
# Color palette
self.palette = [
('border', border_fg, border_bg),
('inside', inside_fg, inside_bg),
('selected', selected_fg, selected_bg),
]
# MainLoop
self.loop = urwid.MainLoop(
self.frame,
palette=self.palette,
unhandled_input=self._unhandled_input,
handle_mouse=True
)
# Cancel timeout and restore title
def _cancel_timeout(self):
if self._timeout_alarm is not None:
self.loop.remove_alarm(self._timeout_alarm)
self._timeout_alarm = None
self.linebox.set_title(self.title) # restore original title
# Mouse double-click
def _double_click(self, button, choice_text):
self._cancel_timeout()
now = time.time()
if now - self.last_click_time < self.double_click_threshold:
self.selected = choice_text
raise urwid.ExitMainLoop()
self.last_click_time = now
# Keyboard or mouse input
def _unhandled_input(self, key):
# Cancel timeout on any input
self._cancel_timeout()
if key == 'enter':
focus_widget, _ = self.listbox.get_focus()
if focus_widget:
self.selected = focus_widget.base_widget.get_label()
raise urwid.ExitMainLoop()
# Scroll events also count: 'up', 'down', 'page up', 'page down', 'mouse press', 'mouse drag', 'mouse wheel'
# urwid passes these to unhandled_input automatically, so we cancel timeout above
# Timeout exit
def _timeout_exit(self, loop, user_data=None):
self.selected = None
raise urwid.ExitMainLoop()
# Update countdown in title, precise timing
def _update_timer(self, loop, user_data=None):
elapsed = time.time() - self._start_time
remaining = self.timeout - elapsed
if remaining <= 0:
self.linebox.set_title(self.title)
self._timeout_exit(loop)
return
self.linebox.set_title(f"{self.title} ({math.ceil(remaining)}s)")
# Schedule next update in 0.1s for precise timing
self._timeout_alarm = loop.set_alarm_in(0.1, self._update_timer)
def show(self):
"""Show menu and return selected item. Returns None if timeout expires."""
if not is_real_terminal():
return fallback_menu(self.choices, title=self.title, timeout=self.timeout)
# Start countdown immediately
if self.timeout is not None:
self._start_time = time.time()
self._update_timer(self.loop) # show countdown immediately
self.loop.run()
return self.selected