mirror of
https://github.com/RGBCube/nu_scripts
synced 2025-07-30 21:57:44 +00:00
512 lines
12 KiB
Text
512 lines
12 KiB
Text
# Build a full-line prompt with widgets for:
|
|
# activated python virtual environment (from `overlay use <ve>/bin/activate.nu`)
|
|
# current working directory
|
|
# git status (branch, branch ahead/behind remote, files changed)
|
|
# current position in remembered working directories (`std dirs`, a.k.a. `shells`)
|
|
# also, as a nu dev special, widget for active nu executable (flags `cargo run` vs "installed" and which branch built from).
|
|
#
|
|
# to use:
|
|
# 1. copy this file to `($nu.default-config-dir | path add 'scripts')` (Or someplace on your $env.NU_LIB_DIRS path, defined in env.nu)
|
|
# 2. cut `$env.PROMPT_COMMAND` and `PROMPT_OMMAND_RIGHT' from your env.nu.
|
|
# These will depend on `use full-line`, which can not be done in env.nu.
|
|
# You can leave the `PROMPT-*INDICATOR*` statements in env.nu or
|
|
# consolidate all prompt stuff in config.nu.
|
|
# 3. Add new prompt setup stuff somewhere in config.nu:
|
|
# ```
|
|
# use full-line.nu
|
|
# $env.PROMPT_COMMAND = {|| full-line }
|
|
# $env.PROMPT_COMMAND_RIGHT = ""
|
|
# ```
|
|
#
|
|
# credit panache-git for the git status widget.
|
|
|
|
use std dirs
|
|
use std assert
|
|
|
|
# build the prompt from segments, looks like:
|
|
#
|
|
# ^(<py ve>) ------------ <workingDirectory> ------ <gitRepoStatus> --- <dirs>$
|
|
#
|
|
# or, if no git repo current directory
|
|
#
|
|
# ^------------------------------------ <workingDirectory> ------------ <dirs>$
|
|
export def main [
|
|
--pad_char (-p) = '-' # character to fill with
|
|
] {
|
|
let left_content = ($"(
|
|
if 'VIRTUAL_ENV_PROMPT' in $env {'(' + $env.VIRTUAL_ENV_PROMPT + ') '}
|
|
)($pad_char + $pad_char + $pad_char)(current_exe)")
|
|
let left_content_len = ($left_content | ansi strip | str length -g)
|
|
|
|
let mid_content = ($" (dir_string) ")
|
|
let mid_content_len = ($mid_content | ansi strip | str length -g)
|
|
|
|
let dirs_segment = $" |(dirs | each {|it| if $it.active {'V'} else {'.'}} | str join '')|"
|
|
let right_content = ($"(repo-styled)($pad_char + $pad_char + $pad_char)($dirs_segment)")
|
|
let right_content_len = ($right_content | ansi strip | str length -g)
|
|
|
|
let term_width = ((term size) | get columns)
|
|
let mid_padding = ($term_width - $left_content_len - $right_content_len)
|
|
|
|
[(ansi reset),
|
|
$left_content,
|
|
($mid_content | fill --character $pad_char --width $mid_padding --alignment center),
|
|
$right_content,
|
|
"\n"
|
|
] | str join ''
|
|
}
|
|
|
|
# build current exe widget
|
|
def current_exe [] {
|
|
let content = ([
|
|
($nu.current-exe | path dirname | path basename | str replace "bin" ""),
|
|
(version | get branch | str replace "main" ""),
|
|
] | str join " " | str trim)
|
|
(if (($content | str length) > 0) {
|
|
$"(' <' | bright-yellow)($content)('> ' | bright-yellow)"
|
|
} else {
|
|
""
|
|
})
|
|
}
|
|
|
|
# build current working directory segment
|
|
## don't get sucked into the path syntax wars: simply color portions of path to flag privileged vs normal user mode.
|
|
def dir_string [] {
|
|
|
|
let path_color = (if (is-admin) { ansi red_bold } else { ansi green_bold })
|
|
let separator_color = (if (is-admin) { ansi light_red_bold } else { ansi light_green_bold })
|
|
$"($path_color)($env.PWD)(ansi reset)" | str replace --all (char path_sep) $"($separator_color)(char path_sep)($path_color)"
|
|
}
|
|
|
|
# Following code cheerfully ~~stolen~~ adapted from:
|
|
# https://github.com/nushell/nu_scripts/blob/ab0d3aaad015ca8ac2c2004d728cc8bac32cda1b/modules/prompt/panache-git.nu
|
|
|
|
# Get repository status as structured data
|
|
def repo-structured [] {
|
|
let in_git_repo = ((git rev-parse HEAD err> /dev/null | complete | get exit_code) == 0)
|
|
|
|
let status = (if $in_git_repo {
|
|
git --no-optional-locks status --porcelain=2 --branch | lines
|
|
} else {
|
|
[]
|
|
})
|
|
|
|
let on_named_branch = (if $in_git_repo {
|
|
$status
|
|
| where ($it | str starts-with '# branch.head')
|
|
| first
|
|
| str contains '(detached)'
|
|
| nope
|
|
} else {
|
|
false
|
|
})
|
|
|
|
let branch_name = (if $on_named_branch {
|
|
$status
|
|
| where ($it | str starts-with '# branch.head')
|
|
| split column ' ' col1 col2 branch
|
|
| get branch
|
|
| first
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let commit_hash = (if $in_git_repo {
|
|
$status
|
|
| where ($it | str starts-with '# branch.oid')
|
|
| split column ' ' col1 col2 full_hash
|
|
| get full_hash
|
|
| first
|
|
| str substring 0..7
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let tracking_upstream_branch = (if $in_git_repo {
|
|
$status
|
|
| where ($it | str starts-with '# branch.upstream')
|
|
| str join
|
|
| is-empty
|
|
| nope
|
|
} else {
|
|
false
|
|
})
|
|
|
|
let upstream_exists_on_remote = (if $in_git_repo {
|
|
$status
|
|
| where ($it | str starts-with '# branch.ab')
|
|
| str join
|
|
| is-empty
|
|
| nope
|
|
} else {
|
|
false
|
|
})
|
|
|
|
let ahead_behind_table = (if $upstream_exists_on_remote {
|
|
$status
|
|
| where ($it | str starts-with '# branch.ab')
|
|
| split column ' ' col1 col2 ahead behind
|
|
} else {
|
|
[[]]
|
|
})
|
|
|
|
let commits_ahead = (if $upstream_exists_on_remote {
|
|
$ahead_behind_table
|
|
| get ahead
|
|
| first
|
|
| into int
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let commits_behind = (if $upstream_exists_on_remote {
|
|
$ahead_behind_table
|
|
| get behind
|
|
| first
|
|
| into int
|
|
| math abs
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let has_staging_or_worktree_changes = (if $in_git_repo {
|
|
$status
|
|
| where ($it | str starts-with '1') or ($it | str starts-with '2')
|
|
| str join
|
|
| is-empty
|
|
| nope
|
|
} else {
|
|
false
|
|
})
|
|
|
|
let has_untracked_files = (if $in_git_repo {
|
|
$status
|
|
| where ($it | str starts-with '?')
|
|
| str join
|
|
| is-empty
|
|
| nope
|
|
} else {
|
|
false
|
|
})
|
|
|
|
let has_unresolved_merge_conflicts = (if $in_git_repo {
|
|
$status
|
|
| where ($it | str starts-with 'u')
|
|
| str join
|
|
| is-empty
|
|
| nope
|
|
} else {
|
|
false
|
|
})
|
|
|
|
let staging_worktree_table = (if $has_staging_or_worktree_changes {
|
|
$status
|
|
| where ($it | str starts-with '1') or ($it | str starts-with '2')
|
|
| split column ' '
|
|
| get column2
|
|
| split column '' staging worktree --collapse-empty
|
|
} else {
|
|
[[]]
|
|
})
|
|
|
|
let staging_added_count = (if $has_staging_or_worktree_changes {
|
|
$staging_worktree_table
|
|
| where staging == 'A'
|
|
| length
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let staging_modified_count = (if $has_staging_or_worktree_changes {
|
|
$staging_worktree_table
|
|
| where staging in ['M', 'R']
|
|
| length
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let staging_deleted_count = (if $has_staging_or_worktree_changes {
|
|
$staging_worktree_table
|
|
| where staging == 'D'
|
|
| length
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let untracked_count = (if $has_untracked_files {
|
|
$status
|
|
| where ($it | str starts-with '?')
|
|
| length
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let worktree_modified_count = (if $has_staging_or_worktree_changes {
|
|
$staging_worktree_table
|
|
| where worktree in ['M', 'R']
|
|
| length
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let worktree_deleted_count = (if $has_staging_or_worktree_changes {
|
|
$staging_worktree_table
|
|
| where worktree == 'D'
|
|
| length
|
|
} else {
|
|
0
|
|
})
|
|
|
|
let merge_conflict_count = (if $has_unresolved_merge_conflicts {
|
|
$status
|
|
| where ($it | str starts-with 'u')
|
|
| length
|
|
} else {
|
|
0
|
|
})
|
|
|
|
{
|
|
in_git_repo: $in_git_repo,
|
|
on_named_branch: $on_named_branch,
|
|
branch_name: $branch_name,
|
|
commit_hash: $commit_hash,
|
|
tracking_upstream_branch: $tracking_upstream_branch,
|
|
upstream_exists_on_remote: $upstream_exists_on_remote,
|
|
commits_ahead: $commits_ahead,
|
|
commits_behind: $commits_behind,
|
|
staging_added_count: $staging_added_count,
|
|
staging_modified_count: $staging_modified_count,
|
|
staging_deleted_count: $staging_deleted_count,
|
|
untracked_count: $untracked_count,
|
|
worktree_modified_count: $worktree_modified_count,
|
|
worktree_deleted_count: $worktree_deleted_count,
|
|
merge_conflict_count: $merge_conflict_count
|
|
}
|
|
}
|
|
|
|
# Get repository status as a styled string
|
|
def repo-styled [] {
|
|
let status = (repo-structured)
|
|
|
|
let is_local_only = ($status.tracking_upstream_branch != true)
|
|
|
|
let upstream_deleted = (
|
|
$status.tracking_upstream_branch and
|
|
$status.upstream_exists_on_remote != true
|
|
)
|
|
|
|
let is_up_to_date = (
|
|
$status.upstream_exists_on_remote and
|
|
$status.commits_ahead == 0 and
|
|
$status.commits_behind == 0
|
|
)
|
|
|
|
let is_ahead = (
|
|
$status.upstream_exists_on_remote and
|
|
$status.commits_ahead > 0 and
|
|
$status.commits_behind == 0
|
|
)
|
|
|
|
let is_behind = (
|
|
$status.upstream_exists_on_remote and
|
|
$status.commits_ahead == 0 and
|
|
$status.commits_behind > 0
|
|
)
|
|
|
|
let is_ahead_and_behind = (
|
|
$status.upstream_exists_on_remote and
|
|
$status.commits_ahead > 0 and
|
|
$status.commits_behind > 0
|
|
)
|
|
|
|
let branch_name = (if $status.in_git_repo {
|
|
(if $status.on_named_branch {
|
|
$status.branch_name
|
|
} else {
|
|
['(' $status.commit_hash '...)'] | str join
|
|
})
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let branch_styled = (if $status.in_git_repo {
|
|
(if $is_local_only {
|
|
(branch-local-only $branch_name)
|
|
} else if $is_up_to_date {
|
|
(branch-up-to-date $branch_name)
|
|
} else if $is_ahead {
|
|
(branch-ahead $branch_name $status.commits_ahead)
|
|
} else if $is_behind {
|
|
(branch-behind $branch_name $status.commits_behind)
|
|
} else if $is_ahead_and_behind {
|
|
(branch-ahead-and-behind $branch_name $status.commits_ahead $status.commits_behind)
|
|
} else if $upstream_deleted {
|
|
(branch-upstream-deleted $branch_name)
|
|
} else {
|
|
$branch_name
|
|
})
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let has_staging_changes = (
|
|
$status.staging_added_count > 0 or
|
|
$status.staging_modified_count > 0 or
|
|
$status.staging_deleted_count > 0
|
|
)
|
|
|
|
let has_worktree_changes = (
|
|
$status.untracked_count > 0 or
|
|
$status.worktree_modified_count > 0 or
|
|
$status.worktree_deleted_count > 0 or
|
|
$status.merge_conflict_count > 0
|
|
)
|
|
|
|
let has_merge_conflicts = $status.merge_conflict_count > 0
|
|
|
|
let staging_summary = (if $has_staging_changes {
|
|
(staging-changes $status.staging_added_count $status.staging_modified_count $status.staging_deleted_count)
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let worktree_summary = (if $has_worktree_changes {
|
|
(worktree-changes $status.untracked_count $status.worktree_modified_count $status.worktree_deleted_count)
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let merge_conflict_summary = (if $has_merge_conflicts {
|
|
(unresolved-conflicts $status.merge_conflict_count)
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let delimiter = (if ($has_staging_changes and $has_worktree_changes) {
|
|
('|' | bright-yellow)
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let local_summary = (
|
|
$'($staging_summary) ($delimiter) ($worktree_summary) ($merge_conflict_summary)' | str trim
|
|
)
|
|
|
|
let local_indicator = (if $status.in_git_repo {
|
|
(if $has_worktree_changes {
|
|
('!' | red)
|
|
} else if $has_staging_changes {
|
|
('~' | bright-cyan)
|
|
} else {
|
|
''
|
|
})
|
|
} else {
|
|
''
|
|
})
|
|
|
|
let repo_summary = (
|
|
$'($branch_styled) ($local_summary) ($local_indicator)' | str trim
|
|
)
|
|
|
|
let left_bracket = ('[' | bright-yellow)
|
|
let right_bracket = (']' | bright-yellow)
|
|
|
|
(if $status.in_git_repo {
|
|
$' ($left_bracket)($repo_summary)($right_bracket) '
|
|
} else {
|
|
''
|
|
})
|
|
}
|
|
|
|
# Helper commands to encapsulate style and make everything else more readable
|
|
|
|
def nope [] {
|
|
each { |it| $it == false }
|
|
}
|
|
|
|
def bright-cyan [] {
|
|
each { |it| $"(ansi -e '96m')($it)(ansi reset)" }
|
|
}
|
|
|
|
def bright-green [] {
|
|
each { |it| $"(ansi -e '92m')($it)(ansi reset)" }
|
|
}
|
|
|
|
def bright-red [] {
|
|
each { |it| $"(ansi -e '91m')($it)(ansi reset)" }
|
|
}
|
|
|
|
def bright-yellow [] {
|
|
each { |it| $"(ansi -e '93m')($it)(ansi reset)" }
|
|
}
|
|
|
|
def green [] {
|
|
each { |it| $"(ansi green)($it)(ansi reset)" }
|
|
}
|
|
|
|
def red [] {
|
|
each { |it| $"(ansi red)($it)(ansi reset)" }
|
|
}
|
|
|
|
def branch-local-only [
|
|
branch: string
|
|
] {
|
|
$branch | bright-cyan
|
|
}
|
|
|
|
def branch-upstream-deleted [
|
|
branch: string
|
|
] {
|
|
$'($branch) (char failed)' | bright-cyan
|
|
}
|
|
|
|
def branch-up-to-date [
|
|
branch: string
|
|
] {
|
|
$'($branch) (char identical_to)' | bright-cyan
|
|
}
|
|
|
|
def branch-ahead [
|
|
branch: string
|
|
ahead: int
|
|
] {
|
|
$'($branch) (char branch_ahead)($ahead)' | bright-green
|
|
}
|
|
|
|
def branch-behind [
|
|
branch: string
|
|
behind: int
|
|
] {
|
|
$'($branch) (char branch_behind)($behind)' | bright-red
|
|
}
|
|
|
|
def branch-ahead-and-behind [
|
|
branch: string
|
|
ahead: int
|
|
behind: int
|
|
] {
|
|
$'($branch) (char branch_behind)($behind) (char branch_ahead)($ahead)' | bright-yellow
|
|
}
|
|
|
|
def staging-changes [
|
|
added: int
|
|
modified: int
|
|
deleted: int
|
|
] {
|
|
$'+($added) ~($modified) -($deleted)' | green
|
|
}
|
|
|
|
def worktree-changes [
|
|
added: int
|
|
modified: int
|
|
deleted: int
|
|
] {
|
|
$'+($added) ~($modified) -($deleted)' | red
|
|
}
|
|
|
|
def unresolved-conflicts [
|
|
conflicts: int
|
|
] {
|
|
$'!($conflicts)' | red
|
|
}
|
|
|