const pj_module_dir = (path self | path dirname) const pj_cache_dir = ($pj_module_dir | path join '..' 'cache') const pj_completion_base_dir = ($pj_cache_dir | path join 'nu_scripts' 'custom-completions') const pj_completion_commands = [git ssh cargo uv rustup make pytest rg tar vscode winget zig] const pj_completion_noop_module = ($pj_cache_dir | path join 'noop.nu') const pj_completion_registry_module = ($pj_cache_dir | path join 'pj_completions_registry.nu') const pj_completion_marker_root = ($pj_cache_dir | path join '.pj-completion-ok') const pj_completion_version = (version).version const pj_completion_marker_dir = ($pj_completion_marker_root | path join $pj_completion_version) const pj_completion_bootstrap_module = if ($pj_completion_registry_module | path exists) { $pj_completion_registry_module } else { $pj_completion_noop_module } def _pj_completion_script_path [name: string] { ($pj_completion_base_dir | path join $name $"($name)-completions.nu") } def _pj_completion_marker_path [name: string] { ($pj_completion_marker_dir | path join $name) } # Check whether a completion script can still be parsed by the running Nushell. # Third-party completion scripts occasionally use syntax that a newer Nushell # rejects; such files must never be pulled into the config or the whole shell # fails to start. def _pj_completion_file_valid [file: string] { if not ($file | path exists) { return false } let probe = (do -i { ^$nu.current-exe --no-config-file -c $"use \"($file)\"" } | complete) ($probe.exit_code == 0) } # Ensure per-version validity markers exist. A marker is only written for a # completion file that parses under the current Nushell version, so the generated # registry can safely gate each `use` on its marker. def _pj_completion_ensure_markers [] { if ($pj_completion_marker_dir | path exists) { return {created: false, invalid: []} } mkdir $pj_completion_marker_dir let results = ($pj_completion_commands | each {|name| let file = (_pj_completion_script_path $name) let valid = (_pj_completion_file_valid $file) if $valid { touch (_pj_completion_marker_path $name) } {command: $name, valid: $valid} }) let invalid = ($results | where {|it| not $it.valid } | get command) {created: true, invalid: $invalid} } def _pj_completion_report [result: record] { if ($result.invalid | is-empty) { print "[pj completion] all completion files are compatible with the current Nushell." } else { print "[pj completion] warning: incompatible completion files were disabled:" $result.invalid | each {|name| print $" - ($name)" } print "[pj completion] run `pj completion -u` to refresh the cached nu_scripts." } } def _pj_required_completion_files [] { $pj_completion_commands | each {|name| { command: $name file: (_pj_completion_script_path $name) } } } def _pj_completion_registry_lines [] { let header = [ "# Auto-generated by `pj completion sync`." "# Edit pj_completion_commands in pj_completions.nu, then re-run sync." "# Each module is only loaded when it was validated for the running Nushell version." "const pj_registry_dir = (path self | path dirname)" "const pj_completion_base_dir = ($pj_registry_dir | path join 'nu_scripts' 'custom-completions')" "const pj_completion_noop_module = ($pj_registry_dir | path join 'noop.nu')" "const pj_completion_marker_dir = ($pj_registry_dir | path join '.pj-completion-ok' (version).version)" "" ] let lines = ($pj_completion_commands | each {|name| [ ( "export use (if ((($pj_completion_marker_dir | path join '" + $name + "') | path exists) and (($pj_completion_base_dir | path join '" + $name + "' '" + $name + "-completions.nu') | path exists)) { ($pj_completion_base_dir | path join '" + $name + "' '" + $name + "-completions.nu') } else { $pj_completion_noop_module }) *" ) "" ] } | flatten) ($header | append $lines) } def _pj_completion_help [] { print "pj completion commands:" print " pj completion Validate cached completion files for the current Nushell." print " pj completion -u Update cache, or clone nu_scripts when cache is missing." print " pj completion -f Force re-clone cached nu_scripts repository." print " pj completion sync Regenerate cache registry, revalidate, and restart Nushell." print " pj completion -s Same as `pj completion sync`." print " pj completion doctor Show which completion files are enabled or disabled." print " pj completion -h Show this help message." } export def "pj completion sync" [ --no-restart(-n) # Regenerate the registry without restarting Nushell. --help(-h) ] { if $help { _pj_completion_help return } mkdir $pj_cache_dir let content = ((_pj_completion_registry_lines) | str join "\n") $content | save -f $pj_completion_registry_module print $"[pj completion] synced: ($pj_completion_registry_module)" # Invalidate validation markers so files are re-checked against the current Nushell. if ($pj_completion_marker_root | path exists) { rm -rf $pj_completion_marker_root } let ensure = (_pj_completion_ensure_markers) _pj_completion_report $ensure if not $no_restart { print "[pj completion] restarting Nushell..." exec nu } } # Show which completion files are enabled or disabled for the current Nushell version. export def "pj completion doctor" [] { let enabled = ($pj_completion_commands | where {|name| (_pj_completion_marker_path $name) | path exists }) let disabled = ($pj_completion_commands | where {|name| not ((_pj_completion_marker_path $name) | path exists) }) print $"Nushell version: ($pj_completion_version)" print $"Marker dir: ($pj_completion_marker_dir)" print "Enabled:" if ($enabled | is-empty) { print " (none)" } else { $enabled | each {|name| print $" - ($name)" } } print "Disabled:" if ($disabled | is-empty) { print " (none)" } else { $disabled | each {|name| print $" - ($name)" } } null } export def "pj completion" [ --update(-u) # Update cached repo, or clone when cache is missing. --force(-f) # Force re-clone cached nu_scripts repository. --sync(-s) # Regenerate static registry and restart Nushell. --no-restart(-n) # Used with --sync: regenerate without restarting Nushell. --help(-h) # Show help. ] { if $help { _pj_completion_help return } if $sync { pj completion sync --no-restart=($no_restart) return } let repo_url = "https://github.com/nushell/nu_scripts.git" let cache_dir = $pj_cache_dir let repo_dir = ($cache_dir | path join 'nu_scripts') let completion_dir = ($repo_dir | path join 'custom-completions') if (which git | is-empty) { error make { msg: "`git` command is required for pj completion" help: "Install Git first, then run `pj completion` again." } } mkdir $cache_dir if $force { if ($repo_dir | path exists) { rm -rf $repo_dir } } if not ($repo_dir | path exists) { if ($update or $force) { print "[pj completion] nu_scripts cache not found, cloning..." ^git clone $repo_url $repo_dir } else { print "[pj completion] nu_scripts cache not found. run `pj completion -u` to clone it." return { cache_dir: $cache_dir repo_dir: $repo_dir completion_dir: $completion_dir commands: $pj_completion_commands missing_files: [] } } } else { if not (($repo_dir | path join '.git') | path exists) { print "[pj completion] existing cache is not a git repo, re-cloning..." rm -rf $repo_dir ^git clone $repo_url $repo_dir } else if $update { print "[pj completion] cache exists, updating..." ^git -C $repo_dir pull --ff-only } } if not ($completion_dir | path exists) { error make { msg: "nu_scripts cloned, but custom-completions directory is missing" help: $"Check repository content under ($repo_dir)." } } let required = (_pj_required_completion_files) let missing = ($required | where {|it| not ($it.file | path exists) }) if not ($missing | is-empty) { print "[pj completion] warning: some required completion files are missing:" $missing | each {|it| print $" - ($it.command): ($it.file)" } } # The cache changed, so previously validated markers are no longer trustworthy. if ($pj_completion_marker_root | path exists) { rm -rf $pj_completion_marker_root } let ensure = (_pj_completion_ensure_markers) _pj_completion_report $ensure print "[pj completion] ok" { cache_dir: $cache_dir repo_dir: $repo_dir completion_dir: $completion_dir commands: $pj_completion_commands missing_files: $missing disabled: $ensure.invalid } } export-env { $env.config = ($env.config? | default {} | merge { completions: { quick: true partial: true algorithm: "fuzzy" case_sensitive: false external: { enable: true max_results: 100 } } }) let registry_missing = (not ($pj_completion_registry_module | path exists)) if $registry_missing { mkdir $pj_cache_dir let content = ((_pj_completion_registry_lines) | str join "\n") $content | save -f $pj_completion_registry_module } # A marker is only written when a file parses under the current Nushell, so a # broken (outdated) third-party completion can never stop the shell from loading. let ensure = (_pj_completion_ensure_markers) if ($registry_missing or $ensure.created) { if (($env.PJ_COMPLETION_BOOTSTRAPPED? | default "0") != "1") { load-env { PJ_COMPLETION_BOOTSTRAPPED: "1" } _pj_completion_report $ensure print "[pj completion] restarting Nushell..." exec nu } } } # Re-export completion externs from generated static registry. export use $pj_completion_bootstrap_module *