diff options
author | 2021-10-31 21:02:15 -0700 | |
---|---|---|
committer | 2021-10-31 21:02:15 -0700 | |
commit | 64d3927879e9af94eaf1aa7f304ab28f03e7a6bb (patch) | |
tree | 102278762c1741416e064415aa7e4e2c1a6325cf /src | |
parent | 49ad70b11ca03739887e7b65835d3aab6bb652e7 (diff) | |
download | bun-64d3927879e9af94eaf1aa7f304ab28f03e7a6bb.tar.gz bun-64d3927879e9af94eaf1aa7f304ab28f03e7a6bb.tar.zst bun-64d3927879e9af94eaf1aa7f304ab28f03e7a6bb.zip |
[CLI] Add completions for Fish
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.zig | 58 | ||||
-rw-r--r-- | src/cli/run_command.zig | 112 | ||||
-rw-r--r-- | src/cli/shell_completions.zig | 43 |
3 files changed, 210 insertions, 3 deletions
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; + } + } +} |