aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-11-01 03:43:27 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-11-01 03:43:27 -0700
commit173724b4020cae1f0caacb5116a31aec86f2c2db (patch)
tree72bb511823910a8d5fe0697e33b7648d3827ecef /src
parent64d3927879e9af94eaf1aa7f304ab28f03e7a6bb (diff)
downloadbun-173724b4020cae1f0caacb5116a31aec86f2c2db.tar.gz
bun-173724b4020cae1f0caacb5116a31aec86f2c2db.tar.zst
bun-173724b4020cae1f0caacb5116a31aec86f2c2db.zip
[CLI] Add zsh completions and auto-install completions
Diffstat (limited to 'src')
-rw-r--r--src/bun_js.zig6
-rw-r--r--src/cli.zig247
-rw-r--r--src/cli/install.sh97
-rw-r--r--src/cli/run_command.zig21
-rw-r--r--src/cli/shell_completions.zig16
-rw-r--r--src/exact_size_matcher.zig9
6 files changed, 355 insertions, 41 deletions
diff --git a/src/bun_js.zig b/src/bun_js.zig
index 681aa08ef..e5fb2bcd7 100644
--- a/src/bun_js.zig
+++ b/src/bun_js.zig
@@ -48,7 +48,8 @@ pub const Run = struct {
} else {
run.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
}
-
+ Output.prettyErrorln("\n", .{});
+ Output.flush();
std.os.exit(1);
};
run.vm.bundler.configureDefines() catch |err| {
@@ -57,7 +58,8 @@ pub const Run = struct {
} else {
run.vm.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false) catch {};
}
-
+ Output.prettyErrorln("\n", .{});
+ Output.flush();
std.os.exit(1);
};
diff --git a/src/cli.zig b/src/cli.zig
index 5274deafc..f26bd4b6f 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -613,7 +613,244 @@ pub const Command = struct {
}
pub const InstallCompletionsCommand = struct {
- pub fn exec(ctx: *std.mem.Allocator) !void {}
+ pub fn testPath(completions_dir: string) !std.fs.Dir {}
+ pub fn exec(allocator: *std.mem.Allocator) !void {
+ var shell = ShellCompletions.Shell.unknown;
+ if (std.os.getenv("SHELL")) |shell_name| {
+ shell = ShellCompletions.Shell.fromEnv(@TypeOf(shell_name), shell_name);
+ }
+
+ switch (shell) {
+ .bash => {
+ Output.prettyErrorln("<r><red>error:<r> Bash completions aren't implemented yet, just zsh & fish. A PR is welcome!", .{});
+ std.os.exit(1);
+ },
+ .unknown => {
+ Output.prettyErrorln("<r><red>error:<r> Unknown or unsupported shell. Please set $SHELL to one of zsh, fish, or bash. To manually output completions, run this:\n bun getcompletes", .{});
+ std.os.exit(1);
+ },
+ else => {},
+ }
+
+ var stdout = std.io.getStdOut();
+
+ if (std.os.getenvZ("IS_BUN_AUTO_UPDATE") == null) {
+ if (!stdout.isTty()) {
+ try stdout.writeAll(shell.completions());
+ std.os.exit(0);
+ }
+ }
+
+ var completions_dir: string = "";
+ found: {
+ var cwd_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ var cwd = std.os.getcwd(&cwd_buf) catch {
+ Output.prettyErrorln("<r><red>error<r>: Could not get current working directory", .{});
+ Output.flush();
+ std.os.exit(1);
+ };
+
+ for (std.os.argv) |arg, i| {
+ if (strings.eqlComptime(std.mem.span(arg), "completions")) {
+ if (std.os.argv.len > i + 1) {
+ const input = std.mem.span(std.os.argv[i + 1]);
+
+ if (!std.fs.path.isAbsolute(input)) {
+ completions_dir = resolve_path.joinAbs(
+ cwd,
+ .auto,
+ input,
+ );
+ }
+
+ if (!std.fs.path.isAbsolute(completions_dir)) {
+ Output.prettyErrorln("<r><red>error:<r> Please pass an absolute path. {s} is invalid", .{completions_dir});
+ Output.flush();
+ std.os.exit(1);
+ }
+
+ std.os.access(completions_dir, std.os.O_DIRECTORY | std.os.O_WRONLY) catch |err| {
+ Output.prettyErrorln("<r><red>error:<r> accessing {s} errored {s}", .{ completions_dir, @errorName(err) });
+ Output.flush();
+ std.os.exit(1);
+ };
+
+ break :found;
+ }
+
+ break;
+ }
+ }
+
+ switch (shell) {
+ .fish => {
+ if (std.os.getenvZ("XDG_CONFIG_HOME")) |config_dir| {
+ outer: {
+ var paths = [_]string{ std.mem.span(config_dir), "./fish/completions" };
+ completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
+ std.os.access(completions_dir, std.os.O_DIRECTORY | std.os.O_WRONLY) catch |err| {
+ Output.prettyErrorln("<r><red>{s}:<r> accessing {s}, trying next one.", .{
+ @errorName(err),
+ completions_dir,
+ });
+ Output.flush();
+ break :outer;
+ };
+ break :found;
+ }
+ }
+
+ if (std.os.getenvZ("XDG_DATA_HOME")) |data_dir| {
+ outer: {
+ var paths = [_]string{ std.mem.span(data_dir), "./fish/completions" };
+ completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
+
+ std.os.access(completions_dir, std.os.O_DIRECTORY | std.os.O_WRONLY) catch |err| {
+ Output.prettyErrorln("<r><red>{s}:<r> accessing {s}, trying next one.", .{
+ @errorName(err),
+ completions_dir,
+ });
+ Output.flush();
+ break :outer;
+ };
+
+ break :found;
+ }
+ }
+
+ if (std.os.getenvZ("HOME")) |home_dir| {
+ outer: {
+ var paths = [_]string{ std.mem.span(home_dir), "./.config/fish/completions" };
+ completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
+ std.os.access(completions_dir, std.os.O_DIRECTORY | std.os.O_WRONLY) catch |err| {
+ Output.prettyErrorln("<r><red>{s}:<r> accessing {s}, trying next one.", .{
+ @errorName(err),
+ completions_dir,
+ });
+ Output.flush();
+ break :outer;
+ };
+ break :found;
+ }
+ }
+
+ outer: {
+ if (Environment.isMac) {
+ if (!Environment.isAarch64) {
+ // homebrew fish
+ completions_dir = "/usr/local/share/fish/completions";
+ std.os.access(completions_dir, std.os.O_WRONLY | std.os.O_DIRECTORY) catch |err| {
+ Output.prettyErrorln("<r><red>{s}:<r> accessing {s}, trying next one.", .{
+ @errorName(err),
+ completions_dir,
+ });
+ Output.flush();
+ break :outer;
+ };
+ break :found;
+ } else {
+ // homebrew fish
+ completions_dir = "/opt/homebrew/share/fish/completions";
+ std.os.access(completions_dir, std.os.O_WRONLY | std.os.O_DIRECTORY) catch |err| {
+ Output.prettyErrorln("<r><red>{s}:<r> accessing {s}, trying next one.", .{
+ @errorName(err),
+ completions_dir,
+ });
+ Output.flush();
+ break :outer;
+ };
+ break :found;
+ }
+ }
+ }
+
+ {
+ completions_dir = "/etc/fish/completions";
+ std.os.access(completions_dir, std.os.O_WRONLY | std.os.O_DIRECTORY) catch |err| {
+ Output.prettyErrorln(
+ "<r><red>error:<r> Could not find a directory to install completions in. Please either pipe \"bun completions > /to/a/file\" or pass a directory in like this:\n bun completions /my/completions/dir",
+ .{},
+ );
+ Output.flush();
+ std.os.exit(1);
+ };
+ break :found;
+ }
+ },
+ .zsh => {
+ if (std.os.getenvZ("fpath")) |fpath| {
+ var splitter = std.mem.split(u8, std.mem.span(fpath), " ");
+
+ while (splitter.next()) |dir| {
+ std.os.access(dir, std.os.O_DIRECTORY | std.os.O_WRONLY) catch continue;
+ completions_dir = dir;
+ break :found;
+ }
+ }
+ },
+ .bash => {
+ Output.prettyErrorln("<r><red>error:<r> Please pass a directory or pipe to a file. Not sure where bash completions go.", .{});
+ Output.flush();
+ std.os.exit(1);
+ },
+ else => unreachable,
+ }
+
+ Output.prettyErrorln(
+ "<r><red>error:<r> Could not find a directory to install completions in. Please either pipe \"bun completions > /to/a/file\" or pass a directory in like this:\n bun completions /my/completions/dir",
+ .{},
+ );
+ Output.flush();
+ std.os.exit(1);
+ }
+
+ const filename = switch (shell) {
+ .fish => "bun.fish",
+ .zsh => "_bun",
+ .bash => "_bun.bash",
+ else => unreachable,
+ };
+
+ std.debug.assert(completions_dir.len > 0);
+
+ var output_dir = std.fs.openDirAbsolute(completions_dir, .{ .iterate = true }) catch |err| {
+ Output.prettyErrorln("<r><red>error:<r> Could not open {s} for writing: {s}", .{
+ completions_dir,
+ @errorName(err),
+ });
+ Output.flush();
+ std.os.exit(1);
+ };
+
+ var output_file = output_dir.createFileZ(filename, .{
+ .truncate = true,
+ }) catch |err| {
+ Output.prettyErrorln("<r><red>error:<r> Could not open {s} for writing: {s}", .{
+ filename,
+ @errorName(err),
+ });
+ Output.flush();
+ std.os.exit(1);
+ };
+
+ output_file.writeAll(shell.completions()) catch |err| {
+ Output.prettyErrorln("<r><red>error:<r> Could not write to {s}: {s}", .{
+ filename,
+ @errorName(err),
+ });
+ Output.flush();
+ std.os.exit(1);
+ };
+
+ output_file.close();
+ output_dir.close();
+
+ Output.prettyErrorln("<r><d>Installed completions to {s}/{s}<r>", .{
+ completions_dir,
+ filename,
+ });
+ Output.flush();
+ }
};
const default_completions_list = [_]string{
@@ -651,6 +888,10 @@ pub const Command = struct {
try BuildCommand.exec(ctx);
},
+ .InstallCompletionsCommand => {
+ try InstallCompletionsCommand.exec(allocator);
+ return;
+ },
.GetCompletionsCommand => {
const ctx = try Command.Context.create(allocator, log, .GetCompletionsCommand);
var filter = ctx.positionals;
@@ -677,6 +918,10 @@ pub const Command = struct {
completions = try RunCommand.completions(ctx, null, .bin);
} else if (strings.eqlComptime(filter[0], "r")) {
completions = try RunCommand.completions(ctx, null, .all);
+ } else if (strings.eqlComptime(filter[0], "g")) {
+ completions = try RunCommand.completions(ctx, null, .all_plus_bun_js);
+ } else if (strings.eqlComptime(filter[0], "j")) {
+ completions = try RunCommand.completions(ctx, null, .bun_js);
}
completions.print();
diff --git a/src/cli/install.sh b/src/cli/install.sh
index 3e487f448..e49d69f64 100644
--- a/src/cli/install.sh
+++ b/src/cli/install.sh
@@ -13,41 +13,40 @@ BGreen=''
if test -t 1; then
# Reset
- Color_Off='\033[0m' # Text Reset
+ Color_Off='\033[0m' # Text Reset
# Regular Colors
- Red='\033[0;31m' # Red
- Green='\033[0;32m' # Green
- White='\033[0;37m' # White
+ Red='\033[0;31m' # Red
+ Green='\033[0;32m' # Green
+ White='\033[0;37m' # White
# Bold
- BGreen='\033[1;32m' # Green
- BWhite='\033[1;37m' # White
+ BGreen='\033[1;32m' # Green
+ BWhite='\033[1;37m' # White
fi
-
if ! command -v unzip >/dev/null; then
- echo -e "${Red}error${Color_Off}: unzip is required to install Bun (see: https://github.com/Jarred-Sumner/bun#unzip-is-required)." 1>&2
- exit 1
+ echo -e "${Red}error${Color_Off}: unzip is required to install Bun (see: https://github.com/Jarred-Sumner/bun#unzip-is-required)." 1>&2
+ exit 1
fi
if [ "$OS" = "Windows_NT" ]; then
- echo "error: Please install Bun using Windows Subsystem for Linux."
+ echo "error: Please install Bun using Windows Subsystem for Linux."
exit 1
else
- case $(uname -sm) in
- "Darwin x86_64") target="darwin-x64" ;;
- "Darwin arm64") target="darwin-aarch64" ;;
- *) target="linux-x64" ;;
- esac
+ case $(uname -sm) in
+ "Darwin x86_64") target="darwin-x64" ;;
+ "Darwin arm64") target="darwin-aarch64" ;;
+ *) target="linux-x64" ;;
+ esac
fi
github_repo="https://github.com/Jarred-Sumner/bun-releases-for-updater"
if [ $# -eq 0 ]; then
- bun_uri="$github_repo/releases/latest/download/bun-${target}.zip"
+ bun_uri="$github_repo/releases/latest/download/bun-${target}.zip"
else
- bun_uri="$github_repo/releases/download/${1}/bun-${target}.zip"
+ bun_uri="$github_repo/releases/download/${1}/bun-${target}.zip"
fi
bun_install="${BUN_INSTALL:-$HOME/.bun}"
@@ -55,27 +54,32 @@ bin_dir="$bun_install/bin"
exe="$bin_dir/bun"
if [ ! -d "$bin_dir" ]; then
- mkdir -p "$bin_dir"
+ mkdir -p "$bin_dir"
+
+ if (($?)); then
+ echo -e "${Red}error${Color_Off}: Failed to create install directory $bin_dir" 1>&2
+ exit 1
+ fi
fi
curl --fail --location --progress-bar --output "$exe.zip" "$bun_uri"
-if (( $? )); then
+if (($?)); then
echo -e "${Red}error${Color_Off}: Failed to download Bun from $bun_uri" 1>&2
exit 1
fi
unzip -d "$bin_dir" -q -o "$exe.zip"
-if (( $? )); then
+if (($?)); then
echo -e "${Red}error${Color_Off}: Failed to extract Bun" 1>&2
exit 1
fi
mv "$bin_dir/bun-${target}/bun" "$exe"
-if (( $? )); then
+if (($?)); then
echo -e "${Red}error${Color_Off}: Failed to extract Bun" 1>&2
exit 1
fi
chmod +x "$exe"
-if (( $? )); then
+if (($?)); then
echo -e "${Red}error${Color_Off}: Failed to set permissions on bun executable." 1>&2
exit 1
fi
@@ -85,22 +89,47 @@ rm "$exe.zip"
echo -e "${Green}Bun was installed successfully to ${BGreen}$exe$Color_Off"
if command -v bun --version >/dev/null; then
+ # Install completions, but we don't care if it fails
+ IS_BUN_AUTO_UPDATE="true" $exe completions >/dev/null 2>&1
+
echo "Run 'bun --help' to get started"
exit 0
fi
if test $(basename $SHELL) == "fish"; then
- echo ""
- echo "Manually add the directory to your \$HOME/.config/fish"
- echo ""
- echo -e " $BWhite set -Ux BUN_INSTALL \"$bun_install\"$Color_Off"
- echo -e " $BWhite set -px --path PATH \"$bin_dir\"$Color_Off"
-elif test $(basename $SHELL) == "zsh"; then
- echo ""
- echo "Manually add the directory to your \$HOME/.zshrc (or similar)"
- echo ""
- echo -e " $BWhite export BUN_INSTALL=\"$bun_install\"$Color_Off"
- echo -e " $BWhite export PATH=\"\$BUN_INSTALL/bin:\$PATH\"$Color_Off"
+ # Install completions, but we don't care if it fails
+ IS_BUN_AUTO_UPDATE="true" SHELL="fish" $exe completions >/dev/null 2>&1
+ if test -f $HOME/.config/fish; then
+ echo -e "\nset -Ux BUN_INSTALL \"$bun_install\"\n" >>"$HOME/.config/fish"
+ echo -e "\nset -px --path PATH \"$bin_dir\"\n" >>"$HOME/.config/fish"
+ echo ""
+ echo -e "$BWhite Added \"$bin_dir\" to PATH in \"$HOME/.config/fish\"$Color_Off"
+ else
+ echo ""
+ echo "Manually add the directory to your \$HOME/.config/fish"
+ echo ""
+ echo -e " $BWhite set -Ux BUN_INSTALL \"$bun_install\"$Color_Off"
+ echo -e " $BWhite set -px --path PATH \"$bin_dir\"$Color_Off"
+ fi
+elif
+ test $(basename $SHELL) == "zsh"
+then
+ # Install completions, but we don't care if it fails
+ IS_BUN_AUTO_UPDATE="true" SHELL="zsh" $exe completions >/dev/null 2>&1
+
+ if test -f $HOME/.zshrc; then
+ echo -e "export BUN_INSTALL=\"$bun_install\"" >>"$HOME/.zshrc"
+ echo -e "export PATH=\"\$BUN_INSTALL/bin:\$PATH\"" >>"$HOME/.zshrc"
+ echo ""
+ echo -e "$BWhite Added \"$bin_dir\" to PATH in \"$HOME/.zshrc\"$Color_Off"
+ else
+ echo ""
+ echo "Manually add the directory to your \$HOME/.zshrc (or similar)"
+ echo ""
+ echo -e " $BWhite export BUN_INSTALL=\"$bun_install\"$Color_Off"
+ echo -e " $BWhite export PATH=\"\$BUN_INSTALL/bin:\$PATH\"$Color_Off"
+ fi
+
else
echo ""
echo "Manually add the directory to your \$HOME/.bashrc (or similar)"
@@ -111,4 +140,4 @@ fi
echo ""
echo -e "To get started, run"
echo -e "$BWhite"
-echo -e " bun --help$Color_Off" \ No newline at end of file
+echo -e " bun --help$Color_Off"
diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig
index 60688e5a9..4593f5f6a 100644
--- a/src/cli/run_command.zig
+++ b/src/cli/run_command.zig
@@ -273,6 +273,8 @@ pub const RunCommand = struct {
script,
bin,
all,
+ bun_js,
+ all_plus_bun_js,
};
pub fn completions(ctx: Command.Context, default_completions: ?[]const string, comptime filter: Filter) !ShellCompletions {
@@ -327,7 +329,7 @@ pub const RunCommand = struct {
}
}
- if (filter == Filter.bin or filter == Filter.all) {
+ if (filter == Filter.bin or filter == Filter.all or filter == Filter.all_plus_bun_js) {
for (this_bundler.resolver.binDirs()) |bin_path| {
if (this_bundler.resolver.readDirInfo(bin_path) catch null) |bin_dir| {
if (bin_dir.getEntriesConst()) |entries| {
@@ -360,7 +362,22 @@ pub const RunCommand = struct {
}
}
- if (filter == Filter.script or filter == Filter.all) {
+ if (filter == Filter.all_plus_bun_js or filter == Filter.bun_js) {
+ if (this_bundler.resolver.readDirInfo(this_bundler.fs.top_level_dir) catch null) |dir_info| {
+ if (dir_info.getEntriesConst()) |entries| {
+ var iter = entries.data.iterator();
+
+ while (iter.next()) |entry| {
+ const name = entry.value.base();
+ if (this_bundler.options.loader(std.fs.path.extension(name)).isJavaScriptLike() and !strings.contains(name, ".d.ts") and entry.value.kind(&this_bundler.fs.fs) == .file) {
+ _ = try results.getOrPut(this_bundler.fs.filename_store.append(@TypeOf(name), name) catch continue);
+ }
+ }
+ }
+ }
+ }
+
+ if (filter == Filter.script or filter == Filter.all or filter == Filter.all_plus_bun_js) {
if (root_dir_info.enclosing_package_json) |package_json| {
if (package_json.scripts) |scripts| {
try results.ensureUnusedCapacity(scripts.count());
diff --git a/src/cli/shell_completions.zig b/src/cli/shell_completions.zig
index f64e7befb..295593d49 100644
--- a/src/cli/shell_completions.zig
+++ b/src/cli/shell_completions.zig
@@ -7,6 +7,19 @@ pub const Shell = enum {
zsh,
fish,
+ const bash_completions = @embedFile("../../completions/bun.bash");
+ const zsh_completions = @embedFile("../../completions/bun.zsh");
+ const fish_completions = @embedFile("../../completions/bun.fish");
+
+ pub fn completions(this: Shell) []const u8 {
+ return switch (this) {
+ .bash => std.mem.span(bash_completions),
+ .zsh => std.mem.span(zsh_completions),
+ .fish => std.mem.span(fish_completions),
+ else => "",
+ };
+ }
+
pub fn fromEnv(comptime Type: type, SHELL: Type) Shell {
const basename = std.fs.path.basename(SHELL);
if (strings.eqlComptime(basename, "bash")) {
@@ -30,12 +43,13 @@ pub fn print(this: @This()) void {
var writer = Output.writer();
if (this.commands.len == 0) return;
+ const delimiter = if (this.shell == Shell.fish) " " else "\n";
writer.writeAll(this.commands[0]) catch return;
if (this.commands.len > 1) {
for (this.commands[1..]) |cmd, i| {
- writer.writeAll(" ") catch return;
+ writer.writeAll(delimiter) catch return;
writer.writeAll(cmd) catch return;
}
diff --git a/src/exact_size_matcher.zig b/src/exact_size_matcher.zig
index 3fd6c2056..3c122e9d9 100644
--- a/src/exact_size_matcher.zig
+++ b/src/exact_size_matcher.zig
@@ -4,7 +4,7 @@ pub fn ExactSizeMatcher(comptime max_bytes: usize) type {
const a: u32 = 1000;
switch (max_bytes) {
- 1, 2, 4, 8, 12 => {},
+ 1, 2, 4, 8, 12, 16 => {},
else => {
@compileError("max_bytes must be 1, 2, 4, 8, or 12.");
},
@@ -64,3 +64,10 @@ test "ExactSizeMatcher 4 letter" {
try expect(Four.match(word) == Four.case("from"));
try expect(Four.match(word) != Four.case("fro"));
}
+
+test "ExactSizeMatcher 12 letter" {
+ const Four = ExactSizeMatcher(12);
+ const word = "from";
+ try expect(Four.match(word) == Four.case("from"));
+ try expect(Four.match(word) != Four.case("fro"));
+}