Zigclc a commandline completing program for the zig compiler

I have been working the last few weeks on extending cligen, a generator for commandline parser code. I reported upon at SYCL 2024 Milan

The initial result of this, is zigclc, a program that can do commandline completion for the zig command (and all of the subcommands, options/arguments for those subcommands (that have help)), and does so for the fish, zsh and bash shell.

zigclc based completion can be installed, by invoking a shell specific line (which is displayed for your login shell if you start the program without parameters). This has zigclc generate a small shell function, which itself again invokes zigclc for feeding the shell with (TAB) completion information.

zigclc parses, and caches, the output from zig build --help to get you the steps, project options and system integrations for completion of zig build.

The zigclc program itself compiles with 0.15.2, but the completion has been analsysed from the 0.16 help output (which differs only slightly from the 0.15 output).

I would appreciate any feedback on actual usage, as I usually work with zsh and tend to wrap zig invocations by some other tool.

Some more details:

  • cligen analyse was implemented to parse the output from zig -h and zig subcommand -h for all subcommands, and generates a YAML specification for zigclc’s subcommands, sub-subcommands (steps), options, arguments. Given the emulation of other commands zig implements, this was non-trivial.
  • The plug-in system for cligen that allows global, user-specific and project specific extensions, was expanded. E.g. _ext/zig_complete_build_steps.yaml in the zigclc source tree holds the zig build parsing code that get inserted in zigclc
  • The updates to cligen for creating zigclc have NOT been committed yet (cligen download | SourceForge.net). You would need those to generate zigclc.zig from the YAML specfication. This needs a bit of cleaning up, and some testing/expanding of the non-completion code generated, which I intend to do next.
  • cligen does only invoke zig when it needs to determine the “dynamic” zig build completions (and help). Your shell will still directly invoke zig when you press Return on the commandline.
  • cligen can be used to generate the “front-end” to a Zig commandline program, handing back a struct with variables that are set (or not) from the commandline or a config file. It could also be used for zig that way, but probably should not do so for the LLVM provided subcommands.
  • zigclc help output is somewhat nicer than that of zig itself, as it determines your terminal width and wraps per column (so no help overflow at the beginning of the next line).
  • I briefly looked at nushell and it looks like you can invoke an external command for the completion, so that seems to be a possible target. I also want to see if powershell could do this.
  • You can have different zigclc completers for different zig version.
  • Some zig subcommands have over a thousand possibilities. That doesn’t really help with trying to select what to type next for a completion. It is in principle possible to have different priorities for the options, that will let you specify something even though it is not offered as a completion.
  • There is no real reason why cligen could not be re-implemented in Zig. However cligen uses far more features of YAML than the average program does, so that would require either a full YAML implementation for Zig, or some equally powerful (tags, anchors/aliases, inclusion) alternative data specification.

Working on a Python program that creates a Zig source, that emits a shell scripts that parses completion information generated by the Zig source, can be confusing at times. At times I had problems keeping else if and elif apart, or when to use # and when // for comments, etc.
Curly braces needed doubling when used in Python’s f-string, etc. Fortunately zig will mostly not compile if you generate something incorrectly.

Having both single and double quoted strings in Python helped a bit in preventing multi-backslash insanity while generating Zig code. But still there is a place where to get a colon escaped for the zsh completion (zsh seperates the completion from its help with a colon), required 8 (eight!) backslashes in the string in the Python code for cligen %-)

11 Likes

I like the idea. I’ve gotten pretty far with just fish abbrs:

abbr -a -- zfs 'zig fetch --save'
abbr -a -- zbr 'zig build run'
abbr -a -- zbt 'zig build test'
abbr -a -- zbc 'zig build coverage'
abbr -a -- zbh 'zig build -h | sed "/^General Options/,\\$d"'

Especially the last one :wink:

I would probably install something like this, if I could type or paste one line into my terminal and never have to think about it again.

1 Like

If you have zigclc compiled, and in your path, you only need to provide

_ZIGCLC_CLIGEN_C=sfish _ZIGCLC_CLIGEN_C_TARGET=zig zigclc | source

once in ~/.config/fish/completions/zigclc.fish when using the fish shell. Those instructions are displayed if you run zigclc. Beforehand you may have to remove old completion instructions for zig with complete -e zig (or better (re)move the file in which those other completion instructions are defined).

I tried this out with your zbr abbreviation/alias and fish seems to expand that, and then still call the completion (for the options) provided by zigclc.

cligen allows you to add an alternative name in the spec for a subcommand. It then generates code that checks for that alternative name being the last part of the first invocation parameter. And when it finds it, then it acts as if the subcommand was selected. That means when then you symlink ar to zig ( with ln -s zig ar ), invoking ar ... will be the same as invoking zig ar .... Currently, since zigclc only does the completion for zig (and not the invocation of the completed commandline), that doesn’t work for zig ( i.e. that behaviour would have to be build into zig, which of course can be relatively easily done without using cligen as well).

1 Like

Oh. I was unclear on something, I thought this would involve doing various surgical operations on your repository using cligen, which is less attractive than running zig build and putting a magic string in a config directory I already have.

That I’m pretty likely to do, when I get a moment.

FYI I updated the code to compile with 0.16.0 (there is also a source file for 0.15.2). This was done by changing the generation program to generate either 0.16 or 0.15 code, and having a handful version specific structs.
(This is not so interesting for zigclc, but it may be interesting for anyone wanting a command-line parser with TAB completion for a not yet converted 0.15 program).
Support for completion is still for fish, zsh and bash only.