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) } } # Disabled along with the language-version probing. See the commented block # near `_pj_lang_segments` to restore. # # 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)/") } # Single `git rev-parse` call: returns whether we are inside a work tree and the # normalized repo root (empty string when not in a repo). def _pj_git_repo_info [] { if (which ^git | is-empty) { return { inside: false, root: "" } } let res = (^git rev-parse --is-inside-work-tree --show-toplevel | complete) if ($res.exit_code != 0) { return { inside: false, root: "" } } let parts = ($res.stdout | lines) let inside_raw = ($parts | get 0? | default "") let root_raw = ($parts | get 1? | default "") let root_norm = if ($root_raw == "") { "" } else { let raw = (($root_raw | str trim) | str replace -a "\\" "/") if ($raw == "/") { "/" } else { ($raw | str trim -r -c "/") } } { inside: (($inside_raw | str trim) == "true"), root: $root_norm } } def _pj_truncate_pwd [pwd: string, length: int = 4, repo: record = {inside: false, root: ""}] { 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 = $repo.root let in_repo = ($repo.inside and (_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 [repo: record] { let empty = { branch: "" changed: 0 untracked: 0 stashed: 0 ahead: 0 behind: 0 } # `repo.inside` can only be true when git exists, so no separate `which` # check is needed here. if (not $repo.inside) { return $empty } # One `git status` call carries: branch name, ahead/behind, stash count, # and the changed/untracked entries. Previously this was 4 separate git # invocations (rev-parse, branch --show-current, status, stash list). let status = (^git status --porcelain=2 --branch --show-stash | complete) if ($status.exit_code != 0) { return $empty } 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 = ($lines | where {|l| $l | str starts-with "# stash " } | get 0? | default "") let stashed_n = if ($stashed == "") { 0 } else { (($stashed | parse -r '# stash (?\d+)' | get 0? | default {n: "0"} | get n) | into int) } let head_line = ($lines | where {|l| $l | str starts-with "# branch.head " } | get 0? | default "") let head_name = if ($head_line == "") { "" } else { ($head_line | str replace "# branch.head " "" | str trim) } # Detached HEAD: fall back to the short oid (first 7 chars), matching the # previous `git rev-parse --short HEAD` behaviour. let branch_text = if ($head_name == "(detached)") { let oid_line = ($lines | where {|l| $l | str starts-with "# branch.oid " } | get 0? | default "") let oid = if ($oid_line == "") { "" } else { ($oid_line | str replace "# branch.oid " "" | str trim) } ($oid | str substring 0..7) } else { $head_name } let ab = ($lines | where {|l| $l | str starts-with "# branch.ab " } | get 0?) let ab_match = if ($ab == null) { null } else { ($ab | parse -r '# branch.ab \+(?\d+) -(?\d+)' | get 0?) } let ahead = if ($ab_match == null) { 0 } else { (($ab_match | get a) | into int) } let behind = if ($ab_match == null) { 0 } else { (($ab_match | get b) | into int) } { branch: $branch_text changed: $changed untracked: $untracked stashed: $stashed_n ahead: $ahead behind: $behind } } def _pj_git_branch_module [p: record, git: record] { 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)" } } # --------------------------------------------------------------------------- # Programming-language detection and version probing — DISABLED. # # These were the dominant cost in prompt rendering (up to ~660ms/frame in # projects with source files, mostly from spawning cc/c++/python --version). # They are commented out for now. To re-enable, uncomment the block below and # remove (or rename) the stub `_pj_lang_segments` that returns an empty list. # # 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 [] { # # One glob sweep replaces 11 separate glob calls. The resulting set of # # lowercase extensions is checked per-language below. glob is # # case-insensitive on Windows, so *.CPP is matched by "cpp" too. # let exts = (glob *.{c,h,cpp,cc,cxx,hpp,hh,hxx,rs,zig,py} # | each {|f| $f | path parse | get extension | str downcase } # | uniq) # # let has_c = ("c" in $exts or "h" in $exts) # let has_cpp = (["cpp" "cc" "cxx" "hpp" "hh" "hxx"] | any {|e| $e in $exts }) # let has_rust = (("Cargo.toml" | path exists) or ("rs" in $exts)) # let has_zig = (("build.zig" | path exists) or ("zig" in $exts)) # let has_python = (("pyproject.toml" | path exists) or ("requirements.txt" | path exists) or (".python-version" | path exists) or ("py" in $exts)) # # 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 # } # --------------------------------------------------------------------------- # Stub: language segments are disabled. Returns an empty list so the prompt # simply omits the "via " parts. See the commented block above to # restore full language detection. def _pj_lang_segments [] { [] } def _pj_prompt_static_base [] { let user = (try { whoami } catch { "" } | into string | str trim) let effective_user = if ($user != "") { $user } else { ($env.USER? | default ($env.USERNAME? | default "user")) } let host = (sys host | get hostname? | default "") let in_ssh = (($env.SSH_CONNECTION? | default "") != "" or ($env.SSH_TTY? | default "") != "") # let prompt_newline = if (($nu.os-info.name | str downcase) == "windows") { "\n\r" } else { "\n\r" } let prompt_newline = "\n\r" { os_symbol: (_pj_os_symbol) effective_user: $effective_user host: $host in_ssh: $in_ssh shell_name: "nu" prompt_newline: $prompt_newline } } def _pj_left_prompt [] { let p = ($env.PJ_PROMPT_PALETTE? | default (_pj_palette_default)) let static = ($env.PJ_LEFT_PROMPT_STATIC_PART? | default (_pj_prompt_static_base)) let user_color = if ($static.effective_user == "root") { $p.c_root } else { $p.c_user } let host_part = if $static.in_ssh { $"($p.c_host)@($static.host)($p.c_reset)($p.c_ssh)  󰬚󰬚󰬏 ($p.c_reset)" } else { "" } let shell_part = $"($p.c_shell) ($static.shell_name) ($p.c_reset)" let repo = (_pj_git_repo_info) let dir = (_pj_truncate_pwd $env.PWD 4 $repo) let git = (_pj_git_info $repo) let git_part = (_pj_git_branch_module $p $git) 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 now = (date now | format date "%R") let time_part = $"($p.c_time)  ($now) ($p.c_reset)" let duration_text = (_pj_format_duration (_pj_cmd_duration_ms)) let duration_part = $"($p.c_duration) 󱫑 ($duration_text) ($p.c_reset)" mut out = "" $out = $"($out)($p.c_os)($static.os_symbol)($p.c_reset)" $out = $"($out)($user_color)($static.effective_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 = $"($static.prompt_newline)($out)($time_part)($duration_part)($static.prompt_newline)" $out } def _pj_prompt_indicator [] { let p = ($env.PJ_PROMPT_PALETTE? | default (_pj_palette_default)) if (($env.LAST_EXIT_CODE? | default 0) == 0) { $"($p.c_indicator_ok)❯($p.c_reset) " } else { $"($p.c_indicator_err)❯($p.c_reset) " } } export-env { load-env { PJ_PROMPT_PALETTE: (_pj_palette_default) PJ_OS_KEY: (_pj_detect_os_key) PJ_LEFT_PROMPT_STATIC_PART: (_pj_prompt_static_base) PROMPT_MULTILINE_INDICATOR: ":" PROMPT_INDICATOR: {|| _pj_prompt_indicator } PROMPT_INDICATOR_VI_INSERT: {|| _pj_prompt_indicator } PROMPT_INDICATOR_VI_NORMAL: {|| _pj_prompt_indicator } PROMPT_COMMAND: {|| _pj_left_prompt } PROMPT_COMMAND_RIGHT: {|| let p = ($env.PJ_PROMPT_PALETTE? | default (_pj_palette_default)) if (($env.LAST_EXIT_CODE? | default 0) != 0) { $"($p.c_right_error)Exit code: ($env.LAST_EXIT_CODE)($p.c_reset)" } else { "" } } config: ($env.config? | default {} | merge { render_right_prompt_on_last_line: true }) } }