Repository: saulpw/visidata

Issue:
ZSH completion fails with invalid option definition
I'm using v2.10.2 installed via the [latest Arch package](https://archlinux.org/packages/community/any/visidata/). Visidata shell completion in my ZSH is currently broken.

```
$ vd <tab>
_arguments:comparguments:327: invalid option definition: --fmt_expand_list[format str to use for names of columns expanded from list (colname, index) (default: %s[%s])]:str
```


Discussion:
Here is the shell completion file that was installed by the Arch package.

```
#compdef vd visidata

(( $+functions[_vd_files] )) ||
_vd_files () {
  case $PREFIX in
    (*) _files $* ;;
  esac
  case $PREFIX in
    (+) _message -e 'toplevel:subsheet:col:row' ;;
    (+<1->) _message -e 'toplevel' ;;
    (+<1->:) _message -e 'subsheet' ;;
    (+<1->:<1->:) _message -e 'col' ;;
    (+<1->:<1->:<1->:) _message -e 'row' ;;
  esac
}

_arguments -S \
    --visidata_dir'[directory to load and store additional files (default: ~/.visidata/)]:str:_files' \
    --disp_splitwin_pct'[height of second sheet on screen (default: 0)]:int' \
    --mouse_interval'[max time between press/release for click (ms) (default: 1)]:int' \
    --color_sidebar'[color of sidebar (default: black on 114 blue)]:str' \
    --null_value'[a value to be counted as null (default: None)]:NoneType' \
    --undo'[enable undo/redo (default: True)]' \
    --disp_currency_fmt'[default fmtstr to format for currency values (default: %.02f)]:str' \
    --disp_float_fmt'[default fmtstr to format for float values (default: {:.02f})]:str' \
    --disp_int_fmt'[default fmtstr to format for int values (default: {:.0f})]:str' \
    --disp_date_fmt'[default fmtstr to strftime for date values (default: %Y-%m-%d)]:str' \
    --col_cache_size'[max number of cache entries in each cached column (default: 0)]:int' \
    {--force_valid_colnames,--clean_names}'[clean column/sheet names to be valid Python identifiers (default: False)]' \
    --default_width'[default column width (default: 20)]:int' \
    --default_height'[default column height (default: 4)]:int' \
    --textwrap_cells'[wordwrap text for multiline rows (default: True)]' \
    --quitguard'[confirm before quitting modified sheet (default: False)]' \
    --debug'[exit on error and display stacktrace (default: False)]' \
    --skip'[skip N rows before header (default: 0)]:int' \
    --header'[parse first N rows as column names (default: 1)]:int' \
    --load_lazy'[load subsheets always (False) or lazily (True) (default: False)]' \
    --force_256_colors'[use 256 colors even if curses reports fewer (default: False)]' \
    --disp_note_none'[visible contents of a cell whose value is None (default: ⌀)]:str' \
    --disp_truncator'[indicator that the contents are only partially visible (default: …)]:str' \
    --disp_oddspace'[displayable character for odd whitespace (default: ·)]:str' \
    --disp_more_left'[header note indicating more columns to the left (default: <)]:str' \
    --disp_more_right'[header note indicating more columns to the right (default: >)]:str' \
    --disp_error_val'[displayed contents for computation exception (default: )]:str' \
    --disp_ambig_width'[width to use for unicode chars marked ambiguous (default: 1)]:int' \
    --disp_pending'[string to display in pending cells (default: )]:str' \
    --note_pending'[note to display for pending cells (default: ⌛)]:str' \
    --note_format_exc'[cell note for an exception during formatting (default: ?)]:str' \
    --note_getter_exc'[cell note for an exception during computation (default: !)]:str' \
    --note_type_exc'[cell note for an exception during type conversion (default: !)]:str' \
    --color_note_pending'[color of note in pending cells (default: bold magenta)]:str' \
    --color_note_type'[color of cell note for non-str types in anytype columns (default: 226 yellow)]:str' \
    --color_note_row'[color of row note on left edge (default: 220 yellow)]:str' \
    --scroll_incr'[amount to scroll with scrollwheel (default: -3)]:int' \
    --disp_column_sep'[separator between columns (default: │)]:str' \
    --disp_keycol_sep'[separator between key columns and rest of columns (default: ║)]:str' \
    --disp_rowtop_sep'[ (default: │)]:str' \
    --disp_rowmid_sep'[ (default: ⁝)]:str' \
    --disp_rowbot_sep'[ (default: ⁝)]:str' \
    --disp_rowend_sep'[ (default: ║)]:str' \
    --disp_keytop_sep'[ (default: ║)]:str' \
    --disp_keymid_sep'[ (default: ║)]:str' \
    --disp_keybot_sep'[ (default: ║)]:str' \
    --disp_endtop_sep'[ (default: ║)]:str' \
    --disp_endmid_sep'[ (default: ║)]:str' \
    --disp_endbot_sep'[ (default: ║)]:str' \
    --disp_selected_note'[ (default: •)]:str' \
    --disp_sort_asc'[characters for ascending sort (default: ↑↟⇞⇡⇧⇑)]:str' \
    --disp_sort_desc'[characters for descending sort (default: ↓↡⇟⇣⇩⇓)]:str' \
    --color_default'[the default fg and bg colors (default: white on black)]:str' \
    --color_default_hdr'[color of the column headers (default: bold)]:str' \
    --color_bottom_hdr'[color of the bottom header row (default: underline)]:str' \
    --color_current_row'[color of the cursor row (default: reverse)]:str' \
    --color_current_col'[color of the cursor column (default: bold)]:str' \
    --color_current_hdr'[color of the header for the cursor column (default: bold reverse)]:str' \
    --color_column_sep'[color of column separators (default: 246 blue)]:str' \
    --color_key_col'[color of key columns (default: 81 cyan)]:str' \
    --color_hidden_col'[color of hidden columns on metasheets (default: 8)]:str' \
    --color_selected_row'[color of selected rows (default: 215 yellow)]:str' \
    --name_joiner'[string to join sheet or column names (default: _)]:str' \
    --value_joiner'[string to join display values (default:  )]:str' \
    --disp_rstatus_fmt'[right-side status format string (default:  {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus} {sheet.options.disp_selected_note}{sheet.nSelectedRows})]:str' \
    --disp_status_fmt'[status line prefix (default: {sheet.shortcut}› {sheet.name}| )]:str' \
    --disp_lstatus_max'[maximum length of left status line (default: 0)]:int' \
    --disp_status_sep'[separator between statuses (default:  │ )]:str' \
    --color_keystrokes'[color of input keystrokes on status line (default: bold 233 black on 110 cyan)]:str' \
    --color_status'[status line color (default: bold black on 110 cyan)]:str' \
    --color_error'[error message color (default: red)]:str' \
    --color_warning'[warning message color (default: yellow)]:str' \
    --color_top_status'[top window status bar color (default: underline)]:str' \
    --color_active_status'[ active window status bar color (default: black on 110 cyan)]:str' \
    --color_inactive_status'[inactive window status bar color (default: 8 on black)]:str' \
    --wrap'[wrap text to fit window width on TextSheet (default: False)]' \
    --save_filetype'[specify default file type to save as (default: tsv)]:str' \
    --profile'[enable profiling on threads (default: False)]' \
    --min_memory_mb'[minimum memory to continue loading and async processing (default: 0)]:int' \
    --color_working'[color of system running smoothly (default: green)]:str' \
    --encoding'[encoding passed to codecs.open (default: utf-8)]:str' \
    --encoding_errors'[encoding_errors passed to codecs.open (default: surrogateescape)]:str' \
    --color_edit_cell'[cell color to use when editing cell (default: white)]:str' \
    --disp_edit_fill'[edit field fill character (default: _)]:str' \
    --disp_unprintable'[substitute character for unprintables (default: ·)]:str' \
    --input_history'[basename of file to store persistent input history (default: )]:str' \
    --bulk_select_clear'[clear selected rows before new bulk selections (default: False)]' \
    --some_selected_rows'[if no rows selected, if True, someSelectedRows returns all rows; if False, fails (default: False)]' \
    {-d,--delimiter}'[field delimiter to use for tsv/usv filetype (default: 	)]:str' \
    --row_delimiter'[row delimiter to use for tsv/usv filetype (default: 
)]:str' \
    --tsv_safe_newline'[replacement for newline character when saving to tsv (default: )]:str' \
    --tsv_safe_tab'[replacement for tab character when saving to tsv (default: )]:str' \
    --visibility'[visibility level (0=low, 1=high) (default: 0)]:int' \
    --default_sample_size'[number of rows to sample for regex.split (0=all) (default: 100)]:int' \
    --fmt_expand_dict'[format str to use for names of columns expanded from dict (colname, key) (default: %s.%s)]:str' \
    --fmt_expand_list'[format str to use for names of columns expanded from list (colname, index) (default: %s[%s])]:str' \
    --json_indent'[indent to use when saving json (default: None)]:NoneType' \
    --json_sort_keys'[sort object keys when saving to json (default: False)]' \
    --default_colname'[column name to use for non-dict rows (default: )]:str' \
    {-f,--filetype}'[specify file type (default: )]:str' \
    {-w,--replay_wait}'[time to wait between replayed commands, in seconds (default: 0.0)]:float' \
    --disp_replay_play'[status indicator for active replay (default: ▶)]:str' \
    --disp_replay_pause'[status indicator for paused replay (default: ‖)]:str' \
    --color_status_replay'[color of replay status indicator (default: green)]:str' \
    --replay_movement'[insert movements during replay (default: False)]' \
    --rowkey_prefix'[string prefix for rowkey in the cmdlog (default: キ)]:str' \
    --cmdlog_histfile'[file to autorecord each cmdlog action to (default: )]:str' \
    {-y,--confirm_overwrite}'[whether to prompt for overwrite confirmation on save (default: True)]' \
    --safe_error'[error string to use while saving (default: #ERR)]:str' \
    --disp_formatter'[formatter to use for display and saving (default: generic)]:str' \
    --clipboard_copy_cmd'[command to copy stdin to system clipboard (default: xclip -selection clipboard -filter)]:str' \
    --clipboard_paste_cmd'[command to send contents of system clipboard to stdout (default: xclip -selection clipboard -o)]:str' \
    --disp_menu'[show menu on top line when not active (default: True)]' \
    --disp_menu_keys'[show keystrokes inline in submenus (default: True)]' \
    --color_menu'[color of menu items in general (default: black on 110 cyan)]:str' \
    --color_menu_active'[color of active menu items (default: 223 yellow on black)]:str' \
    --color_menu_spec'[color of sheet-specific menu items (default: black on 34 green)]:str' \
    --color_menu_help'[color of helpbox (default: black italic on 110 cyan)]:str' \
    --disp_menu_boxchars'[box characters to use for menus (default: ││──┌┐└┘├┤)]:str' \
    --disp_menu_more'[command submenu indicator (default: »)]:str' \
    --disp_menu_push'[indicator if command pushes sheet onto sheet stack (default: ⎘)]:str' \
    --disp_menu_input'[indicator if input required for command (default: …)]:str' \
    --disp_menu_fmt'[right-side menu format string (default: Ctrl+H for help menu)]:str' \
    --fancy_chooser'[a nicer selection interface for aggregators and jointype (default: False)]' \
    --describe_aggrs'[numeric aggregators to calculate on Describe sheet (default: mean stdev)]:str' \
    --disp_histogram'[histogram element character (default: *)]:str' \
    --disp_histolen'[width of histogram column (default: 50)]:int' \
    --histogram_bins'[number of bins for histogram of numeric columns (default: 0)]:int' \
    --numeric_binning'[bin numeric columns into ranges (default: False)]' \
    --regex_flags'[flags to pass to re.compile() \[AILMSUX\] (default: I)]:str' \
    --regex_maxsplit'[maxsplit to pass to regex.split (default: 0)]:int' \
    --show_graph_labels'[show axes and legend on graph (default: True)]' \
    --plot_colors'[list of distinct colors to use for plotting distinct objects (default: green red yellow cyan magenta white 38 136 168)]:str' \
    --disp_canvas_charset'[charset to render 2x4 blocks on canvas (default: ⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿)]:str' \
    --disp_pixel_random'[randomly choose attr from set of pixels instead of most common (default: False)]' \
    --zoom_incr'[amount to multiply current zoomlevel when zooming (default: 2.0)]:float' \
    --color_graph_hidden'[color of legend for hidden attribute (default: 238 blue)]:str' \
    --color_graph_selected'[color of selected graph points (default: bold)]:str' \
    --color_graph_axis'[color for graph axis labels (default: bold)]:str' \
    --motd_url'[source of randomized startup messages (default: https://visidata.org/motd-2.10.2)]:str:_urls' \
    {-r,--dir_recurse}'[walk source path recursively on DirSheet (default: False)]' \
    --dir_hidden'[load hidden files on DirSheet (default: False)]' \
    {-c,--config}'[config file to exec in Python (default: /build/.visidatarc)]:visidata.path.Path:_files' \
    {-p,--play}'[file.vd to replay (default: )]:str:_files' \
    {-b,--batch}'[replay in batch mode (with no interface and all status sent to stdout) (default: False)]' \
    {-o,--output}'[save the final visible sheet to output at the end of replay (default: None)]:NoneType:_files' \
    {-P,--preplay}'[longnames to preplay before replay (default: )]:str' \
    --imports'[imports to preload before .visidatarc (command-line only) (default: plugins)]:str' \
    {-N,--nothing}'[no config, no plugins, nothing extra (default: False)]' \
    --color_add_pending'[color for rows pending add (default: green)]:str' \
    --color_change_pending'[color for cells pending modification (default: reverse yellow)]:str' \
    --color_delete_pending'[color for rows pending delete (default: red)]:str' \
    --unfurl_empty'[if unfurl includes rows for empty containers (default: False)]' \
    --incr_base'[start value for column increments (default: 1.0)]:float' \
    --csv_dialect'[dialect passed to csv.reader (default: excel)]:str' \
    --csv_delimiter'[delimiter passed to csv.reader (default: ,)]:str' \
    --csv_quotechar'[quotechar passed to csv.reader (default: ")]:str' \
    --csv_skipinitialspace'[skipinitialspace passed to csv.reader (default: True)]' \
    --csv_escapechar'[escapechar passed to csv.reader (default: None)]:NoneType' \
    --csv_lineterminator'[lineterminator passed to csv.writer (default: 
)]:str' \
    --safety_first'[sanitize input/output to handle edge cases, with a performance cost (default: False)]' \
    --xlsx_meta_columns'[include columns for cell objects, font colors, and fill colors (default: False)]' \
    --fixed_rows'[number of rows to check for fixed width columns (default: 1000)]:int' \
    --fixed_maxcols'[max number of fixed-width columns to create (0 is no max) (default: 0)]:int' \
    --postgres_schema'[The desired schema for the Postgres database (default: public)]:str' \
    --http_max_next'[max next.url pages to follow in http response (default: 0)]:int' \
    --http_req_headers'[http headers to send to requests (default: {})]:dict' \
    --html_title'[table header when saving to html (default: <h2>{sheet.name}</h2>)]:str' \
    --pcap_internet'[(y/s/n) if save_dot includes all internet hosts separately (y), combined (s), or does not include the internet (n) (default: n)]:str:(y s n)' \
    --graphviz_edge_labels'[whether to include edge labels on graphviz diagrams (default: True)]' \
    --npy_allow_pickle'[numpy allow unpickling objects (unsafe) (default: False)]' \
    --pdf_tables'[parse PDF for tables instead of pages of text (default: False)]' \
    --color_xword_active'[color of active clue (default: green)]:str' \
    --plugins_url'[source of plugins sheet (default: https://visidata.org/plugins/plugins.jsonl)]:str:_urls' \
    --plugins_autoload'[do not autoload plugins if False (default: True)]' \
  '(-p --play -w --replay-wait -b --batch -o --output --replay-movement)*:file:_vd_files'
```
Hi @pigmonkey, thanks for the report.  This feature was added in #1455.  @Freed-Wu, can you figure out what's going wrong, and fix it if needed?

Resolving patch:
diff --git a/dev/zsh-completion.py b/dev/zsh-completion.py
--- a/dev/zsh-completion.py
+++ b/dev/zsh-completion.py
@@ -33,13 +33,14 @@ def generate_completion(opt):
         completion += ":_files"
     elif opt.name in ["plugins_url", "motd_url"]:
         completion += ":_urls"
-    helpstr = opt.helpstr.replace("[", "\\[").replace("]", "\\]")
+    helpstr = opt.helpstr
     selections = pat_select.findall(helpstr)
     if len(selections):
         completion += f":{selections[0].replace('/', ' ')}"
     # TODO: use `zstyle ':completion:*' extra-verbose true`
     # to control the display of default value
     helpstr = helpstr + f" (default: {opt.value})"
+    helpstr = helpstr.replace("[", "\\[").replace("]", "\\]")
     return f"{prefix}'[{helpstr}]{completion}'"
 
 


Test patch: