From 2241e10f472c14cc103da974ff41c86e9f892d0f Mon Sep 17 00:00:00 2001 From: gwbeip Date: Fri, 29 May 2026 19:30:55 +0800 Subject: [PATCH] feat(Nushell): remove starship in the prompt rendering --- nushell/modules/pj_prompt.nu | 599 +++++++++++++++++++++++++++++---- nushell/modules/pj_starship.nu | 38 +++ 2 files changed, 572 insertions(+), 65 deletions(-) create mode 100644 nushell/modules/pj_starship.nu diff --git a/nushell/modules/pj_prompt.nu b/nushell/modules/pj_prompt.nu index 227eba4..6b5eaa4 100644 --- a/nushell/modules/pj_prompt.nu +++ b/nushell/modules/pj_prompt.nu @@ -1,80 +1,549 @@ -export def --env "pj starship config" [name: string] { - if (which ^starship | is-empty) { - echo "starship is not installed." - return +def _pj_detect_os_key [] { + let host_name = (((sys host | get name?) | default ($nu.os-info.name? | default "")) | into string) + if ($host_name == "Darwin") { + return "Macos" + } + if ($host_name =~ "Windows") { + return "Windows" + } + if ($host_name != "Linux") { + return "Unknown" + } + + 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 { + "Linux" } - let cfg = ($env.NU_POLYJUICE_PATH | path join ".." "starship" $"starship_($name).toml") - $env.STARSHIP_CONFIG = $cfg } -export def --env "pj starship list" [] { - if (which ^starship | is-empty) { - echo "starship is not installed." - return +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" => "󰍲 " + _ => " " } - ls ($env.NU_POLYJUICE_PATH | path join ".." "starship") -s | get name | parse -r '^(starship_(?.*).toml)$' | get name } -export def "pj starship doctor" [] { - let installed = ((which ^starship | length) > 0) - let prompt_type = ($env.PROMPT_COMMAND? | describe) - let right_prompt_type = ($env.PROMPT_COMMAND_RIGHT? | describe) - let prompt_cmd = (($env.PROMPT_COMMAND? | to nuon --serialize) | default "") - let right_prompt_cmd = (($env.PROMPT_COMMAND_RIGHT? | to nuon --serialize) | default "") - let prompt_uses_starship = ($prompt_cmd | str contains "starship' prompt") - let right_prompt_uses_starship = ($right_prompt_cmd | str contains "starship' prompt") +def _pj_os_symbol [] { + let key = ($env.PJ_OS_KEY? | default (_pj_detect_os_key)) + _pj_os_symbol_from_key $key +} + +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_truncate_pwd [pwd: string, length: int = 4] { + let home = ($env.HOME? | default "") + let normalized_pwd = (($pwd | into string) | str replace -a "\\" "/") + let normalized_home = (($home | into string) | str replace -a "\\" "/") + + let shown = if ($normalized_home != "" and ($normalized_pwd | str starts-with $normalized_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]:/') { + ($shown | str substring 3.. | 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) } + } + + mut parts = [] + if ($changed > 0) { $parts = ($parts | append $"!($changed)") } + if ($untracked > 0) { $parts = ($parts | append $"?($untracked)") } + if ($stashed > 0) { $parts = ($parts | append $"\$($stashed)") } + if ($ahead > 0) { $parts = ($parts | append $"⇡($ahead)") } + if ($behind > 0) { $parts = ($parts | append $"⇣($behind)") } { - starship_installed: $installed - starship_path: ((which ^starship | get path.0?) | default "") - starship_version: (if $installed { (^starship --version | str trim) } else { "" }) - STARSHIP_SHELL: ($env.STARSHIP_SHELL? | default "") - STARSHIP_CONFIG: ($env.STARSHIP_CONFIG? | default "") - prompt_command_type: $prompt_type - prompt_command_uses_starship: $prompt_uses_starship - prompt_command_right_type: $right_prompt_type - prompt_command_right_uses_starship: $right_prompt_uses_starship + branch: $branch_text + changed: $changed + untracked: $untracked + stashed: $stashed + ahead: $ahead + behind: $behind } } +def _pj_git_branch_module [] { + let c_git = (ansi '#4ca47b') + let c_alert = (ansi '#c2295b') + let reset = (ansi reset) + let git = (_pj_git_info) + let branch = ($git.branch | default "") + + mut status_parts = [] + if (($git.changed | default 0) > 0) { + $status_parts = ($status_parts | append $"($c_alert)!($git.changed)($reset)") + } + if (($git.untracked | default 0) > 0) { + $status_parts = ($status_parts | append $"($c_alert)?($git.untracked)($reset)") + } + if (($git.stashed | default 0) > 0) { + $status_parts = ($status_parts | append $"($c_alert)\$($git.stashed)($reset)") + } + if (($git.ahead | default 0) > 0) { + $status_parts = ($status_parts | append $"($c_git)⇡($git.ahead)($reset)") + } + if (($git.behind | default 0) > 0) { + $status_parts = ($status_parts | append $"($c_git)⇣($git.behind)($reset)") + } + + let git_status = if (($status_parts | length) > 0) { + $" (($status_parts | str join ' '))" + } else { + "" + } + + if ($branch == "") { + "" + } else { + $"($c_git)on  ($branch)($git_status) ($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 [] { + let reset = (ansi reset) + $"(ansi cyan_bold) nu ($reset)" +} + +def _pj_time_module [] { + let c_time = (ansi '#a6356f') + let reset = (ansi reset) + let now = (date now | format date "%R") + $"($c_time)  ($now) ($reset)" +} + +def _pj_duration_module [] { + let c_duration = (ansi '#a6356f') + let reset = (ansi reset) + let ms = (_pj_cmd_duration_ms) + let duration_text = if ($ms >= 1000) { + let s = (($ms / 1000) | into int) + $"($s)s" + } else { + $"($ms)ms" + } + $"($c_duration) 󱫑 ($duration_text) ($reset)" +} + +def _pj_left_prompt [] { + let c_os = (ansi '#6b6b6b') + let c_user = (ansi '#4ca47b') + let c_root = (ansi '#c2295b') + let c_host = (ansi '#4ca47b') + let c_ssh = (ansi '#d65d0e') + let c_dir = (ansi '#9d8fbe') + let c_c = (ansi '#306898') + let c_cpp = (ansi '#306898') + let c_rust = (ansi '#000000') + let c_zig = (ansi '#306898') + let c_python = (ansi '#4685b7') + let reset = (ansi reset) + + let user = ($env.USER? | default ($env.USERNAME? | default "user")) + let is_root = ($user == "root") + let user_color = if $is_root { $c_root } else { $c_user } + + let host = (sys host | get hostname? | default "") + let in_ssh = (($env.SSH_CONNECTION? | default "") != "" or ($env.SSH_TTY? | default "") != "") + let host_part = if $in_ssh { + $"($c_host)@($host)($reset)($c_ssh)  󰬚󰬚󰬏 ($reset)" + } else { + "" + } + + let shell_part = (_pj_shell_module) + let dir = (_pj_truncate_pwd $env.PWD 4) + let git_part = (_pj_git_branch_module) + + let langs = (_pj_lang_segments) + let lang_parts = ($langs | each {|seg| + if ($seg | str starts-with "") { + $"($c_python)via ($seg) ($reset)" + } else if ($seg | str starts-with "󱘗") { + $"($c_rust)via ($seg) ($reset)" + } else if ($seg | str starts-with "") { + $"($c_cpp)via ($seg) ($reset)" + } else if ($seg | str starts-with "") { + $"($c_c)via ($seg) ($reset)" + } else if ($seg | str starts-with "") { + $"($c_zig)via ($seg) ($reset)" + } else { + $seg + } + }) + + let time_part = (_pj_time_module) + let duration_part = (_pj_duration_module) + + mut out = "" + $out = $"($out)($c_os)(_pj_os_symbol)($reset)" + $out = $"($out)($user_color)($user)($reset)" + if ($host_part != "") { $out = $"($out)($host_part)" } + $out = $"($out)($shell_part)" + $out = $"($out)($c_dir) ($dir) ($reset)" + if ($git_part != "") { $out = $"($out)($git_part)" } + for m in $lang_parts { + $out = $"($out)($m)" + } + $out = $"($out)($time_part)($duration_part)" + $out +} + +def _pj_prompt_indicator [] { + if (($env.LAST_EXIT_CODE? | default 0) == 0) { + $"(ansi green_bold)❯(ansi reset) " + } else { + $"(ansi red_bold)❯(ansi reset) " + } +} + +def _pj_prompt_content [] { + $"(_pj_left_prompt)\n(_pj_prompt_indicator)" +} + export-env { - if (which ^starship | is-not-empty) { - $env.STARSHIP_SHELL = "nu" - load-env { - STARSHIP_SESSION_KEY: (random chars -l 16) - PROMPT_MULTILINE_INDICATOR: ( - ^'starship' prompt --continuation - ) - - # Does not play well with default character module. - # TODO: Also Use starship vi mode indicators? - PROMPT_INDICATOR: "" - - PROMPT_COMMAND: {|| - # jobs are not supported - ( - ^'starship' prompt - --cmd-duration $env.CMD_DURATION_MS - $"--status=($env.LAST_EXIT_CODE)" - --terminal-width (term size).columns - ) - } - - config: ($env.config? | default {} | merge { - render_right_prompt_on_last_line: true - }) - - PROMPT_COMMAND_RIGHT: {|| - ( - ^'starship' prompt - --right - --cmd-duration $env.CMD_DURATION_MS - $"--status=($env.LAST_EXIT_CODE)" - --terminal-width (term size).columns - ) - } + load-env { + PJ_OS_KEY: (_pj_detect_os_key) + PROMPT_MULTILINE_INDICATOR: "" + PROMPT_INDICATOR: "" + PROMPT_INDICATOR_VI_INSERT: "" + PROMPT_INDICATOR_VI_NORMAL: "" + PROMPT_COMMAND: {|| + (_pj_prompt_content) } - let default_cfg = ($env.NU_POLYJUICE_PATH | path join ".." "starship" $"starship_default.toml") - $env.STARSHIP_CONFIG = $default_cfg + PROMPT_COMMAND_RIGHT: {|| + "" + } + config: ($env.config? | default {} | merge { + render_right_prompt_on_last_line: false + }) } } diff --git a/nushell/modules/pj_starship.nu b/nushell/modules/pj_starship.nu new file mode 100644 index 0000000..5e46c69 --- /dev/null +++ b/nushell/modules/pj_starship.nu @@ -0,0 +1,38 @@ +export def --env "pj starship config" [name: string] { + if (which ^starship | is-empty) { + echo "starship is not installed." + return + } + let cfg = ($env.NU_POLYJUICE_PATH | path join ".." "starship" $"starship_($name).toml") + $env.STARSHIP_CONFIG = $cfg +} + +export def --env "pj starship list" [] { + if (which ^starship | is-empty) { + echo "starship is not installed." + return + } + ls ($env.NU_POLYJUICE_PATH | path join ".." "starship") -s | get name | parse -r '^(starship_(?.*).toml)$' | get name +} + +export def "pj starship doctor" [] { + let installed = ((which ^starship | length) > 0) + let prompt_type = ($env.PROMPT_COMMAND? | describe) + let right_prompt_type = ($env.PROMPT_COMMAND_RIGHT? | describe) + let prompt_cmd = (($env.PROMPT_COMMAND? | to nuon --serialize) | default "") + let right_prompt_cmd = (($env.PROMPT_COMMAND_RIGHT? | to nuon --serialize) | default "") + let prompt_uses_starship = ($prompt_cmd | str contains "starship' prompt") + let right_prompt_uses_starship = ($right_prompt_cmd | str contains "starship' prompt") + + { + starship_installed: $installed + starship_path: ((which ^starship | get path.0?) | default "") + starship_version: (if $installed { (^starship --version | str trim) } else { "" }) + STARSHIP_SHELL: ($env.STARSHIP_SHELL? | default "") + STARSHIP_CONFIG: ($env.STARSHIP_CONFIG? | default "") + prompt_command_type: $prompt_type + prompt_command_uses_starship: $prompt_uses_starship + prompt_command_right_type: $right_prompt_type + prompt_command_right_uses_starship: $right_prompt_uses_starship + } +}