Skip to content

Commit be79513

Browse files
committed
feat(cli): add shell completion command (bash)
- introduce 'vix completion' command - generate bash completion script from dispatcher entries - remove dependency on bash-completion (_init_completion) - enable tab completion for commands and 'vix help <cmd>' - integrate completion into CLI help output Usage: source <(vix completion bash)
1 parent e0c260b commit be79513

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
*
3+
* @file CompletionCommand.hpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*
13+
*/
14+
#ifndef VIX_COMPLETION_COMMAND_HPP
15+
#define VIX_COMPLETION_COMMAND_HPP
16+
17+
#include <vector>
18+
#include <string>
19+
20+
namespace vix::commands
21+
{
22+
struct CompletionCommand
23+
{
24+
static int run(const std::vector<std::string> &args);
25+
static int help();
26+
};
27+
}
28+
29+
#endif

src/CLI.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <vix/cli/commands/UpdateCommand.hpp>
4343
#include <vix/cli/commands/OutdatedCommand.hpp>
4444
#include <vix/cli/commands/MakeCommand.hpp>
45+
#include <vix/cli/commands/CompletionCommand.hpp>
4546
#include <vix/utils/Env.hpp>
4647
#include <vix/cli/Style.hpp>
4748
#include <vix/utils/Logger.hpp>
@@ -345,14 +346,17 @@ namespace vix
345346
{
346347
if (!dispatcher.has(cmd))
347348
{
348-
std::cerr << "error: unrecognized subcommand\n\n";
349+
vix::cli::util::err_line(
350+
std::cerr,
351+
"unrecognized subcommand " + vix::cli::util::quote(cmd));
349352

350353
auto suggestion = find_closest_command(cmd, dispatcher.entries());
351354

352355
if (suggestion.has_value())
353356
{
354-
std::cerr << " tip: a similar command exists: '"
355-
<< suggestion.value() << "'\n";
357+
vix::cli::util::tip_line(
358+
std::cerr,
359+
"A similar command exists: " + vix::cli::util::quote(suggestion.value()));
356360
}
357361

358362
return 1;
@@ -371,14 +375,17 @@ namespace vix
371375

372376
if (!dispatcher.has(cmd))
373377
{
374-
std::cerr << "error: unrecognized subcommand\n\n";
378+
vix::cli::util::err_line(
379+
std::cerr,
380+
"unrecognized subcommand " + vix::cli::util::quote(cmd));
375381

376382
auto suggestion = find_closest_command(cmd, dispatcher.entries());
377383

378384
if (suggestion.has_value())
379385
{
380-
std::cerr << " tip: a similar command exists: '"
381-
<< suggestion.value() << "'\n";
386+
vix::cli::util::tip_line(
387+
std::cerr,
388+
"A similar command exists: " + vix::cli::util::quote(suggestion.value()));
382389
}
383390

384391
return 1;
@@ -444,6 +451,8 @@ namespace vix
444451
return commands::StoreCommand::help();
445452
if (cmd == "publish")
446453
return commands::PublishCommand::help();
454+
if (cmd == "completion")
455+
return commands::CompletionCommand::help();
447456
if (cmd == "deps")
448457
{
449458
vix::cli::util::warn_line(std::cout, "'vix deps' is deprecated, use 'vix install'");
@@ -552,6 +561,7 @@ namespace vix
552561
// Help
553562
out << indent(2) << "Help:\n";
554563
out << indent(3) << "help [command] Show command help\n";
564+
out << indent(3) << "completion Generate shell completion script\n";
555565
out << indent(3) << "version Show version\n\n";
556566

557567
out << indent(1) << "Global options:\n";

src/commands/CompletionCommand.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
*
3+
* @file CompletionCommand.hpp
4+
* @author Gaspard Kirira
5+
*
6+
* Copyright 2025, Gaspard Kirira. All rights reserved.
7+
* https://github.com/vixcpp/vix
8+
* Use of this source code is governed by a MIT license
9+
* that can be found in the License file.
10+
*
11+
* Vix.cpp
12+
*
13+
*/
14+
#include <vix/cli/commands/CompletionCommand.hpp>
15+
#include <vix/cli/commands/Dispatch.hpp>
16+
17+
#include <iostream>
18+
#include <vector>
19+
#include <string>
20+
#include <algorithm>
21+
22+
namespace vix::commands
23+
{
24+
namespace
25+
{
26+
std::vector<std::string> collect_commands()
27+
{
28+
std::vector<std::string> names;
29+
30+
const auto &entries = vix::cli::dispatch::global().entries();
31+
names.reserve(entries.size());
32+
33+
for (const auto &[name, _] : entries)
34+
{
35+
names.push_back(name);
36+
}
37+
38+
std::sort(names.begin(), names.end());
39+
return names;
40+
}
41+
42+
int print_bash()
43+
{
44+
const auto commands = collect_commands();
45+
46+
std::cout << "_vix_completions()\n";
47+
std::cout << "{\n";
48+
std::cout << " local cur prev\n";
49+
std::cout << " cur=\"${COMP_WORDS[COMP_CWORD]}\"\n";
50+
std::cout << " prev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n\n";
51+
52+
std::cout << " local commands=\"";
53+
for (size_t i = 0; i < commands.size(); ++i)
54+
{
55+
if (i != 0)
56+
std::cout << " ";
57+
std::cout << commands[i];
58+
}
59+
std::cout << "\"\n\n";
60+
61+
std::cout << " if [[ ${COMP_CWORD} -eq 1 ]]; then\n";
62+
std::cout << " COMPREPLY=( $(compgen -W \"$commands\" -- \"$cur\") )\n";
63+
std::cout << " return 0\n";
64+
std::cout << " fi\n\n";
65+
66+
std::cout << " case \"${COMP_WORDS[1]}\" in\n";
67+
std::cout << " help)\n";
68+
std::cout << " COMPREPLY=( $(compgen -W \"$commands\" -- \"$cur\") )\n";
69+
std::cout << " return 0\n";
70+
std::cout << " ;;\n";
71+
std::cout << " esac\n\n";
72+
73+
std::cout << " COMPREPLY=()\n";
74+
std::cout << "}\n\n";
75+
76+
std::cout << "complete -o default -F _vix_completions vix\n";
77+
return 0;
78+
}
79+
}
80+
81+
int CompletionCommand::run(const std::vector<std::string> &args)
82+
{
83+
if (args.empty() || args[0] == "bash")
84+
return print_bash();
85+
86+
if (args[0] == "--help" || args[0] == "-h")
87+
return help();
88+
89+
std::cerr << "vix completion: unsupported shell '" << args[0] << "'\n";
90+
std::cerr << "Supported: bash\n";
91+
return 1;
92+
}
93+
94+
int CompletionCommand::help()
95+
{
96+
std::cout
97+
<< "vix completion [bash]\n\n"
98+
<< "Generate shell completion script.\n\n"
99+
<< "Examples:\n"
100+
<< " vix completion bash > ~/.vix-completion.bash\n"
101+
<< " source ~/.vix-completion.bash\n";
102+
return 0;
103+
}
104+
}

src/commands/Dispatch.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include <vix/cli/commands/OutdatedCommand.hpp>
4242
#include <vix/cli/commands/MakeCommand.hpp>
4343
#include <vix/cli/util/Ui.hpp>
44+
#include <vix/cli/commands/CompletionCommand.hpp>
4445

4546
#include <stdexcept>
4647

@@ -62,6 +63,14 @@ namespace vix::cli::dispatch
6263
[]()
6364
{ return vix::commands::NewCommand::help(); }});
6465

66+
add({"completion",
67+
"Info",
68+
"Generate shell completion script",
69+
[](const Args &a)
70+
{ return vix::commands::CompletionCommand::run(a); },
71+
[]()
72+
{ return vix::commands::CompletionCommand::help(); }});
73+
6574
add({"build",
6675
"Project",
6776
"Configure + build project",

0 commit comments

Comments
 (0)