pericmd 032: More on tab completion (4): Completing paths

There are several kinds of tree-like entities that can be addressed using a path. Filesystem is one, another is a hierarchy of Perl module/package (yet another is Riap URL, which we haven’t really covered in depth, but suffice to say that local Riap URL also map to Perl packages in Perl-based application). A module called Complete::Path is used as a backend to complete all these kinds of path. Each specific type of path is then completed using a higher-level function which uses Complete::Path, but they support the same settings that Complete::Path supports/respects.

Completing filesystem path

Function complete_file in Complete::Util can be used to complete filesystem path (that is, files and directories). There is a filter option which can be a simple string like "d" to only use directories and not files or "x" to only include files (and directories) that have their executable bit set, or as complex as you want since it can also be a coderef.

Let’s try using the function directly. Suppose we have a directory containing these files:

% mkdir tmp
% cd tmp
% mkdir dir1 dir2 Dir3 dir2/dir4
% touch file1 file2-a file2_b File3 dir2/file4 dir2/dir4/file5

Then this code:

% perl -MComplete::Util=complete_file -MData::Dump -E'dd complete_file(word=>"d")'
["Dir3/", "dir1/", "dir2/"]

Note how directories are automatically appended with path separator character (in this case, /). This is for convenience to let you press Tab again directly to dig a filesystem deeper into subdirectories without typing the path separator character manually.

The map_case option. complete_file() also accepts map_case option (will be passed to Complete::Path) which, if turned on (by default it is), will regard underscore (_) and dash (-) as the same character. This is for convenience to let you use dash (which does not require pressing the Shift key on US keyboards) for completing words that might use underscores as separators. Example:

% perl -MComplete::Util=complete_file -MData::Dump -E'dd complete_file(word=>"file2-")'
["file2-a", "file2_b"]

The exp_im_path option. exp_im_path is short for “expand intermediate paths” and is another convenience option which by default is turned on (can be turned off globally by setting environment COMPLETE_OPT_EXP_IM_PATH to 0). This option lets you type only one or a few characters of intermediate paths. For example:

% perl -MComplete::Util=complete_file -MData::Dump -E'dd complete_file(word=>"d/d/f")'

This is akin to a shell wildcard like d*/d*/f*.

Note that by default, expansion is limited only when each intermediate path is only 1 or 2 characters long. As to why this is done, the documentation for Complete module contains the gory details.

The dig_leaf option. This is another convenience option (again, by default is turned on and can be turned off using COMPLETE_OPT_DIG_LEAF=0), which lets Complete::Path dig immediately several levels down if it finds only a single directory in the intermediate paths. For example:

% perl -MComplete::Util=complete_file -MData::Dump -E'dd complete_file(word=>"dir2/")'
["dir2/dir4/file5", "dir2/file4"]

Inside dir2 there is only a single file (file4) and a single subdirectory (dir4). Instead of settling with those, since there is only a single directory, Complete::Path will dig inside dir4 and add the files inside it to the completion answer. If dir4 in turn only contains a single subdirectory, the process is repeated. The effect is, if you have a deep directory structure, e.g. lib/TAP/Parser/Iterator/ and you happen to have only a single file like that and no other intermediate paths, you just have to type “lib” (or even “l/”, due to exp_im_path setting) and voila, the whole path is completed using a single Tab press instead of you having to Tab-Tab-Tab your way into the deep directory.

Completing Perl module names

Perl module names can be completed using the complete_module function in Complete::Module module. Since Perl modules also form a hierarchical namespace, the function also calls Complete::Path::complete_path as its backend and shares the same support for options like exp_im_path and dig_leaf. Let’s see some examples:

% perl -MComplete::Module=complete_module -MData::Dump -E'dd complete_module(word=>"TA")'
  path_sep => "/",
  words => ["TAP/", "TableDef", "Taint/", "Task/Weaken", "tainting"],
% perl -MComplete::Module=complete_module -MData::Dump -E'dd complete_module(word=>"TAP::")'
  path_sep => "::",
  words => [

Wait, why is the path separator still “/”, shouldn’t it be “::” (double colon)? Yes, this is for convenience when doing bash completion. Path separator will only become “::” if the word already contains “::”. Otherwise . See the documentation of Complete::Module (or some of my old blog posts) for more details.

You can force using “::” by specifying path_sep argument:

% perl -MComplete::Module=complete_module -MData::Dump -E'dd complete_module(word=>"TA", path_se=>"::")'
  path_sep => "::",
  words => ["TAP::", "TableDef", "Taint::", "Task::Weaken", "tainting"],

Also, why does instead of array of words, the function returns a hash structure instead? This allows for setting metadata (like the path_sep key above) useful for hints when formatting the completion. The hash completion answer structure will be discussed in the next blog post.

Another convenience that the function provides is some common shortcuts like “dzp” automatically being expanded to “Dist/Zilla/Plugin/”, “pws” to “Pod/Weaver/Section/” and so on. This list of shortcuts can be customized, even from the environment variable.

Let’s see the complete_module() function in action in an actual CLI program. Install App::PMUtils from CPAN. It contains several CLI apps like pmversion or pmpath:

% pmversion t/ansit<tab>
% pmversion Text/ANSITable _

% pmpath dat<tab><tab>
Data/            DateTime         DateTimePP       
Date/            DateTime/        DateTimePPExtra  
% pmpath date/<tab>
% pmpath Date/<tab><tab>
Date/Format     Date/Language   Date/Language/  Date/Parse      
% pmpath Date/f<tab>
% pmpath Date/Format _

% pmversion dzb<tab>
% pmversion Dist/Zilla/PluginBundle/<tab>
% pmversion Dist/Zilla/PluginBundle/a/perla<tab>
% pmversion Dist/Zilla/PluginBundle/Author/PERLANCAR _

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s