Sync from development - prepare for v0.3.0

This commit is contained in:
Omni
2026-02-07 18:26:54 +00:00
parent b55e1cf768
commit 12294d3186
169 changed files with 31749 additions and 33649 deletions

View File

@@ -0,0 +1,98 @@
"""
Menu handler input and readline tab completion.
Exports: READLINE_* constants, basic_input_prompt, input_prompt, simple_path_completer, _shell_path_completer, _simple_path_completer.
"""
import logging
import os
import glob
from .ui_colors import COLOR_PROMPT, COLOR_RESET
READLINE_AVAILABLE = False
READLINE_HAS_PROMPT = False
READLINE_HAS_DISPLAY_HOOK = False
try:
import readline
READLINE_AVAILABLE = True
logging.debug("Readline imported for tab completion")
if hasattr(readline, 'set_prompt'):
READLINE_HAS_PROMPT = True
logging.debug("Readline has set_prompt capability")
else:
logging.debug("Readline does not have set_prompt capability, will use fallback")
try:
readline.parse_and_bind('tab: complete')
logging.debug("Readline tab completion successfully configured")
except Exception as e:
logging.warning(f"Error configuring readline tab completion: {e}. Tab completion may be limited.")
if hasattr(readline, 'set_completion_display_matches_hook'):
READLINE_HAS_DISPLAY_HOOK = True
logging.debug("Readline has completion display hook capability")
def custom_display_completions(substitution, matches, longest_match_length):
print()
try:
import shutil
term_width = shutil.get_terminal_size().columns
except (ImportError, AttributeError):
term_width = 80
items_per_line = max(1, term_width // (longest_match_length + 2))
for i, match in enumerate(matches):
print(f"{match:<{longest_match_length + 2}}", end='' if (i + 1) % items_per_line else '\n')
if len(matches) % items_per_line != 0:
print()
current_input = readline.get_line_buffer()
print(f"{COLOR_PROMPT}> {COLOR_RESET}{current_input}", end='', flush=True)
try:
readline.set_completion_display_matches_hook(custom_display_completions)
logging.debug("Custom completion display hook successfully set")
except Exception as e:
logging.warning(f"Error setting completion display hook: {e}. Using default display behavior.")
READLINE_HAS_DISPLAY_HOOK = False
else:
logging.debug("Readline doesn't have completion display hook capability, using default")
except ImportError:
logging.warning("readline not available. Tab completion for paths will be disabled.")
except Exception as e:
logging.warning(f"Error initializing readline: {e}. Tab completion for paths will be disabled.")
def basic_input_prompt(message, **kwargs):
return input(message)
input_prompt = basic_input_prompt
def _shell_path_completer(text, state):
"""Shell-like pathname completer for readline. Expands ~, handles absolute/relative paths."""
expanded = os.path.expanduser(os.path.expandvars(text))
if os.path.isdir(expanded):
pattern = os.path.join(expanded, '*')
else:
pattern = expanded + '*'
matches = glob.glob(pattern)
matches = [m + ('/' if os.path.isdir(m) else '') for m in matches]
if not text:
matches = glob.glob('*')
matches = [m + ('/' if os.path.isdir(m) else '') for m in matches]
try:
return matches[state]
except IndexError:
return None
def _simple_path_completer(text, state):
"""Simple pathname completer for readline. Prefix matching on path components."""
matches = glob.glob(text + '*')
matches = [f + ('/' if os.path.isdir(f) else '') for f in matches]
try:
return matches[state]
except IndexError:
return None
simple_path_completer = _simple_path_completer