feat(ui): Add color-coordinated syntax, fix clipping, redesign flag browser

Major UI improvements addressing user feedback:

1. Color-Coordinated Syntax Highlighting
   - Command names: Bright cyan + bold
   - [OPTIONS]/[OPTION]: Yellow
   - UPPERCASE args (FILE, PATH, STRING): Magenta
   - Lowercase optional args: Green
   - Ellipsis (...): Dim white
   - Applied to both interactive menu and explain mode

2. Fixed Clipping Bug in Interactive Menu
   - Recalculated width allocations more conservatively
   - Changed to percentage-based DESC_WIDTH (40% of terminal)
   - Added min/max caps for widths
   - Improved spacing calculation (TERM_WIDTH - VISIBLE_WIDTH - 8)
   - Added safety bounds (min 3, max 20 spaces)
   - Syntax now always visible (truncated with ... if needed)

3. Redesigned Flag Browser for Better UX
   - Eliminated wasted dead space above fzf
   - Integrated syntax and description INTO fzf header
   - Created elegant box header with command info:
     - Command name
     - Colorized syntax
     - Description
   - Height increased to 95% for more flags visible
   - Preview window reduced to down:3:wrap
   - Removed echo spam ("Extracting flags...", "Found X flags...")
   - Clean, immediate transition to flag browser

4. Updated Function Signatures
   - show_flags() now accepts: cmd_name, syntax, description
   - browse_flags_fuzzy() now accepts: cmd_name, syntax, description
   - Interactive mode passes all three parameters
   - Explain mode uses colorize_syntax() function

5. New Functions
   - colorize_syntax(): Parse and colorize syntax by element type
   - Applied consistently across all display modes

User Experience Improvements:
-  No more clipping - syntax always visible
-  Instant recognition of parameter types via color
-  No wasted screen space before flag browser
-  Command info integrated cleanly in fzf interface
-  Professional, color-coordinated appearance

Technical Details:
- Width calculation: DESC (40% terminal), SYNTAX (remaining - 25)
- Safety bounds on all width calculations
- Color coordination: cyan/yellow/magenta/green/dim
- Consistent formatting across interactive and explain modes
This commit is contained in:
2025-10-28 02:01:29 -07:00
parent 73de59c92b
commit c0af8c15ca
+109 -84
View File
@@ -407,29 +407,31 @@ mode_explain() {
if command -v "$base_cmd" &> /dev/null || type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then if command -v "$base_cmd" &> /dev/null || type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then
echo -e "${CYAN}${BOLD}SYNTAX:${NC}" echo -e "${CYAN}${BOLD}SYNTAX:${NC}"
local raw_syntax=""
if type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then if type "$base_cmd" 2>/dev/null | grep -q "shell builtin"; then
# For builtins, get the first line of help # For builtins, get the first line of help
local syntax=$(help "$base_cmd" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//') raw_syntax=$(help "$base_cmd" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//')
if [ -n "$syntax" ]; then [ -z "$raw_syntax" ] && raw_syntax="$base_cmd [options]"
echo -e " ${GREEN}$syntax${NC}"
else
echo -e " ${GREEN}$base_cmd [options]${NC}"
fi
else else
# For regular commands, extract SYNOPSIS section from man page # For regular commands, extract SYNOPSIS section from man page
local synopsis=$(man "$base_cmd" 2>/dev/null | col -b | sed -n '/^SYNOPSIS/,/^DESCRIPTION/p' | head -15 | tail -n +2 | head -n -1 | sed 's/^[[:space:]]*/ /') local synopsis=$(man "$base_cmd" 2>/dev/null | col -b | sed -n '/^SYNOPSIS/,/^DESCRIPTION/p' | head -15 | tail -n +2 | head -n -1 | tr '\n' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
if [ -n "$synopsis" ]; then if [ -n "$synopsis" ]; then
echo -e "${GREEN}$synopsis${NC}" raw_syntax="$synopsis"
else else
# Fallback: try to get usage from command itself # Fallback: try to get usage from command itself
local usage=$("$base_cmd" --help 2>&1 | grep -i "usage" | head -1 | sed 's/^[Uu]sage: //') local usage=$("$base_cmd" --help 2>&1 | grep -i "usage" | head -1 | sed 's/^[Uu]sage: //')
if [ -n "$usage" ]; then if [ -n "$usage" ]; then
echo -e " ${GREEN}$usage${NC}" raw_syntax="$usage"
else else
echo -e " ${GREEN}$base_cmd [options]${NC}" raw_syntax="$base_cmd [options]"
fi fi
fi fi
fi fi
# Display colorized syntax
if [ -n "$raw_syntax" ]; then
colorize_syntax "$raw_syntax" | sed 's/^/ /'
fi
echo "" echo ""
# Add practical example # Add practical example
@@ -590,6 +592,39 @@ get_practical_example() {
echo "$example" echo "$example"
} }
# Colorize syntax elements by type for instant recognition
colorize_syntax() {
local syntax="$1"
local colored_syntax="$syntax"
# Color scheme for syntax elements:
# - Command name (first word): Bright cyan
# - [OPTIONS], [OPTION], etc.: Yellow
# - UPPERCASE args (FILE, STRING, SET1, etc.): Magenta
# - Lowercase/optional args: Green
# - ... (ellipsis): Dim white
# Extract command name (first word)
local cmd_word=$(echo "$syntax" | awk '{print $1}')
# Start with command in bright cyan
colored_syntax=$(echo "$syntax" | sed "s/^$cmd_word/\\${CYAN}\\${BOLD}$cmd_word\\${NC}/")
# Color [OPTIONS], [OPTION], [EXPRESSION], etc. in yellow
colored_syntax=$(echo "$colored_syntax" | sed -E "s/\[([A-Z][A-Z_-]*)\]/[\\${YELLOW}\1\\${NC}]/g")
# Color UPPERCASE arguments in magenta (FILE, STRING, PATH, SET1, etc.)
colored_syntax=$(echo "$colored_syntax" | sed -E "s/([[:space:]])([A-Z][A-Z0-9_-]*[A-Z])([[:space:][:punct:]$])/\1\\${MAGENTA}\2\\${NC}\3/g")
# Color lowercase optional arguments in green
colored_syntax=$(echo "$colored_syntax" | sed -E "s/\[([a-z][a-z-]*)\]/[\\${GREEN}\1\\${NC}]/g")
# Color ellipsis (...) in dim white
colored_syntax=$(echo "$colored_syntax" | sed "s/\.\.\./\\${DIM}...\\${NC}/g")
echo -e "$colored_syntax"
}
# Extract all flags from man page or help # Extract all flags from man page or help
extract_all_flags() { extract_all_flags() {
local cmd_name="$1" local cmd_name="$1"
@@ -608,90 +643,86 @@ extract_all_flags() {
} }
# Interactive flag browser with fuzzy search # Interactive flag browser with fuzzy search
# Now includes syntax and description in the fzf interface itself
browse_flags_fuzzy() { browse_flags_fuzzy() {
local cmd_name="$1" local cmd_name="$1"
local cmd_syntax="$2"
local cmd_desc="$3"
echo "" # Extract all flags silently
echo -e "${CYAN}Extracting flags from man page...${NC}"
# Extract all flags
local all_flags=$(extract_all_flags "$cmd_name") local all_flags=$(extract_all_flags "$cmd_name")
if [ -z "$all_flags" ]; then if [ -z "$all_flags" ]; then
echo ""
echo -e "${RED}No flags found for: $cmd_name${NC}" echo -e "${RED}No flags found for: $cmd_name${NC}"
echo "" echo ""
echo "Showing basic command info..."
return 1 return 1
fi fi
# Count flags # Count flags
local flag_count=$(echo "$all_flags" | wc -l) local flag_count=$(echo "$all_flags" | wc -l)
echo -e "${GREEN}Found $flag_count flags. Opening fuzzy search...${NC}" # Colorize the syntax for the header
echo "" local colored_syntax=$(colorize_syntax "$cmd_syntax")
sleep 1
# Show in fzf for fuzzy searching # Create multi-line header with command info
local header="╔═══════════════════════════════════════════════════════════════════════════════╗
║ Command: ${CYAN}${BOLD}$cmd_name${NC}
║ Syntax: $colored_syntax
║ Desc: ${GREEN}$cmd_desc${NC}
╚═══════════════════════════════════════════════════════════════════════════════╝
🔍 Fuzzy search $flag_count flags | Multi-select: Ctrl-A/D | Esc: Back to menu"
# Show in fzf with integrated command info
echo "$all_flags" | fzf \ echo "$all_flags" | fzf \
--height 80% \ --height 95% \
--border \ --border=none \
--header "🔍 Fuzzy search through $cmd_name flags (Esc to exit)" \ --header "$header" \
--preview 'echo {}' \ --preview 'echo {}' \
--preview-window=up:40%:wrap \ --preview-window=down:3:wrap \
--prompt "Search flags: " \ --prompt "Search: " \
--multi \ --multi \
--bind 'ctrl-a:select-all' \ --bind 'ctrl-a:select-all' \
--bind 'ctrl-d:deselect-all' --bind 'ctrl-d:deselect-all' \
--ansi \
--color=header:bold:cyan
return 0 return 0
} }
show_flags() { show_flags() {
CMD_NAME=$1 CMD_NAME=$1
FILTER=$2 CMD_SYNTAX=$2
CMD_DESC=$3
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}Command Details: ${GREEN}$CMD_NAME${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# Display SYNTAX section
if command -v "$CMD_NAME" &> /dev/null || type "$CMD_NAME" 2>/dev/null | grep -q "shell builtin"; then
echo -e "${CYAN}${BOLD}SYNTAX:${NC}"
# Extract syntax if not provided
if [ -z "$CMD_SYNTAX" ]; then
if type "$CMD_NAME" 2>/dev/null | grep -q "shell builtin"; then if type "$CMD_NAME" 2>/dev/null | grep -q "shell builtin"; then
# For builtins, get the first line of help # For builtins, get the first line of help
local syntax=$(help "$CMD_NAME" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//') CMD_SYNTAX=$(help "$CMD_NAME" 2>/dev/null | head -1 | sed 's/^[[:space:]]*//')
if [ -n "$syntax" ]; then [ -z "$CMD_SYNTAX" ] && CMD_SYNTAX="$CMD_NAME [options]"
echo -e " ${GREEN}$syntax${NC}"
else
echo -e " ${GREEN}$CMD_NAME [options]${NC}"
fi
else else
# For regular commands, extract SYNOPSIS section from man page # For regular commands, extract SYNOPSIS section from man page
local synopsis=$(man "$CMD_NAME" 2>/dev/null | col -b | sed -n '/^SYNOPSIS/,/^DESCRIPTION/p' | head -15 | tail -n +2 | head -n -1 | sed 's/^[[:space:]]*/ /') local synopsis=$(man "$CMD_NAME" 2>/dev/null | col -b | sed -n '/^SYNOPSIS/,/^DESCRIPTION/p' | head -15 | tail -n +2 | head -n -1 | tr '\n' ' ' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')
if [ -n "$synopsis" ]; then if [ -n "$synopsis" ]; then
echo -e "${GREEN}$synopsis${NC}" CMD_SYNTAX="$synopsis"
else else
# Fallback: try to get usage from command itself # Fallback: try to get usage from command itself
local usage=$("$CMD_NAME" --help 2>&1 | grep -i "usage" | head -1 | sed 's/^[Uu]sage: //') local usage=$("$CMD_NAME" --help 2>&1 | grep -i "usage" | head -1 | sed 's/^[Uu]sage: //')
if [ -n "$usage" ]; then if [ -n "$usage" ]; then
echo -e " ${GREEN}$usage${NC}" CMD_SYNTAX="$usage"
else else
echo -e " ${GREEN}$CMD_NAME [options]${NC}" CMD_SYNTAX="$CMD_NAME [options]"
fi fi
fi fi
fi fi
echo ""
fi fi
# Display FLAGS/OPTIONS section with fuzzy search # Default description if not provided
echo -e "${YELLOW}${BOLD}FLAGS/OPTIONS:${NC}" [ -z "$CMD_DESC" ] && CMD_DESC="No description available"
echo -e "${DIM}Opening interactive flag browser...${NC}"
echo ""
# Use fuzzy flag browser instead of static display # Directly open fuzzy flag browser with integrated command info (no wasted space)
browse_flags_fuzzy "$CMD_NAME" browse_flags_fuzzy "$CMD_NAME" "$CMD_SYNTAX" "$CMD_DESC"
echo "" echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
@@ -935,16 +966,17 @@ if [ "$INTERACTIVE" = true ]; then
# Get terminal width for proper alignment # Get terminal width for proper alignment
TERM_WIDTH=$(tput cols 2>/dev/null || echo 120) TERM_WIDTH=$(tput cols 2>/dev/null || echo 120)
# Calculate widths (leave room for colors and spacing) # Calculate widths more conservatively to prevent clipping
# Reserve space for: command (15) + ": " (2) + description (40%) + spaces (5) + syntax (remaining)
MAX_CMD_WIDTH=15 MAX_CMD_WIDTH=15
SYNTAX_WIDTH=45 DESC_WIDTH=$((TERM_WIDTH * 40 / 100))
DESC_WIDTH=$((TERM_WIDTH - MAX_CMD_WIDTH - SYNTAX_WIDTH - 10))
# Ensure minimum widths
if [ $DESC_WIDTH -lt 30 ]; then
DESC_WIDTH=30
SYNTAX_WIDTH=$((TERM_WIDTH - MAX_CMD_WIDTH - DESC_WIDTH - 10)) SYNTAX_WIDTH=$((TERM_WIDTH - MAX_CMD_WIDTH - DESC_WIDTH - 10))
fi
# Ensure minimum widths and cap maximums
[ $DESC_WIDTH -lt 20 ] && DESC_WIDTH=20
[ $SYNTAX_WIDTH -lt 30 ] && SYNTAX_WIDTH=30
[ $DESC_WIDTH -gt 50 ] && DESC_WIDTH=50
[ $SYNTAX_WIDTH -gt 60 ] && SYNTAX_WIDTH=60
# Format each command with colors and right-aligned syntax # Format each command with colors and right-aligned syntax
FORMATTED_COMMANDS=() FORMATTED_COMMANDS=()
@@ -961,7 +993,7 @@ if [ "$INTERACTIVE" = true ]; then
SYNTAX_PART="$CMD_NAME [options]" SYNTAX_PART="$CMD_NAME [options]"
fi fi
# Truncate if too long (accounting for actual content, not color codes) # Truncate if too long
if [ ${#DESC_PART} -gt $DESC_WIDTH ]; then if [ ${#DESC_PART} -gt $DESC_WIDTH ]; then
DESC_PART="${DESC_PART:0:$((DESC_WIDTH-3))}..." DESC_PART="${DESC_PART:0:$((DESC_WIDTH-3))}..."
fi fi
@@ -969,25 +1001,29 @@ if [ "$INTERACTIVE" = true ]; then
SYNTAX_PART="${SYNTAX_PART:0:$((SYNTAX_WIDTH-3))}..." SYNTAX_PART="${SYNTAX_PART:0:$((SYNTAX_WIDTH-3))}..."
fi fi
# Calculate padding for right-alignment of syntax # Calculate exact padding needed for right-alignment
# Total visible length = cmd_name + ": " + desc + spaces + syntax # Account for visual width only (not ANSI codes)
CMD_LEN=${#CMD_NAME} CMD_LEN=${#CMD_NAME}
DESC_LEN=${#DESC_PART} DESC_LEN=${#DESC_PART}
SYNTAX_LEN=${#SYNTAX_PART} SYNTAX_LEN=${#SYNTAX_PART}
NEEDED_SPACES=$((TERM_WIDTH - CMD_LEN - 2 - DESC_LEN - SYNTAX_LEN - 5))
# Ensure at least 2 spaces # Calculate spaces needed: terminal width - all visible characters - separator chars
if [ $NEEDED_SPACES -lt 2 ]; then # Visible: cmd + ": " + desc + syntax = CMD_LEN + 2 + DESC_LEN + SYNTAX_LEN
NEEDED_SPACES=2 VISIBLE_WIDTH=$((CMD_LEN + 2 + DESC_LEN + SYNTAX_LEN))
fi NEEDED_SPACES=$((TERM_WIDTH - VISIBLE_WIDTH - 8)) # Extra padding for safety
# Minimum spacing
[ $NEEDED_SPACES -lt 3 ] && NEEDED_SPACES=3
# Maximum spacing to prevent excessive gaps
[ $NEEDED_SPACES -gt 20 ] && NEEDED_SPACES=20
# Create spacing string # Create spacing string
SPACES=$(printf '%*s' "$NEEDED_SPACES" '') SPACES=$(printf '%*s' "$NEEDED_SPACES" '')
# Format with colors: # Format with colors:
# - Cyan for command name # - Bright cyan for command name
# - White for description # - White for description
# - Dim cyan for syntax # - Dim cyan for syntax (right-aligned)
formatted_line="\033[0;36m${CMD_NAME}\033[0m: ${DESC_PART}${SPACES}\033[2;36m${SYNTAX_PART}\033[0m" formatted_line="\033[0;36m${CMD_NAME}\033[0m: ${DESC_PART}${SPACES}\033[2;36m${SYNTAX_PART}\033[0m"
FORMATTED_COMMANDS+=("$formatted_line") FORMATTED_COMMANDS+=("$formatted_line")
@@ -1030,20 +1066,9 @@ if [ "$INTERACTIVE" = true ]; then
fi fi
done done
echo "" # Directly show fuzzy flag browser with integrated command info (no wasted space)
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" clear
echo -e "${YELLOW}Command:${NC} ${MAGENTA}$CMD${NC}" show_flags "$CMD" "$SYNTAX" "$DESC"
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
echo ""
echo -e "${CYAN}${BOLD}SYNTAX:${NC}"
echo -e " ${GREEN}$SYNTAX${NC}"
echo ""
echo -e "${GREEN}Description:${NC} $DESC"
echo ""
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
# Directly show fuzzy flag browser (no filter prompt)
show_flags "$CMD"
# After viewing flags, ask if user wants to search another command # After viewing flags, ask if user wants to search another command
echo "" echo ""