diff --git a/config_selector.py b/config_selector.py index 132240b..667fa71 100755 --- a/config_selector.py +++ b/config_selector.py @@ -406,9 +406,19 @@ def draw_menu(stdscr, selector: ConfigSelector, selected_idx: int, view_mode: st def add_prefix_dialog(stdscr): """Show dialog to add a new prefix.""" h, w = stdscr.getmaxyx() - dialog_h, dialog_w = 5, 50 - dialog_y = (h - dialog_h) // 2 - dialog_x = (w - dialog_w) // 2 + + # Safety checks for minimum terminal size + min_h, min_w = 7, 20 + if h < min_h or w < min_w: + return "" # Terminal too small + + dialog_h, dialog_w = min(5, h - 2), min(50, w - 4) + dialog_y = max(0, (h - dialog_h) // 2) + dialog_x = max(0, (w - dialog_w) // 2) + + # Final safety check + if dialog_h <= 0 or dialog_w <= 0 or dialog_y < 0 or dialog_x < 0: + return "" # Draw dialog box dialog_win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x) @@ -416,8 +426,9 @@ def add_prefix_dialog(stdscr): dialog_win.addstr(1, 2, "Enter new prefix (ESC or empty input to cancel):") dialog_win.refresh() - # Input field - input_win = curses.newwin(1, dialog_w - 6, dialog_y + 2, dialog_x + 3) + # Input field - ensure it fits within the dialog + input_w = max(1, dialog_w - 6) + input_win = curses.newwin(1, input_w, dialog_y + 2, dialog_x + 3) input_win.clear() # Setup input mode @@ -465,14 +476,29 @@ def add_prefix_dialog(stdscr): def confirm_dialog(stdscr, message): """Show a confirmation dialog.""" h, w = stdscr.getmaxyx() - dialog_h, dialog_w = 5, 50 - dialog_y = (h - dialog_h) // 2 - dialog_x = (w - dialog_w) // 2 + + # Safety checks for minimum terminal size + min_h, min_w = 7, 20 + if h < min_h or w < min_w: + return False # Terminal too small, default to cancel + + dialog_h, dialog_w = min(5, h - 2), min(max(50, len(message) + 4), w - 4) + dialog_y = max(0, (h - dialog_h) // 2) + dialog_x = max(0, (w - dialog_w) // 2) + + # Final safety check + if dialog_h <= 0 or dialog_w <= 0 or dialog_y < 0 or dialog_x < 0: + return False # Draw dialog box dialog_win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x) dialog_win.box() - dialog_win.addstr(1, 2, message) + + # Truncate message if it's too long for the dialog + max_msg_len = dialog_w - 4 + display_message = message[:max_msg_len] if len(message) > max_msg_len else message + + dialog_win.addstr(1, 2, display_message) dialog_win.addstr(3, 2, "Press Y to confirm, any other key to cancel") dialog_win.refresh() @@ -488,19 +514,34 @@ def choose_prefix_dialog(stdscr, selector: ConfigSelector): unmatched_prefixes = sorted(selector.unmatched_files.keys()) if not unmatched_prefixes: - # Show message if no unmatched prefixes - message_win = curses.newwin(3, 40, (h - 3) // 2, (w - 40) // 2) + # Show message if no unmatched prefixes - ensure minimum window size + msg_h = min(5, h - 2) + msg_w = min(40, w - 4) + if msg_h < 3 or msg_w < 10: + return None # Terminal too small + message_win = curses.newwin(msg_h, msg_w, max(0, (h - msg_h) // 2), max(0, (w - msg_w) // 2)) message_win.box() message_win.addstr(1, 2, "No unmatched prefixes available.") message_win.refresh() message_win.getch() return None - # Calculate dialog dimensions - dialog_h = min(15, len(unmatched_prefixes) + 4) # +4 for title, instructions, and border - dialog_w = 60 - dialog_y = (h - dialog_h) // 2 - dialog_x = (w - dialog_w) // 2 + # Calculate dialog dimensions with safety checks + min_dialog_h = 6 # Minimum height for usable dialog + min_dialog_w = 30 # Minimum width for usable dialog + + # Ensure we have enough terminal space + if h < min_dialog_h + 2 or w < min_dialog_w + 4: + return None # Terminal too small + + dialog_h = min(max(min_dialog_h, min(15, len(unmatched_prefixes) + 4)), h - 2) # +4 for title, instructions, and border + dialog_w = min(max(min_dialog_w, 60), w - 2) + dialog_y = max(0, (h - dialog_h) // 2) + dialog_x = max(0, (w - dialog_w) // 2) + + # Final safety check + if dialog_h <= 0 or dialog_w <= 0 or dialog_y < 0 or dialog_x < 0: + return None # Draw dialog box dialog_win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x) @@ -580,8 +621,12 @@ def select_individual_files_dialog(stdscr, selector: ConfigSelector, prefix: str action_text = "SPACE: Toggle selection | ENTER: Add selected | ESC: Cancel" if not files: - # Show message if no files - message_win = curses.newwin(3, 40, (h - 3) // 2, (w - 40) // 2) + # Show message if no files - ensure minimum window size + msg_h = min(5, h - 2) + msg_w = min(40, w - 4) + if msg_h < 3 or msg_w < 10: + return [] # Terminal too small + message_win = curses.newwin(msg_h, msg_w, max(0, (h - msg_h) // 2), max(0, (w - msg_w) // 2)) message_win.box() message_win.addstr(1, 2, "No files available.") message_win.refresh() @@ -594,11 +639,22 @@ def select_individual_files_dialog(stdscr, selector: ConfigSelector, prefix: str # Track selected files selected_files = set() - # Calculate dialog dimensions - dialog_h = min(20, len(files) + 5) # +5 for title, instructions, pagination, and border - dialog_w = min(w - 4, 80) - dialog_y = (h - dialog_h) // 2 - dialog_x = (w - dialog_w) // 2 + # Calculate dialog dimensions with safety checks + min_dialog_h = 8 # Minimum height for usable dialog + min_dialog_w = 30 # Minimum width for usable dialog + + # Ensure we have enough terminal space + if h < min_dialog_h + 2 or w < min_dialog_w + 4: + return [] # Terminal too small + + dialog_h = min(max(min_dialog_h, min(20, len(files) + 5)), h - 2) # +5 for title, instructions, pagination, and border + dialog_w = min(max(min_dialog_w, min(w - 4, 80)), w - 2) + dialog_y = max(0, (h - dialog_h) // 2) + dialog_x = max(0, (w - dialog_w) // 2) + + # Final safety check + if dialog_h <= 0 or dialog_w <= 0 or dialog_y < 0 or dialog_x < 0: + return [] # Draw dialog box dialog_win = curses.newwin(dialog_h, dialog_w, dialog_y, dialog_x) @@ -722,11 +778,20 @@ def preview_yaml_cleaning(stdscr, content): """Show a simplified schematic preview of the YAML cleaning process.""" h, w = stdscr.getmaxyx() + # Safety checks for minimum terminal size + min_h, min_w = 10, 30 + if h < min_h or w < min_w: + return # Terminal too small + # Calculate preview window size - preview_h = min(12, h - 8) # Smaller height - preview_w = min(70, w - 4) - preview_y = (h - preview_h) // 2 - preview_x = (w - preview_w) // 2 + preview_h = min(max(8, min(12, h - 8)), h - 2) # Smaller height + preview_w = min(max(30, min(70, w - 4)), w - 2) + preview_y = max(0, (h - preview_h) // 2) + preview_x = max(0, (w - preview_w) // 2) + + # Final safety check + if preview_h <= 0 or preview_w <= 0 or preview_y < 0 or preview_x < 0: + return # Create window preview_win = curses.newwin(preview_h, preview_w, preview_y, preview_x) @@ -797,11 +862,22 @@ def confirm_yaml_cleaning(stdscr, selector): except Exception as e: # In case of error, just show a simple message error_msg = f"Error reading sample file: {str(e)}" - message_win = curses.newwin(3, min(len(error_msg) + 4, w - 4), (h - 3) // 2, (w - min(len(error_msg) + 4, w - 4)) // 2) - message_win.box() - message_win.addstr(1, 2, error_msg[:w-8]) - message_win.refresh() - message_win.getch() + # Safety checks for window creation + if h >= 5 and w >= 20: + msg_h = min(3, h - 2) + msg_w = min(len(error_msg) + 4, w - 4) + if msg_w < 10: + msg_w = min(20, w - 4) + msg_y = max(0, (h - msg_h) // 2) + msg_x = max(0, (w - msg_w) // 2) + + if msg_h > 0 and msg_w > 0 and msg_y >= 0 and msg_x >= 0: + message_win = curses.newwin(msg_h, msg_w, msg_y, msg_x) + message_win.box() + display_msg = error_msg[:msg_w-4] if len(error_msg) > msg_w-4 else error_msg + message_win.addstr(1, 2, display_msg) + message_win.refresh() + message_win.getch() return confirm_dialog(stdscr, "Copy all matched files to config directory and remove UUID and _core?") @@ -995,11 +1071,21 @@ def main(stdscr): else: # Show message that you can only select files from complete prefixes h, w = stdscr.getmaxyx() - message_win = curses.newwin(3, 60, (h - 3) // 2, (w - 60) // 2) - message_win.box() - message_win.addstr(1, 2, "Can only select files from complete prefixes.") - message_win.refresh() - message_win.getch() + # Safety checks for window creation + if h >= 5 and w >= 30: + msg_h = min(3, h - 2) + msg_w = min(60, w - 4) + if msg_w < 20: + msg_w = min(30, w - 4) + msg_y = max(0, (h - msg_h) // 2) + msg_x = max(0, (w - msg_w) // 2) + + if msg_h > 0 and msg_w > 0 and msg_y >= 0 and msg_x >= 0: + message_win = curses.newwin(msg_h, msg_w, msg_y, msg_x) + message_win.box() + message_win.addstr(1, 2, "Can only select files from complete prefixes.") + message_win.refresh() + message_win.getch() else: # Unmatched view # Find a matching prefix to add files to target_prefix = None