├── fish_theme ├── conf.d │ ├── fish_greeting.fish │ ├── fish_title.fish │ ├── fish_prompt.fish │ └── fish_right_prompt.fish ├── LICENSE └── README.md ├── bruh.zsh-theme └── src ├── main.zig ├── cwd.zig └── time_format.zig /fish_theme/conf.d/fish_greeting.fish: -------------------------------------------------------------------------------- 1 | function fish_greeting 2 | end 3 | -------------------------------------------------------------------------------- /fish_theme/conf.d/fish_title.fish: -------------------------------------------------------------------------------- 1 | function fish_title 2 | # Customize terminal window title 3 | end 4 | -------------------------------------------------------------------------------- /fish_theme/conf.d/fish_prompt.fish: -------------------------------------------------------------------------------- 1 | function fish_prompt 2 | set -l last_status $status 3 | 4 | echo -en " " 5 | 6 | if test -n "$SSH_CONNECTION" 7 | echo -en (whoami)"@"(hostname)" " 8 | end 9 | 10 | if [ $last_status != 0 ] 11 | set_color red 12 | echo -en "$last_status " 13 | set_color normal 14 | end 15 | 16 | if test -n "$duration" 17 | and test $duration -ne 0 18 | set_color --bold green 19 | echo -en (bruh tf $duration)" " 20 | set_color normal 21 | end 22 | 23 | echo -en 24 | 25 | echo -en (bruh cwd)" " 26 | end 27 | 28 | function fish_command_timer_postexec -e fish_postexec 29 | set -g duration (math -s3 "$CMD_DURATION/1000") 30 | end 31 | 32 | function fish_mode_prompt 33 | switch $fish_bind_mode 34 | case default 35 | set_color --bold blue 36 | echo -ne ' n' 37 | set_color normal 38 | case visual 39 | set_color --bold purple 40 | echo -ne ' v' 41 | set_color normal 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /fish_theme/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Haze Booth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fish_theme/conf.d/fish_right_prompt.fish: -------------------------------------------------------------------------------- 1 | function fish_right_prompt 2 | show_git_info 3 | end 4 | 5 | function show_dirty_sign 6 | set -l have_changes (git diff-index --quiet HEAD -- 2>/dev/null) 7 | if [ $status -ne 0 ] 8 | set_color green 9 | echo -ne '+ ' 10 | set_color normal 11 | end 12 | end 13 | 14 | function show_branch_name 15 | set -l branch_name (git rev-parse --abbrev-ref HEAD 2>/dev/null) 16 | if test -n $branch_name 17 | echo -ne $branch_name" " 18 | end 19 | end 20 | 21 | function show_hash 22 | set -l hash (git rev-parse --short HEAD 2>/dev/null) 23 | if test -n $hash 24 | set_color green 25 | echo -ne $hash" " 26 | set_color normal 27 | end 28 | end 29 | 30 | function show_last_commit_age 31 | set -l last_commit_age (git show -s --format=%ct 2>/dev/null) 32 | set -l git_show_status $status 33 | if test -n $last_commit_age 34 | and [ $git_show_status -ne 128 ] 35 | echo -ne (bruh lca (date +%s) $last_commit_age)" " 36 | end 37 | end 38 | 39 | function show_git_info 40 | set -l git_root (git rev-parse --show-toplevel -q 2>/dev/null) 41 | if [ $status -eq 0 ] 42 | show_dirty_sign 43 | show_branch_name 44 | show_hash 45 | show_last_commit_age 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /fish_theme/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #### bruh 4 | > A theme for [Oh My Fish][omf-link]. 5 | 6 | [![MIT License](https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square)](/LICENSE) 7 | [![Fish Shell Version](https://img.shields.io/badge/fish-v3.0.0-007EC7.svg?style=flat-square)](https://fishshell.com) 8 | [![Oh My Fish Framework](https://img.shields.io/badge/Oh%20My%20Fish-Framework-007EC7.svg?style=flat-square)](https://www.github.com/oh-my-fish/oh-my-fish) 9 | 10 |
11 | 12 | 13 | ## Install 14 | 15 | ```fish 16 | $ omf install bruh 17 | ``` 18 | 19 | 20 | ## Features 21 | 22 | * Lorem ipsum dolor sit amet. 23 | * Consectetur adipisicing elit. 24 | 25 | 26 | ## Screenshot 27 | 28 |

29 | 30 |

31 | 32 | 33 | # License 34 | 35 | [MIT][mit] © [Haze Booth][author] et [al][contributors] 36 | 37 | 38 | [mit]: https://opensource.org/licenses/MIT 39 | [author]: https://github.com/{{USER}} 40 | [contributors]: https://github.com/{{USER}}/theme-bruh/graphs/contributors 41 | [omf-link]: https://www.github.com/oh-my-fish/oh-my-fish 42 | 43 | [license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg?style=flat-square 44 | -------------------------------------------------------------------------------- /bruh.zsh-theme: -------------------------------------------------------------------------------- 1 | # bruh theme 2 | 3 | # setup 4 | setopt prompt_subst 5 | hl='%F{green}' 6 | reset='%F{fb_default_code}' 7 | 8 | function preexec() { 9 | before=$EPOCHREALTIME 10 | } 11 | 12 | function precmd() { 13 | git_commit_age='' 14 | if [ -z "$SSH_CONNECTION" ]; then 15 | connection='' 16 | else 17 | user="$(whoami)" 18 | hostname="$(hostname)" 19 | connection="$user@$hostname " 20 | fi 21 | if [ -z "${before}" ]; then 22 | elapsed='' 23 | else 24 | after=$EPOCHREALTIME 25 | raw_elapsed=$(echo "$after-$before" | bc) 26 | elapsed="$hl$(bruh tf $raw_elapsed)$reset " 27 | before='' 28 | fi 29 | git_dirty='' 30 | git_top_level="$(git rev-parse --show-toplevel -q 2>/dev/null)" 31 | if [ $? -eq 0 ] 32 | then 33 | git_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null) " 34 | git_shorthash="$hl$(git rev-parse --short HEAD 2>/dev/null)$reset " 35 | if [ -d "$PWD/.git" ]; then 36 | git_raw_last_commit_age="$(git show -s --format=%ct 2>/dev/null)" 37 | if [ $? -eq 0 ]; then 38 | git_commit_age="$(bruh lca $(date +%s) $git_raw_last_commit_age)" 39 | fi 40 | else 41 | fi 42 | 43 | $(git diff-index --quiet HEAD -- 2>/dev/null) 44 | do_we_have_changes=$? 45 | if [ $do_we_have_changes -ne 0 ]; then 46 | git_dirty="$hl+$reset " 47 | fi 48 | else 49 | git_branch='' 50 | git_shorthash='' 51 | git_commit_age='' 52 | fi 53 | } 54 | 55 | PROMPT=' $connection$elapsed$(bruh cwd) ' 56 | RPROMPT='$git_dirty$git_branch$git_shorthash$git_commit_age' 57 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cwd = @import("cwd.zig"); 3 | const tf = @import("time_format.zig"); 4 | pub const lib_log = std.log.scoped(.bruh); 5 | 6 | pub const Config = struct { 7 | dir_cutoff_len: ?usize = cwd.DEFAULT_DIR_CUTOFF_LEN, 8 | early_dir_cutoff_len: ?usize = cwd.DEFAULT_EARLY_DIR_CUTOFF_LEN, 9 | home_dir: ?[]const u8 = null, 10 | 11 | fn fromEnv(env_map: std.BufMap) !Config { 12 | var conf = Config{}; 13 | if (env_map.get(cwd.BRUH_DIR_CUTOFF_ENV_KEY)) |cutoff_len| { 14 | conf.dir_cutoff_len = try std.fmt.parseUnsigned(usize, cutoff_len, 10); 15 | } 16 | if (env_map.get(cwd.BRUH_EARLY_DIR_CUTOFF_ENV_KEY)) |cutoff_len| { 17 | conf.early_dir_cutoff_len = try std.fmt.parseUnsigned(usize, cutoff_len, 10); 18 | } 19 | if (env_map.get("HOME")) |home_dir| { 20 | conf.home_dir = home_dir; 21 | } 22 | return conf; 23 | } 24 | }; 25 | 26 | pub fn main() !void { 27 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 28 | var allocator = &arena.allocator; 29 | 30 | var env_map = try std.process.getEnvMap(allocator); 31 | 32 | var config = try Config.fromEnv(env_map); 33 | 34 | var arg_iter = std.process.args(); 35 | // skip executable 36 | _ = arg_iter.skip(); 37 | 38 | if (arg_iter.next(allocator)) |maybe_arg| { 39 | const arg = try maybe_arg; 40 | if (std.mem.eql(u8, arg, "cwd")) { 41 | cwd.printCollapsedWorkingDir(allocator, config); 42 | } else if (std.mem.eql(u8, arg, "lca")) { 43 | const time_a_raw = try arg_iter.next(allocator) orelse { 44 | lib_log.err("No source seconds given to lca", .{}); 45 | return; 46 | }; 47 | const time_b_raw = try arg_iter.next(allocator) orelse { 48 | lib_log.err("No dest seconds given to lca", .{}); 49 | return; 50 | }; 51 | const time_a = try std.fmt.parseFloat(f64, time_a_raw); 52 | const time_b = try std.fmt.parseFloat(f64, time_b_raw); 53 | const diff = std.math.absFloat(time_a - time_b); 54 | tf.printFormattedTime(diff); 55 | } else if (std.mem.eql(u8, arg, "tf")) { 56 | if (arg_iter.next(allocator)) |maybe_seconds| { 57 | const seconds_arg = try maybe_seconds; 58 | tf.printFormattedTime(try std.fmt.parseFloat(f64, seconds_arg)); 59 | } else { 60 | lib_log.err("No seconds given to time_format", .{}); 61 | } 62 | } else lib_log.err("Invalid subcommand '{s}'", .{arg}); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/cwd.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const bruh = @import("main.zig"); 3 | 4 | /// Any paths longer than this variable will be cut off 5 | pub const DEFAULT_DIR_CUTOFF_LEN: usize = 5; 6 | pub const DEFAULT_EARLY_DIR_CUTOFF_LEN: usize = 3; 7 | pub const BRUH_DIR_CUTOFF_ENV_KEY = "BRUH_DIR_CUTOFF_LEN"; 8 | pub const BRUH_EARLY_DIR_CUTOFF_ENV_KEY = "BRUH_EARLY_DIR_CUTOFF_LEN"; 9 | pub const SEPARATING_CHARS = "-_ "; 10 | 11 | fn createCollapsedWorkingDir(allocator: *std.mem.Allocator, config: bruh.Config) ![]const u8 { 12 | var in_home_dir = false; 13 | var current_working_dir = try std.process.getCwdAlloc(allocator); 14 | if (config.home_dir) |home_dir| { 15 | if (std.mem.startsWith(u8, current_working_dir, home_dir)) { 16 | in_home_dir = true; 17 | current_working_dir = current_working_dir[home_dir.len..]; 18 | } 19 | } 20 | var new_working_dir = try std.ArrayList(u8).initCapacity(allocator, current_working_dir.len); 21 | if (in_home_dir) { 22 | try new_working_dir.append('~'); 23 | try new_working_dir.append(std.fs.path.sep); 24 | } else try new_working_dir.append('/'); 25 | // TODO(haze): this is kinda naive 26 | const num_paths: usize = std.mem.count(u8, current_working_dir, std.fs.path.sep_str); 27 | var path_iterator = std.mem.tokenize(u8, current_working_dir, std.fs.path.sep_str); 28 | var path_idx: usize = 0; 29 | while (path_iterator.next()) |path_segment| { 30 | try appendPathHelper(&new_working_dir, path_idx, num_paths, path_iterator, config, path_segment); 31 | path_idx += 1; 32 | } 33 | return new_working_dir.toOwnedSlice(); 34 | } 35 | 36 | fn appendElipses(buf: *std.ArrayList(u8)) !void { 37 | if (@import("builtin").os.tag == .windows) 38 | try buf.appendSlice("...") 39 | else 40 | try buf.appendSlice("…"); 41 | } 42 | 43 | fn appendPathHelper(buf: *std.ArrayList(u8), path_idx: usize, total_num_paths: usize, path_iterator: std.mem.TokenIterator(u8), config: bruh.Config, path_segment: []const u8) !void { 44 | const is_last_path = path_iterator.rest().len == 0; 45 | var print_without_truncating = true; 46 | if (!is_last_path) { 47 | var is_early_cutoff = path_idx >= (total_num_paths / 2); 48 | var segment_head: usize = 0; 49 | // how many chars can we add before we need to shorten? 50 | var count: usize = 0; 51 | while (segment_head < path_segment.len) : (segment_head += 1) { 52 | if (config.early_dir_cutoff_len) |cutoff_len| { 53 | if (count > cutoff_len) { 54 | if (is_early_cutoff) { 55 | try buf.appendSlice(path_segment[0..count]); 56 | try appendElipses(buf); 57 | } else try buf.append(path_segment[0]); 58 | print_without_truncating = false; 59 | break; 60 | } 61 | } 62 | // if we find a separating char at our head, reset the count 63 | if (std.mem.indexOfScalar(u8, SEPARATING_CHARS, path_segment[segment_head]) != null) { 64 | count = 0; 65 | continue; 66 | } 67 | count += 1; 68 | } 69 | } 70 | if (print_without_truncating) 71 | try buf.appendSlice(path_segment); 72 | if (!is_last_path) 73 | try buf.append(std.fs.path.sep); 74 | } 75 | 76 | pub fn printCollapsedWorkingDir(allocator: *std.mem.Allocator, config: bruh.Config) void { 77 | const stdOut = std.io.getStdOut().writer(); 78 | const shortened_cwd = createCollapsedWorkingDir(allocator, config) catch |shortened_cwd_err| { 79 | stdOut.print("ERR({s})\n", .{@errorName(shortened_cwd_err)}) catch |stdout_err| { 80 | bruh.lib_log.err("Failed to write to stdout: {s}", .{@errorName(stdout_err)}); 81 | }; 82 | return; 83 | }; 84 | stdOut.print("{s}\n", .{shortened_cwd}) catch |stdout_err_prime| { 85 | bruh.lib_log.err("Failed to write to stdout: {s}", .{@errorName(stdout_err_prime)}); 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /src/time_format.zig: -------------------------------------------------------------------------------- 1 | // This file is responsible for creaeting nice looking time formats 2 | const std = @import("std"); 3 | const bruh = @import("main.zig"); 4 | 5 | // Unsigned Duration type, can only be positive 6 | // 5 seconds - 1 day = 0 nanos 7 | const Duration = struct { 8 | const Unit = enum { 9 | nanos, 10 | micros, 11 | millis, 12 | seconds, 13 | minutes, 14 | hours, 15 | days, 16 | 17 | fn qualifier(self: Unit) []const u8 { 18 | return switch (self) { 19 | .nanos => "ns", 20 | .micros => "µs", 21 | .millis => "ms", 22 | .seconds => "s", 23 | .minutes => "m", 24 | .hours => "hr", 25 | .days => "d", 26 | }; 27 | } 28 | 29 | fn toNanos(self: Unit, scalar: f64) f64 { 30 | return switch (self) { 31 | .nanos => scalar, 32 | .micros => scalar * std.time.ns_per_us, 33 | .millis => scalar * std.time.ns_per_ms, 34 | .seconds => scalar * std.time.ns_per_s, 35 | .minutes => scalar * std.time.ns_per_min, 36 | .hours => scalar * std.time.ns_per_hour, 37 | .days => scalar * std.time.ns_per_day, 38 | }; 39 | } 40 | }; 41 | ns: f64, 42 | largest_unit: Unit, 43 | 44 | fn from(unit: Unit, scalar: f64) Duration { 45 | var dur: Duration = undefined; 46 | dur.ns = unit.toNanos(scalar); 47 | dur.largest_unit = dur.getLargestUnit(); 48 | return dur; 49 | } 50 | 51 | fn getLargestUnit(self: Duration) Unit { 52 | if (self.days() >= 1) { 53 | return .days; 54 | } else if (self.hours() >= 1) { 55 | return .hours; 56 | } else if (self.minutes() >= 1) { 57 | return .minutes; 58 | } else if (self.seconds() >= 1) { 59 | return .seconds; 60 | } else if (self.millis() >= 1) { 61 | return .millis; 62 | } else if (self.micros() >= 1) { 63 | return .micros; 64 | } else { 65 | return .nanos; 66 | } 67 | } 68 | 69 | fn nanos(self: Duration) f64 { 70 | return self.ns; 71 | } 72 | 73 | fn micros(self: Duration) f64 { 74 | return self.nanos() / std.time.ns_per_us; 75 | } 76 | 77 | fn millis(self: Duration) f64 { 78 | return self.nanos() / std.time.ns_per_ms; 79 | } 80 | 81 | fn seconds(self: Duration) f64 { 82 | return self.nanos() / std.time.ns_per_s; 83 | } 84 | 85 | fn minutes(self: Duration) f64 { 86 | return self.nanos() / std.time.ns_per_min; 87 | } 88 | 89 | fn hours(self: Duration) f64 { 90 | return self.nanos() / std.time.ns_per_hour; 91 | } 92 | 93 | fn days(self: Duration) f64 { 94 | return self.nanos() / std.time.ns_per_day; 95 | } 96 | 97 | fn as(self: Duration, unit: Unit) f64 { 98 | return switch (unit) { 99 | .nanos => self.ns, 100 | .millis => self.millis(), 101 | .days => self.days(), 102 | .hours => self.hours(), 103 | .minutes => self.minutes(), 104 | .seconds => self.seconds(), 105 | .micros => self.micros(), 106 | }; 107 | } 108 | 109 | pub fn format( 110 | self: Duration, 111 | comptime fmt: []const u8, 112 | _: std.fmt.FormatOptions, 113 | out_stream: anytype, 114 | ) !void { 115 | _ = fmt; 116 | // if it has a fractional part, print with up to 2 places of precision 117 | if (std.math.modf(self.as(self.largest_unit)).fpart > 0) { 118 | // if we are seconds and above, print with more precision (we only want 2 for millis and below) 119 | if (@enumToInt(self.largest_unit) > @enumToInt(Unit.seconds)) { 120 | try std.fmt.format(out_stream, "{d:.2}{s}", .{ self.as(self.largest_unit), Unit.qualifier(self.largest_unit) }); 121 | } else { 122 | try std.fmt.format(out_stream, "{d:.2}{s}", .{ self.as(self.largest_unit), Unit.qualifier(self.largest_unit) }); 123 | } 124 | } else { 125 | // otherwise, print with none 126 | try std.fmt.format(out_stream, "{d}{s}", .{ self.as(self.largest_unit), Unit.qualifier(self.largest_unit) }); 127 | } 128 | } 129 | }; 130 | 131 | pub fn printFormattedTime(seconds: f64) void { 132 | var stdout = std.io.getStdOut().writer(); 133 | stdout.print("{}\n", .{Duration.from(.seconds, seconds)}) catch |stdout_err| { 134 | bruh.lib_log.err("Failed to print calculated formatted time: {s}", .{@errorName(stdout_err)}); 135 | }; 136 | } 137 | --------------------------------------------------------------------------------