aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-10-31 21:02:15 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-10-31 21:02:15 -0700
commit64d3927879e9af94eaf1aa7f304ab28f03e7a6bb (patch)
tree102278762c1741416e064415aa7e4e2c1a6325cf
parent49ad70b11ca03739887e7b65835d3aab6bb652e7 (diff)
downloadbun-64d3927879e9af94eaf1aa7f304ab28f03e7a6bb.tar.gz
bun-64d3927879e9af94eaf1aa7f304ab28f03e7a6bb.tar.zst
bun-64d3927879e9af94eaf1aa7f304ab28f03e7a6bb.zip
[CLI] Add completions for Fish
-rw-r--r--completions/bun.fish49
-rw-r--r--src/cli.zig58
-rw-r--r--src/cli/run_command.zig112
-rw-r--r--src/cli/shell_completions.zig43
4 files changed, 259 insertions, 3 deletions
diff --git a/completions/bun.fish b/completions/bun.fish
new file mode 100644
index 000000000..d3484a78c
--- /dev/null
+++ b/completions/bun.fish
@@ -0,0 +1,49 @@
+function __fish__get_bun_bins
+ string split ' ' (bun getcompletes b)
+end
+
+function __fish__get_bun_scripts
+ string split ' ' (bun getcompletes s)
+end
+
+
+set -l bun_builtin_cmds dev create help bun upgrade discord run
+set -l bun_builtin_cmds_without_run dev create help bun upgrade discord
+set -l bun_builtin_cmds_without_create dev help bun upgrade discord run
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_run; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_use_subcommand" -a '(__fish__get_bun_bins)' -d 'package bin'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_run; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_use_subcommand" -a '(__fish__get_bun_scripts)' -d 'script'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_run; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_seen_subcommand_from run" -a '(__fish__get_bun_bins)' -d 'package bin'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_run; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_seen_subcommand_from run" -a '(__fish__get_bun_scripts)' -d 'script'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts) and __fish_use_subcommand" -a 'run' -d 'Run a script or bin'
+
+complete -c bun -n "not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts);" -F -s 'u' -l 'origin' -r -d 'Server URL. Rewrites import paths'
+
+complete -c bun -n "not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts);" -F -s 'p' -l 'port' -r -d 'Port number to start server from'
+
+complete -c bun -n "not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts);" -F -s 'd' -l 'define' -r -d 'Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\"'
+
+complete -c bun -n "not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts);" -F -s 'e' -l 'external' -r -d 'Exclude module from transpilation (can use * wildcards). ex: -e react'
+
+complete -c bun -n "not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts);" -F -l 'use' -r -d 'Use a framework (ex: next)'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts) and __fish_use_subcommand" -a 'dev' -d 'Start dev server'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_use_subcommand" -F -a 'create' -d 'Create a new project from a template'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_create next react; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_seen_subcommand_from create" -f -a 'next' -d 'Create a new Next.js project'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_create next react; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_seen_subcommand_from create" -f -a 'react' -d 'Create a new React project'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_create; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_seen_subcommand_from create next" -F
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_use_subcommand" -x -a 'upgrade' -d 'Upgrade Bun to the latest version'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_use_subcommand" -x -a '--help' -d 'See all commands and flags'
+
+complete -c bun -n "not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_use_subcommand" -x -a 'discord' -d 'Open Bun\'s Discord server'
+
diff --git a/src/cli.zig b/src/cli.zig
index 86e9409fb..5274deafc 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -36,6 +36,7 @@ const CreateCommand = @import("./cli/create_command.zig").CreateCommand;
const CreateListExamplesCommand = @import("./cli/create_command.zig").CreateListExamplesCommand;
const RunCommand = @import("./cli/run_command.zig").RunCommand;
const UpgradeCommand = @import("./cli/upgrade_command.zig").UpgradeCommand;
+const ShellCompletions = @import("./cli/shell_completions.zig");
var start_time: i128 = undefined;
pub const Cli = struct {
@@ -145,8 +146,8 @@ pub const Arguments = struct {
clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
clap.parseParam("-i, --inject <STR>... Inject module at the top of every file") catch unreachable,
clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: jsx, js, json, tsx, ts, css") catch unreachable,
- clap.parseParam("--origin <STR> Rewrite import paths to start with --origin. Default: \"\"") catch unreachable,
- clap.parseParam("--port <STR> Port to serve Bun's dev server on. Default: \"3000\"") catch unreachable,
+ clap.parseParam("-u, --origin <STR> Rewrite import URLs to start with --origin. Default: \"\"") catch unreachable,
+ clap.parseParam("-p, --port <STR> Port to serve Bun's dev server on. Default: \"3000\"") catch unreachable,
clap.parseParam("--silent Don't repeat the command for bun run") catch unreachable,
// clap.parseParam("-o, --outdir <STR> Save output to directory (default: \"out\" if none provided and multiple entry points passed)") catch unreachable,
@@ -463,6 +464,7 @@ const HelpCommand = struct {
\\> <r> <b><green>run <r><d> test <r> Run a package.json script or executable<r>
\\> <r> <b><cyan>create <r><d>next ./app<r> Start a new project from a template <d>(shorthand: c)<r>
\\> <r> <b><blue>upgrade <r> Get the latest version of Bun
+ \\> <r> <b><d>completions<r> Install shell completions for tab-completion
\\> <r> <b><d>discord <r> Open Bun's Discord server
\\> <r> <b><d>help <r> Print this help menu
\\
@@ -576,7 +578,7 @@ pub const Command = struct {
}
const first_arg_name = std.mem.span(next_arg);
- const RootCommandMatcher = strings.ExactSizeMatcher(8);
+ const RootCommandMatcher = strings.ExactSizeMatcher(16);
if (comptime FeatureFlags.dev_only) {
return switch (RootCommandMatcher.match(first_arg_name)) {
@@ -584,6 +586,8 @@ pub const Command = struct {
RootCommandMatcher.case("bun") => .BunCommand,
RootCommandMatcher.case("discord") => .DiscordCommand,
RootCommandMatcher.case("upgrade") => .UpgradeCommand,
+ RootCommandMatcher.case("completions") => .InstallCompletionsCommand,
+ RootCommandMatcher.case("getcompletes") => .GetCompletionsCommand,
RootCommandMatcher.case("c"), RootCommandMatcher.case("create") => .CreateCommand,
RootCommandMatcher.case("b"), RootCommandMatcher.case("build") => .BuildCommand,
@@ -608,6 +612,20 @@ pub const Command = struct {
}
}
+ pub const InstallCompletionsCommand = struct {
+ pub fn exec(ctx: *std.mem.Allocator) !void {}
+ };
+
+ const default_completions_list = [_]string{
+ // "build",
+ "run",
+ "dev",
+ "create",
+ "bun",
+ "upgrade",
+ "discord",
+ };
+
pub fn start(allocator: *std.mem.Allocator, log: *logger.Log) !void {
const tag = which(allocator);
switch (tag) {
@@ -633,6 +651,38 @@ pub const Command = struct {
try BuildCommand.exec(ctx);
},
+ .GetCompletionsCommand => {
+ const ctx = try Command.Context.create(allocator, log, .GetCompletionsCommand);
+ var filter = ctx.positionals;
+
+ for (filter) |item, i| {
+ if (strings.eqlComptime(item, "getcompletes")) {
+ if (i + 1 < filter.len) {
+ filter = filter[i + 1 ..];
+ } else {
+ filter = &[_]string{};
+ }
+
+ break;
+ }
+ }
+
+ var completions = ShellCompletions{};
+
+ if (filter.len == 0) {
+ completions = try RunCommand.completions(ctx, &default_completions_list, .all);
+ } else if (strings.eqlComptime(filter[0], "s")) {
+ completions = try RunCommand.completions(ctx, null, .script);
+ } else if (strings.eqlComptime(filter[0], "b")) {
+ completions = try RunCommand.completions(ctx, null, .bin);
+ } else if (strings.eqlComptime(filter[0], "r")) {
+ completions = try RunCommand.completions(ctx, null, .all);
+ }
+
+ completions.print();
+
+ return;
+ },
.CreateCommand => {
const ctx = try Command.Context.create(allocator, log, .CreateCommand);
var positionals: [2]string = undefined;
@@ -715,5 +765,7 @@ pub const Command = struct {
HelpCommand,
CreateCommand,
UpgradeCommand,
+ InstallCompletionsCommand,
+ GetCompletionsCommand,
};
};
diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig
index fba8296a8..60688e5a9 100644
--- a/src/cli/run_command.zig
+++ b/src/cli/run_command.zig
@@ -33,6 +33,8 @@ const NpmArgs = struct {
const yarn_commands: []u64 = @import("./list-of-yarn-commands.zig").all_yarn_commands;
+const ShellCompletions = @import("./shell_completions.zig");
+
pub const RunCommand = struct {
const shells_to_search = &[_]string{
"bash",
@@ -267,6 +269,116 @@ pub const RunCommand = struct {
this_bundler.configureLinker();
}
+ pub const Filter = enum {
+ script,
+ bin,
+ all,
+ };
+
+ pub fn completions(ctx: Command.Context, default_completions: ?[]const string, comptime filter: Filter) !ShellCompletions {
+ var shell_out = ShellCompletions{};
+
+ if (default_completions) |defaults| {
+ shell_out.commands = defaults;
+ }
+
+ var args = ctx.args;
+ args.node_modules_bundle_path = null;
+ args.node_modules_bundle_path_server = null;
+ args.generate_node_module_bundle = false;
+
+ var this_bundler = bundler.Bundler.init(ctx.allocator, ctx.log, args, null, null) catch return shell_out;
+ this_bundler.options.env.behavior = Api.DotEnvBehavior.load_all;
+ this_bundler.options.env.prefix = "";
+ this_bundler.env.quiet = true;
+
+ this_bundler.resolver.care_about_bin_folder = true;
+ this_bundler.resolver.care_about_scripts = true;
+ defer {
+ this_bundler.resolver.care_about_bin_folder = false;
+ this_bundler.resolver.care_about_scripts = false;
+ }
+ this_bundler.configureLinker();
+
+ var root_dir_info = (this_bundler.resolver.readDirInfo(this_bundler.fs.top_level_dir) catch null) orelse return shell_out;
+
+ {
+ this_bundler.env.loadProcess();
+
+ if (this_bundler.env.map.get("NODE_ENV")) |node_env| {
+ if (strings.eqlComptime(node_env, "production")) {
+ this_bundler.options.production = true;
+ }
+ }
+ }
+
+ const ResultList = std.StringArrayHashMap(void);
+
+ if (this_bundler.env.map.get("SHELL")) |shell| {
+ shell_out.shell = ShellCompletions.Shell.fromEnv(@TypeOf(shell), shell);
+ }
+
+ var results = ResultList.init(ctx.allocator);
+
+ if (default_completions) |defaults| {
+ try results.ensureUnusedCapacity(defaults.len);
+ for (defaults) |item| {
+ _ = results.getOrPutAssumeCapacity(item);
+ }
+ }
+
+ if (filter == Filter.bin or filter == Filter.all) {
+ for (this_bundler.resolver.binDirs()) |bin_path| {
+ if (this_bundler.resolver.readDirInfo(bin_path) catch null) |bin_dir| {
+ if (bin_dir.getEntriesConst()) |entries| {
+ var iter = entries.data.iterator();
+ var has_copied = false;
+ var path_slice: string = "";
+ var dir_slice: string = "";
+ while (iter.next()) |entry| {
+ if (entry.value.kind(&this_bundler.fs.fs) == .file) {
+ if (!has_copied) {
+ std.mem.copy(u8, &path_buf, entry.value.dir);
+ dir_slice = path_buf[0..entry.value.dir.len];
+ if (!strings.endsWithChar(entry.value.dir, std.fs.path.sep)) {
+ dir_slice = path_buf[0 .. entry.value.dir.len + 1];
+ }
+ has_copied = true;
+ }
+
+ const base = entry.value.base();
+ std.mem.copy(u8, path_buf[dir_slice.len..], base);
+ path_buf[dir_slice.len + base.len] = 0;
+ var slice = path_buf[0 .. dir_slice.len + base.len :0];
+ std.os.accessZ(slice, std.os.X_OK) catch continue;
+ // we need to dupe because the string pay point to a pointer that only exists in the current scope
+ _ = try results.getOrPut(this_bundler.fs.filename_store.append(@TypeOf(base), base) catch continue);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (filter == Filter.script or filter == Filter.all) {
+ if (root_dir_info.enclosing_package_json) |package_json| {
+ if (package_json.scripts) |scripts| {
+ try results.ensureUnusedCapacity(scripts.count());
+ for (scripts.keys()) |key| {
+ _ = results.getOrPutAssumeCapacity(key);
+ }
+ }
+ }
+ }
+
+ var all_keys = results.keys();
+
+ strings.sortAsc(all_keys);
+ shell_out.commands = all_keys;
+
+ return shell_out;
+ }
+
pub fn exec(ctx: Command.Context, comptime bin_dirs_only: bool, comptime log_errors: bool) !bool {
// Step 1. Figure out what we're trying to run
var positionals = ctx.positionals;
diff --git a/src/cli/shell_completions.zig b/src/cli/shell_completions.zig
new file mode 100644
index 000000000..f64e7befb
--- /dev/null
+++ b/src/cli/shell_completions.zig
@@ -0,0 +1,43 @@
+const std = @import("std");
+usingnamespace @import("../global.zig");
+
+pub const Shell = enum {
+ unknown,
+ bash,
+ zsh,
+ fish,
+
+ pub fn fromEnv(comptime Type: type, SHELL: Type) Shell {
+ const basename = std.fs.path.basename(SHELL);
+ if (strings.eqlComptime(basename, "bash")) {
+ return Shell.bash;
+ } else if (strings.eqlComptime(basename, "zsh")) {
+ return Shell.zsh;
+ } else if (strings.eqlComptime(basename, "fish")) {
+ return Shell.fish;
+ } else {
+ return Shell.unknown;
+ }
+ }
+};
+
+commands: []const []const u8 = &[_][]u8{},
+flags: []const []const u8 = &[_][]u8{},
+shell: Shell = Shell.unknown,
+
+pub fn print(this: @This()) void {
+ defer Output.flush();
+ var writer = Output.writer();
+
+ if (this.commands.len == 0) return;
+
+ writer.writeAll(this.commands[0]) catch return;
+
+ if (this.commands.len > 1) {
+ for (this.commands[1..]) |cmd, i| {
+ writer.writeAll(" ") catch return;
+
+ writer.writeAll(cmd) catch return;
+ }
+ }
+}