bm CLI Completion
One of the features that I would like to include in bm is completion of command-line options and arguments. This feature allows users to search for a bookmark by tabbing through the keyword hierarchy.
I am using the popular optparse-applicative
library to implement option parsing. The library has built-in completion
support, but I discovered that it is quite limited. The API makes it
easy to provide completion for single options and arguments, but it does
not work for the bm
interface where a sequence of arguments
determines what arguments may follow. The library does not provide a way
to override the default implementation, unfortunately.
When I worked on it yesterday, I figured that I would need to rewrite the option parser for the application so that I could cleanly add completion support. I have some other tasks that I want to work on, so I planned on adding completion support in the future, after the first release. My brain would not stop thinking about how to proceed, however, so I ended up implementing it today so that I can clear my mind of the problem.
I decided to continue to use optparse-applicative
and
just handle completion arguments specially. I implemented a
parseArgs
function to parse the arguments:
= do
main Options{..} <- parseArgs
...
The parseArgs
function passes the command-line arguments
to a handleCompletion
function that handles completion
arguments. When completion arguments are processed, the
handleCompletion
function does not return. Otherwise, it
returns an error or the list of arguments for
optparse-applicative
to parse, which parseArgs
handles.
parseArgs :: IO Options
= do
parseArgs <- handleCompletion =<< getArgs
eeas $ case eeas of
OA.handleParseResult Right args -> OA.execParserPure OA.defaultPrefs pinfo args
Left parseError ->
OA.Failure $ OA.parserFailure OA.defaultPrefs pinfo parseError mempty
where
pinfo :: OA.ParserInfo Options
= ... pinfo
The handleCompletion
function checks for completion
options that are built into optparse-applicative
and turns
them into errors. My custom implementation uses different options. When
one of these options is seen, the corresponding implementation function
is called, which does not return. Otherwise, the arguments are returned
for processing by optparse-applicative
like usual.
handleCompletion :: [String] -> IO (Either OA.ParseError [String])
= case find isOAOption optArgs of
handleCompletion args Just arg ->
return . Left $ OA.UnexpectedError arg (OAT.SomeParser options)
Nothing
| "--complete" `elem` optArgs -> handleComplete args
| "--complete-bash" `elem` optArgs -> handleCompleteBash args
| otherwise -> return $ Right args
where
optArgs :: [String]
= takeWhile (/= "--") args
optArgs
isOAOption :: String -> Bool
=
isOAOption &&) <$> ("--" `isPrefixOf`) <*> ("completion" `isInfixOf`) (
I only implemented Bash completion for the first release, though it
should be easy to add completion for other shells in the future. The
--complete-bash
option prints Bash code to enable
completion like optparse-applicative
does. The
--complete
option preforms completion and should work for
all shells. The usage information for both options is handled specially,
since these options are not configured in the
optparse-applicative
Options
.
I have tested the following behavior:
- Completion is supported for all options.
- Typing
bm --<TAB>
gives a list of options. - Partially input long options are completed.
- Short options are handled correctly.
- Typing
- The
--config
argument is completed, showing directories and YAML files.- Since most completion is not of filenames, the filename option is turned off. Unfortunately, this results in the appending of a space when a directory name is selected. I will search for a solution to this issue.
- The
--
argument is correctly handled.- Typing
bm -- --<TAB>
does not provide option completion.
- Typing
- Options and arguments can be interleaved.
- For example, typing
bm nix --trace m<TAB>
completes them
tomanual
using my configuration.
- For example, typing
- If a configuration file is specified using a
--config
option, that configuration file is used for completion of arguments.