11

I would like to slightly extend a zsh completion function.

I would like to avoid putting the complete function body into my homedir with only one line changed. Instead I would like to intercept it's call and then call the original function myself. In quasi code:

<make sure _the_original_function is loaded>
_backup_of_original_function=_the_original_function
_the_original_function() {
 _arguments "-superduper[that one option I always need]"
 _backup_of_originial_function $@
}

In my concrete case, I have an cmake property in practically all my projects such that I would like to modify the cmake completion. Not having that option in the completion would annoy me much more than having it in projects where the option does not belong. So instead of copying _cmake somewhere I would just redefine _cmake_define_common_property_names() in my .zshrc:

_cmake_define_common_property_names() {
 local properties; properties=(
 'MY_GREAT_OPTION:be awesome'
 ) 
 _describe -t 'my-property-names' 'my property name' properties $@
 **call original _cmake_define_common_property_names here
} 

So what's missing is loading _cmake_define_common_property_names and assigning a new function name to it.

From here I tried

autoload -Uz +X _cmake_define_common_property_names

but this fails (the function is not defined within its own file, but rather in _cmake.

NB: I don't assign a new function name to avoid having to modify the place from where the function gets called in its original version.

What works partially is autoload -Uz +X _cmake BUT this only ensures that _cmake is loaded (which I verify by calling functions _cmake). It does not load the helper function _cmake_define_common_property_names.

So my two questions are

  1. how do I load a function from within an $fpath file
  2. Once I have a function loaded. How do I copy it in a script / Assign a new function name?
Gilles 'SO- stop being evil'
865k204 gold badges1.8k silver badges2.3k bronze badges
asked Jun 15, 2018 at 16:48

1 Answer 1

15

How to patch a function

The code of a function is stored in the associative array functions. That's the source code with normalized whitespace and no comments (zsh has done lexical analysis and pretty-prints the tokens). You can change the code of a function by modifying the entry of the functions array. For example, to add extra code at the beginning:

functions[_cmake_define_common_property_names]="
 ... # your extra code here
 $functions[_cmake_define_common_property_names]"

If the changes you want to make only involve running code before and after the original code (or more generally around, e.g. to put it inside a conditional), you can copy the function to a new name and redefine the function to call that new name. Since zsh 5.8, you can do this easily with functions -c.

functions -c _cmake_define_common_property_names _cmake_define_common_property_names_orig
_cmake_define_common_property_names () {
 ...
 _cmake_define_common_property_names_orig "$@"
}

Alternatively, in any version of zsh, you can also use the functions array to copy a function to another name.

functions[_cmake_define_common_property_names_orig]=$functions[_cmake_define_common_property_names]
_cmake_define_common_property_names () {
 ...
 _cmake_define_common_property_names_orig "$@"
}

Loading all the functions

The only sure-fire way to load all the functions from a file made for autoload is to execute the function, if you can arrange to execute it with no side effects. For a completion function, just run the function with errors redirected to the bit bucket. It'll do nothing but complain that it isn't being executed in a completion context.

_cmake 2>/dev/null
# Now _cmake_xxx is defined

The reason autoload -Uz +X _cmake doesn't work is that the definitions of the auxiliary functions are in the _cmake function itself.

% echo $functions[_cmake]
builtin autoload -XU
% autoload -Uz +X _cmake
% echo $functions[_cmake]
...
 (( $+functions[_cmake_define_property_names] )) || _cmake_define_property_names () {
...
}
...
 local cmake_command_actions
 cmake_command_actions=('-E[CMake command mode]:*:command') 
 _cmake_command () {
 _arguments -C -s - command "$cmake_command_actions[@]"
 }
 local cmake_suggest_build
 cmake_suggest_build=('--build[build]') 
 if [ $CURRENT -eq 2 ]
 then
 _arguments -C -s - help "$cmake_help_actions[@]" - command "$cmake_command_actions[@]" - build_opts "$cmake_build_options[@]" - build_cmds "$cmake_suggest_build[@]" && return 0
 elif [[ $words[2] = --help* ]]
 then
 _cmake_help
 elif [[ $words[2] != -E ]]
 then
 _cmake_on_build
 else
 _cmake_command
 fi

If you really don't want to execute the toplevel function, you have several choices:

  • Patch the definition of _cmake_define_common_property_names inside the definition of _cmake.

  • Extract the definition of _cmake_define_common_property_names from the definition of _cmake and use that to define _cmake_define_common_property_names_orig or _cmake_define_common_property_names.

  • Patch the definition of _cmake to remove the parts other than the function definitions, then execute it. It isn't really workable with _cmake, but some other completion functions are better structured. For example _cvs consists purely of conditional function definitions (e.g. (( $+functions[_cvs_command] )) || _cvs_command () { ... }), a definition of the titular function, and a call of the titular function as the very last thing, thus you can define all the function but not execute anything by removing the last line.

     autoload -Uz +X _cvs
     functions[_cvs]=${functions_cvs%'$\n'*}
     _cvs
     # Patch auxiliary functions here
    
answered Jun 16, 2018 at 9:27
3
  • Thanks! I may have put the things together too carelessly but got a few errors _tags:comptags:60: nesting level too deep. Which I fixed by putting the creation of the _orig functions into an "only if backup not yet made" block: (( $+functions[_cmake_define_property_names_orig] )) || functions[_cmake_define_property_names_orig]=$functions[_cmake_define_property_names] Commented Jun 17, 2018 at 14:15
  • functions -c <old> <new> is a slightly more compact way to copy a function. Commented Apr 16, 2023 at 16:05
  • 1
    @ak2 Indeed, thanks (it didn't exist when I posted this answer). Commented Apr 16, 2023 at 19:28

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.