def _pj_detect_os_key [] { let host_name = (((sys host | get name?) | default ($nu.os-info.name? | default "")) | into string | str downcase) if ($host_name | str contains "darwin") { return "Macos" } if ($host_name | str contains "windows") { return "Windows" } let os_release = if ("/etc/os-release" | path exists) { "/etc/os-release" } else if ("/usr/lib/os-release" | path exists) { "/usr/lib/os-release" } else { "" } if ($os_release != "") { let id_line = (open $os_release | lines | where {|l| $l | str starts-with "ID=" } | get 0?) let id = if ($id_line == null) { "" } else { (($id_line | str replace "ID=" "") | str trim -c '"') } match ($id | str downcase) { "alpine" => "Alpine" "alma" => "AlmaLinux" "almalinux" => "AlmaLinux" "amzn" => "Amazon" "arch" => "Arch" "artix" => "Artix" "centos" => "CentOS" "debian" => "Debian" "endeavouros" => "EndeavourOS" "fedora" => "Fedora" "garuda" => "Garuda" "gentoo" => "Gentoo" "kali" => "Kali" "linuxmint" => "Mint" "manjaro" => "Manjaro" "mariner" => "Mariner" "nixos" => "NixOS" "nobara" => "Nobara" "opensuse" => "openSUSE" "opensuse-leap" => "openSUSE" "opensuse-tumbleweed" => "openSUSE" "ol" => "OracleLinux" "pop" => "Pop" "raspbian" => "Raspbian" "rhel" => "RedHatEnterprise" "rocky" => "RockyLinux" "solus" => "Solus" "sles" => "SUSE" "opensuse-slowroll" => "openSUSE" "ubuntu" => "Ubuntu" "void" => "Void" _ => "Linux" } } else { if ($host_name | str contains "linux") { "Linux" } else { "Unknown" } } } def _pj_os_symbol_from_key [key: string] { match $key { "Alpaquita" => " " "Alpine" => " " "AlmaLinux" => " " "Amazon" => " " "Android" => " " "Arch" => " " "Artix" => " " "CachyOS" => " " "CentOS" => " " "Debian" => " " "DragonFly" => " " "Emscripten" => " " "EndeavourOS" => " " "Fedora" => " " "FreeBSD" => " " "Garuda" => "󰛓 " "Gentoo" => " " "HardenedBSD" => "󰞌 " "Illumos" => "󰈸 " "Kali" => " " "Linux" => " " "Mabox" => " " "Macos" => " " "Manjaro" => " " "Mariner" => " " "MidnightBSD" => " " "Mint" => " " "NetBSD" => " " "NixOS" => " " "Nobara" => " " "OpenBSD" => "󰈺 " "openSUSE" => " " "OracleLinux" => "󰌷 " "Pop" => " " "Raspbian" => " " "Redhat" => " " "RedHatEnterprise" => " " "RockyLinux" => " " "Redox" => "󰀘 " "Solus" => "󰠳 " "SUSE" => " " "Ubuntu" => " " "Unknown" => " " "Void" => " " "Windows" => "󰍲 " _ => " " } } def _pj_os_symbol [] { let key = ($env.PJ_OS_KEY? | default (_pj_detect_os_key)) _pj_os_symbol_from_key $key } def _pj_palette_default [] { { c_reset: (ansi reset) c_os: (ansi '#6b6b6b') c_user: (ansi '#4ca47b') c_root: (ansi '#c2295b') c_host: (ansi '#4ca47b') c_ssh: (ansi '#d65d0e') c_dir: (ansi '#9d8fbe') c_c: (ansi '#306898') c_cpp: (ansi '#306898') c_rust: (ansi '#000000') c_zig: (ansi '#306898') c_python: (ansi '#4685b7') c_git: (ansi '#4ca47b') c_alert: (ansi '#c2295b') c_time: (ansi '#a6356f') c_duration: (ansi '#a6356f') c_shell: (ansi cyan_bold) c_indicator_ok: (ansi green_bold) c_indicator_err: (ansi red_bold) c_right_error: (ansi red_bold) } } def _pj_palette [] { ($env.PJ_PROMPT_PALETTE? | default (_pj_palette_default)) } def _pj_runtime_default [] { let user = ($env.USER? | default ($env.USERNAME? | default "user")) let host = (sys host | get hostname? | default "") { user: $user is_root: ($user == "root") host: $host in_ssh: (($env.SSH_CONNECTION? | default "") != "" or ($env.SSH_TTY? | default "") != "") } } def _pj_runtime [] { (_pj_runtime_default) } def _pj_first_version [text: string] { let parsed = ($text | parse -r '(?\d+(?:\.\d+)+)') if (($parsed | length) > 0) { ($parsed | get ver.0) } else { "" } } def _pj_cmd_duration_ms [] { let raw = ($env.CMD_DURATION_MS? | default 0) let kind = ($raw | describe) if ($kind == "int") { return $raw } if ($kind == "duration") { return (($raw | into int) / 1_000_000) } let s = ($raw | into string | str trim) let parsed = ($s | parse -r '^(?\d+)') if (($parsed | length) > 0) { (($parsed | get n.0) | into int) } else { 0 } } def _pj_format_duration [ms: int] { if ($ms < 1000) { return $"($ms)ms" } let ms_part = ($ms mod 1000) if ($ms < 60_000) { let s = ($ms // 1000) return $"($s)s ($ms_part)ms" } if ($ms < 3_600_000) { let total_s = ($ms // 1000) let s = ($total_s mod 60) let m = ($total_s // 60) return $"($m)m ($s)s ($ms_part)ms" } let total_s = ($ms // 1000) let s = ($total_s mod 60) let total_m = ($total_s // 60) let m = ($total_m mod 60) let h = ($total_m // 60) $"($h)h ($m)m ($s)s ($ms_part)ms" } def _pj_is_path_within [path: string, base: string] { if ($base == "") { return false } if ($path == $base) { return true } ($path | str starts-with $"($base)/") } def _pj_git_root_normalized [] { if (which ^git | is-empty) { return "" } let top = (^git rev-parse --show-toplevel | complete) if ($top.exit_code != 0) { return "" } let raw = (($top.stdout | str trim) | str replace -a "\\" "/") if ($raw == "/") { "/" } else { ($raw | str trim -r -c "/") } } def _pj_truncate_pwd [pwd: string, length: int = 4] { let home = (($nu.home-dir? | default ($nu.home-path? | default ($env.HOME? | default ""))) | into string) let normalized_pwd_raw = (($pwd | into string) | str replace -a "\\" "/") let normalized_home_raw = (($home | into string) | str replace -a "\\" "/") let normalized_pwd = if ($normalized_pwd_raw == "/") { "/" } else { ($normalized_pwd_raw | str trim -r -c "/") } let normalized_home = if ($normalized_home_raw == "/") { "/" } else { ($normalized_home_raw | str trim -r -c "/") } let in_home = if ($normalized_home == "") { false } else { (_pj_is_path_within $normalized_pwd $normalized_home) } let repo_root = (_pj_git_root_normalized) let in_repo = (_pj_is_path_within $normalized_pwd $repo_root) if $in_repo { let repo_name = ($repo_root | path basename) let rel = if ($normalized_pwd == $repo_root) { "" } else { ($normalized_pwd | str substring (($repo_root | str length) + 1)..) } let rel_parts = if ($rel == "") { [] } else { ($rel | split row "/" | where {|it| $it != "" }) } let keep_rel_count = if ($length <= 1) { 0 } else { $length - 1 } let shown_rel_parts = if (($rel_parts | length) <= $keep_rel_count) { $rel_parts } else { ($rel_parts | last $keep_rel_count) } if (($shown_rel_parts | length) == 0) { return $"…/($repo_name)" } else { return $"…/($repo_name)/($shown_rel_parts | str join '/')" } } let shown = if $in_home { let rest = ($normalized_pwd | str substring ($normalized_home | str length)..) if ($rest == "") { "~" } else { $"~($rest)" } } else { $normalized_pwd } if ($shown == "~") { return "~" } let parts = if ($shown | str starts-with "~/") { ($shown | str substring 2.. | split row "/" | where {|it| $it != "" }) } else if ($shown | str starts-with "/") { ($shown | str substring 1.. | split row "/" | where {|it| $it != "" }) } else if ($shown =~ '^[A-Za-z]:(/|$)') { let start = if ($shown =~ '^[A-Za-z]:/') { 3 } else { 2 } ($shown | str substring $start.. | split row "/" | where {|it| $it != "" }) } else { ($shown | split row "/" | where {|it| $it != "" }) } let prefix = if ($shown | str starts-with "~/") { "~/" } else if ($shown | str starts-with "/") { "/" } else if ($shown =~ '^[A-Za-z]:(/|$)') { let drive = ($shown | str substring 0..1) $"($drive)/" } else { "" } if (($parts | length) <= $length) { if ($prefix == "") { ($parts | str join "/") } else { $"($prefix)($parts | str join '/')" } } else { let tail = ($parts | last $length | str join "/") $"($prefix)…/($tail)" } } def _pj_git_info [] { if (which ^git | is-empty) { return { branch: "" changed: 0 untracked: 0 stashed: 0 ahead: 0 behind: 0 } } let repo_check = (^git rev-parse --is-inside-work-tree | complete) if ($repo_check.exit_code != 0 or (($repo_check.stdout | str trim) != "true")) { return { branch: "" changed: 0 untracked: 0 stashed: 0 ahead: 0 behind: 0 } } let branch_result = (^git branch --show-current | complete) let branch = ($branch_result.stdout | str trim) let branch_text = if ($branch != "") { $branch } else { let head_result = (^git rev-parse --short HEAD | complete) if ($head_result.exit_code == 0) { ($head_result.stdout | str trim) } else { "" } } let status = (^git status --porcelain=2 --branch | complete) if ($status.exit_code != 0) { return { branch: $branch_text changed: 0 untracked: 0 stashed: 0 ahead: 0 behind: 0 } } let lines = ($status.stdout | lines) let changed = ($lines | where {|l| ($l | str starts-with "1 ") or ($l | str starts-with "2 ") or ($l | str starts-with "u ") } | length) let untracked = ($lines | where {|l| $l | str starts-with "? " } | length) let stashed = ((^git stash list | complete).stdout | lines | length) let ab = ($lines | where {|l| $l | str starts-with "# branch.ab " } | get 0?) let ahead = if ($ab == null) { 0 } else { let m = (($ab | parse -r '# branch.ab \+(?\d+) -(?\d+)') | get 0?) if ($m == null) { 0 } else { (($m | get a) | into int) } } let behind = if ($ab == null) { 0 } else { let m = (($ab | parse -r '# branch.ab \+(?\d+) -(?\d+)') | get 0?) if ($m == null) { 0 } else { (($m | get b) | into int) } } { branch: $branch_text changed: $changed untracked: $untracked stashed: $stashed ahead: $ahead behind: $behind } } def _pj_git_branch_module [p: record] { let git = (_pj_git_info) let branch = ($git.branch | default "") mut status_parts = [] if (($git.changed | default 0) > 0) { $status_parts = ($status_parts | append $"($p.c_alert)!($git.changed)($p.c_reset)") } if (($git.untracked | default 0) > 0) { $status_parts = ($status_parts | append $"($p.c_alert)?($git.untracked)($p.c_reset)") } if (($git.stashed | default 0) > 0) { $status_parts = ($status_parts | append $"($p.c_alert)\$($git.stashed)($p.c_reset)") } if (($git.ahead | default 0) > 0) { $status_parts = ($status_parts | append $"($p.c_alert)⇡($git.ahead)($p.c_reset)") } if (($git.behind | default 0) > 0) { $status_parts = ($status_parts | append $"($p.c_alert)⇣($git.behind)($p.c_reset)") } let git_status = if (($status_parts | length) > 0) { $" (($status_parts | str join ' '))" } else { "" } if ($branch == "") { "" } else { $"($p.c_git)on  ($branch)($git_status) ($p.c_reset)" } } def _pj_has_glob [pattern: string] { ((glob $pattern | length) > 0) } def _pj_c_version [] { if (which ^cc | is-empty) { return "" } let out = (^cc --version | complete) _pj_first_version (($out.stdout | str trim) + " " + ($out.stderr | str trim)) } def _pj_cpp_version [] { if (which ^'c++' | is-empty) { return "" } let out = (^'c++' --version | complete) _pj_first_version (($out.stdout | str trim) + " " + ($out.stderr | str trim)) } def _pj_rust_version [] { if (which ^rustc | is-empty) { return "" } let out = (^rustc --version | complete) _pj_first_version (($out.stdout | str trim) + " " + ($out.stderr | str trim)) } def _pj_zig_version [] { if (which ^zig | is-empty) { return "" } let out = (^zig version | complete) _pj_first_version (($out.stdout | str trim) + " " + ($out.stderr | str trim)) } def _pj_python_version [] { if (which ^python | is-not-empty) { let out = (^python --version | complete) return (_pj_first_version (($out.stdout | str trim) + " " + ($out.stderr | str trim))) } if (which ^python3 | is-not-empty) { let out = (^python3 --version | complete) return (_pj_first_version (($out.stdout | str trim) + " " + ($out.stderr | str trim))) } "" } def _pj_python_venv [] { let venv = ($env.VIRTUAL_ENV? | default "") if ($venv != "") { return ($venv | path basename) } ($env.CONDA_DEFAULT_ENV? | default "") } def _pj_lang_segments [] { let has_c = ((_pj_has_glob "*.c") or (_pj_has_glob "*.h")) let has_cpp = ((_pj_has_glob "*.cpp") or (_pj_has_glob "*.cc") or (_pj_has_glob "*.cxx") or (_pj_has_glob "*.hpp") or (_pj_has_glob "*.hh") or (_pj_has_glob "*.hxx")) let has_rust = (("Cargo.toml" | path exists) or (_pj_has_glob "*.rs")) let has_zig = (("build.zig" | path exists) or (_pj_has_glob "*.zig")) let has_python = (("pyproject.toml" | path exists) or ("requirements.txt" | path exists) or (".python-version" | path exists) or (_pj_has_glob "*.py")) mut out = [] if $has_c { let v = (_pj_c_version) if ($v != "") { $out = ($out | append $" v($v)") } else { $out = ($out | append "") } } if $has_cpp { let v = (_pj_cpp_version) if ($v != "") { $out = ($out | append $" v($v)") } else { $out = ($out | append "") } } if $has_rust { let v = (_pj_rust_version) if ($v != "") { $out = ($out | append $"󱘗 v($v)") } else { $out = ($out | append "󱘗") } } if $has_zig { let v = (_pj_zig_version) if ($v != "") { $out = ($out | append $" v($v)") } else { $out = ($out | append "") } } if $has_python { let pyver = (_pj_python_version) let venv = (_pj_python_venv) let py_base = if ($pyver != "") { $" v($pyver)" } else { "" } if ($venv != "") { $out = ($out | append $"($py_base) \(($venv)\)") } else { $out = ($out | append $py_base) } } $out } def _pj_shell_module [p: record] { $"($p.c_shell) nu ($p.c_reset)" } def _pj_time_module [p: record] { let now = (date now | format date "%R") $"($p.c_time)  ($now) ($p.c_reset)" } def _pj_duration_module [p: record] { let ms = (_pj_cmd_duration_ms) let duration_text = (_pj_format_duration $ms) $"($p.c_duration) 󱫑 ($duration_text) ($p.c_reset)" } def _pj_left_prompt [] { let p = (_pj_palette) let runtime = (_pj_runtime) let user_color = if ($runtime.is_root | default false) { $p.c_root } else { $p.c_user } let host_part = if ($runtime.in_ssh | default false) { $"($p.c_host)@($runtime.host)($p.c_reset)($p.c_ssh)  󰬚󰬚󰬏 ($p.c_reset)" } else { "" } let shell_part = (_pj_shell_module $p) let dir = (_pj_truncate_pwd $env.PWD 4) let git_part = (_pj_git_branch_module $p) let langs = (_pj_lang_segments) let lang_parts = ($langs | each {|seg| if ($seg | str starts-with "") { $"($p.c_python)via ($seg) ($p.c_reset)" } else if ($seg | str starts-with "󱘗") { $"($p.c_rust)via ($seg) ($p.c_reset)" } else if ($seg | str starts-with "") { $"($p.c_cpp)via ($seg) ($p.c_reset)" } else if ($seg | str starts-with "") { $"($p.c_c)via ($seg) ($p.c_reset)" } else if ($seg | str starts-with "") { $"($p.c_zig)via ($seg) ($p.c_reset)" } else { $seg } }) let time_part = (_pj_time_module $p) let duration_part = (_pj_duration_module $p) mut out = "" $out = $"($out)($p.c_os)(_pj_os_symbol)($p.c_reset)" $out = $"($out)($user_color)($runtime.user)($p.c_reset)" if ($host_part != "") { $out = $"($out)($host_part)" } $out = $"($out)($shell_part)" $out = $"($out)($p.c_dir) ($dir) ($p.c_reset)" if ($git_part != "") { $out = $"($out)($git_part)" } for m in $lang_parts { $out = $"($out)($m)" } $out = $"\n($out)($time_part)($duration_part)\n" $out } def _pj_prompt_indicator [] { let p = (_pj_palette) if (($env.LAST_EXIT_CODE? | default 0) == 0) { $"($p.c_indicator_ok)❯($p.c_reset) " } else { $"($p.c_indicator_err)❯($p.c_reset) " } } def _pj_right_prompt [] { let p = (_pj_palette) if (($env.LAST_EXIT_CODE? | default 0) != 0) { $"($p.c_right_error)Exit code: ($env.LAST_EXIT_CODE)($p.c_reset)" } else { $"" } } export-env { load-env { PJ_PROMPT_PALETTE: (_pj_palette_default) PJ_OS_KEY: (_pj_detect_os_key) PROMPT_MULTILINE_INDICATOR: ":" PROMPT_INDICATOR: {|| _pj_prompt_indicator } PROMPT_INDICATOR_VI_INSERT: "" PROMPT_INDICATOR_VI_NORMAL: "" PROMPT_COMMAND: {|| _pj_left_prompt } PROMPT_COMMAND_RIGHT: {|| _pj_right_prompt } config: ($env.config? | default {} | merge { render_right_prompt_on_last_line: true }) } }