From 7a8deaaca1fbcb13e254b19f08280f3ea863763c Mon Sep 17 00:00:00 2001 From: nash Date: Sun, 15 Sep 2024 14:33:49 +0800 Subject: [PATCH] Split into smaller modules Remove fast mode. --- modules/prompt/powerline/README.md | 66 ++-- modules/prompt/powerline/lib/profile.nu | 30 ++ modules/prompt/powerline/lib/pwd.nu | 63 ++++ modules/prompt/powerline/mod.nu | 445 +++++++++++++++++++++++ modules/prompt/powerline/plugin/atuin.nu | 18 + modules/prompt/powerline/plugin/git.nu | 157 ++++++++ modules/prompt/powerline/plugin/kube.nu | 64 ++++ 7 files changed, 814 insertions(+), 29 deletions(-) create mode 100644 modules/prompt/powerline/lib/profile.nu create mode 100644 modules/prompt/powerline/lib/pwd.nu create mode 100644 modules/prompt/powerline/mod.nu create mode 100644 modules/prompt/powerline/plugin/atuin.nu create mode 100644 modules/prompt/powerline/plugin/git.nu create mode 100644 modules/prompt/powerline/plugin/kube.nu diff --git a/modules/prompt/powerline/README.md b/modules/prompt/powerline/README.md index 4d8e2fed1..b0439e671 100644 --- a/modules/prompt/powerline/README.md +++ b/modules/prompt/powerline/README.md @@ -1,44 +1,59 @@ ### configuration ``` -use power.nu - use power_git.nu +use power/power.nu + use power/plugin/git.nu * power inject 0 1 {source: git, color: '#504945'} - use power_kube.nu + use power/plugin/kube.nu * power inject 1 2 {source: kube, color: '#504945'} { - context: cyan - } { - reverse: true - separator: '@' + theme: { + context: cyan + } + config: { + reverse: true + separator: '@' + } + } + power set time { + config: { style: compact } } - use power_utils.nu - power inject 0 1 {source: atuin, color: '#4C4B4A'} - power set time null { style: compact } power init ``` or ``` +use power +use power/plugin/git.nu * +use power/plugin/kube.nu * $env.NU_POWER_SCHEMA = [ [ - {source: pwd, color: '#353230'} - {source: git, color: '#504945'} - ] + [source, color]; + [pwd, xterm_grey23] + [git, xterm_grey30] + ], [ - {source: proxy, color: 'dark_gray'} - {source: host, color: '#353230'} - {source: kube, color: '#504945'} - {source: time, color: '#666560'} + [source, color]; + [proxy, xterm_grey39] + [host, xterm_grey30] + [kube, xterm_grey23] + [time, xterm_grey27] ] ] - -use power.nu - use power_git.nu - use power_kube.nu +power set time { + config: { style: compact } +} +power set kube { + theme: { + context: cyan + } + config: { + reverse: true + separator: '@' + } +} power init ``` `$env.NU_POWER_SCHEMA` support configuring dynamically ## mode -- `$env.NU_POWER_MODE = ''` fast mode and default mode (experimental) - `$env.NU_POWER_DECORATOR = ''` power mode and plain mode - `$env.NU_POWER_FRAME = ''` two line prompt (experimental) @@ -49,11 +64,6 @@ $env.NU_POWER_BENCHMARK = true Then execute a few commands casually, such as pressing the Enter key continuously. then execute -``` -$env.NU_POWER_MODE = 'fast' # or 'power' -``` - -Go ahead and press enter, Execute power timelog to analyze the results. ``` power analyze @@ -61,9 +71,7 @@ power analyze ## todo - [x] source return `null` for hiding -- [ ] in fast mode, there is still a problem with hideable components on the left - [x] proxy stat invalid in plain mode - - '<<' not longer hide separator in `fast` mode - [ ] implement `power eject` - [ ] `$env.config.menus[].maker` can be restored - [x] support color theme diff --git a/modules/prompt/powerline/lib/profile.nu b/modules/prompt/powerline/lib/profile.nu new file mode 100644 index 000000000..f3dca579b --- /dev/null +++ b/modules/prompt/powerline/lib/profile.nu @@ -0,0 +1,30 @@ +export def logtime [msg act] { + let start = date now + let result = do $act + # HACK: serialization + let period = (date now) - $start | format duration ns | str replace ' ' '' + + echo $'($start | format date '%Y-%m-%d_%H:%M:%S%z')(char tab)($period)(char tab)($msg)(char newline)' + | save -a ~/.cache/nushell/power_time.log + + $result +} + +export def timelog [] { + open ~/.cache/nushell/power_time.log + | from tsv -n + | rename start duration message + | each {|x| + $x + | update start ($x.start | into datetime -f '%Y-%m-%d_%H:%M:%S%z') + | update duration ($x.duration | into duration) + } +} + +export def analyze [] { + timelog + | group-by message + | transpose component metrics + | each {|x| $x | upsert metrics ($x.metrics | get duration | math avg)} +} + diff --git a/modules/prompt/powerline/lib/pwd.nu b/modules/prompt/powerline/lib/pwd.nu new file mode 100644 index 000000000..42e2b921a --- /dev/null +++ b/modules/prompt/powerline/lib/pwd.nu @@ -0,0 +1,63 @@ +def 'path parents' [] { + let p = $in | path expand | path split + mut r = [] + for i in ($p | length | $in - 1)..0 { + $r ++= { a: ($p | range 0..$i), b: ($p | range ($i + 1)..) } + } + $r +} + +def 'find vcs' [] { + for i in ($in | path parents) { + if ($i.a | append '.git' | path join | path exists) { + return $i + } + } +} + +export def "pwd_abbr" [] { + {|bg| + let vcso = $env.PWD | find vcs + let no_vcs = $vcso | is-empty + let vcs = if $no_vcs { + [$env.PWD] + } else { + [($vcso.a | path join), ($vcso.b | path join)] + } + let relative = do -i { $vcs.0 | path relative-to $env.HOME } + let in_home = ($relative | describe -d).type == 'string' + + let cwd = if $in_home { + if ($relative | is-empty) { '~' } else { $'~(char separator)($relative)' } + } else { + $vcs.0 + } + + mut dir_comp = ($cwd | path split) + + if ($dir_comp | length) + ($vcso.b? | default 0 | length) > 5 { + let first = $dir_comp | first + let last = $dir_comp | last + let body = $dir_comp + |range 1..-2 + |each {|x| $x | str substring ..<2 } + $dir_comp = ([$first $body $last] | flatten) + } + let dir_comp = $dir_comp | path join + + let theme = $env.NU_POWER_THEME.pwd + + let tm = if $in_home { $theme.default } else { $theme.out_home } + let fg = if $no_vcs { + $"($tm)($dir_comp)" + } else { + if ($vcs.1 | is-empty) { + $"($tm)($dir_comp)" + } else { + [$"($tm)($dir_comp)($theme.vcs)" $"($vcs.1)"] + | path join + } + } + [$bg $fg] + } +} diff --git a/modules/prompt/powerline/mod.nu b/modules/prompt/powerline/mod.nu new file mode 100644 index 000000000..ca3e2616b --- /dev/null +++ b/modules/prompt/powerline/mod.nu @@ -0,0 +1,445 @@ +### pwd +use lib/pwd.nu * + +### proxy +export def proxy_stat [] { + {|bg| + let theme = $env.NU_POWER_THEME.proxy + if ($env.https_proxy? | is-not-empty) or ($env.http_proxy? | is-not-empty) { + [$bg ''] + } else { + [$bg null] + } + } +} + +### host +def host_abbr [] { + {|bg| + let theme = $env.NU_POWER_THEME.host + let n = (sys host).hostname + let ucl = if (is-admin) { + $theme.is_admin + } else { + $theme.default + } + [$bg $"($ucl)($n)"] + } +} + +### time +def time_segment [] { + {|bg| + let config = $env.NU_POWER_CONFIG.time + let theme = $env.NU_POWER_THEME.time + let format = match $config.style { + "compact" => { $'($theme.fst)%y%m%d($theme.snd)%w($theme.fst)%H%M%S' } + "rainbow" => { + let fmt = [w y m d H M S] + let color = ['1;93m' '1;35m' '1;34m' '1;36m' '1;32m' '1;33m' '1;91m'] + $fmt + | enumerate + | each { |x| $"(ansi -e ($color | get $x.index))%($x.item)" } + | str join + } + _ => { $'($theme.fst)%y-%m-%d[%w]%H:%M:%S' } + } + [$bg $"(date now | format date $format)"] + } +} + +export use lib/profile.nu * + +export def wraptime [message action] { + if $env.NU_POWER_BENCHMARK? == true { + {|| logtime $message $action } + } else { + $action + } +} + +def get_component [schema] { + let component = $env.NU_PROMPT_COMPONENTS | get $schema.source + if $env.NU_POWER_BENCHMARK? == true { + {|bg| logtime $'component ($schema.source)' {|| do $component $bg } } + } else { + $component + } +} + +### prompt +def decorator [ ] { + match $env.NU_POWER_DECORATOR { + 'plain' => { + {|s, direction?: string, color?: string = 'light_yellow', next_color?: string| + match $direction { + '|>'|'>' => { + let r = $'(ansi light_yellow)|' + $"($s)($r)" + } + '>>'|'<<' => { + $s + } + '<' => { + let l = $'(ansi light_yellow)|' + $"($l)($s)" + } + } + } + } + 'power' => { + {|s, direction?: string, color?: string = 'light_yellow', next_color?: string| + match $direction { + '|>' => { + let l = (ansi -e {bg: $color}) + let r = $'(ansi -e {fg: $color, bg: $next_color})(char nf_left_segment)' + $'($l)($s)($r)' + } + '>' => { + let r = $'(ansi -e {fg: $color, bg: $next_color})(char nf_left_segment)' + $'($s)($r)' + } + '>>' => { + let r = $'(ansi reset)(ansi -e {fg: $color})(char nf_left_segment)' + $'($s)($r)' + } + '<'|'<<' => { + let l = $'(ansi -e {fg: $color})(char nf_right_segment)(ansi -e {bg: $color})' + $'($l)($s)' + } + } + } + } + } +} + +def left_prompt [segment] { + let decorator = decorator + let segment = $segment + | each {|x| + [$x.color (get_component $x)] + } + {|| + let segment = $segment + | reduce -f [] {|x, acc| + let y = do $x.1 $x.0 + if $y.1 == null { + $acc + } else { + $acc | append [$y] + } + } + let stop = ($segment | length) - 1 + let cs = $segment | each {|x| $x.0 } | append $segment.0.0 | range 1.. + $segment + | zip $cs + | enumerate + | each {|x| + if $x.index == $stop { + do $decorator $x.item.0.1 '>>' $x.item.0.0 $x.item.1 + } else if $x.index == 0 { + do $decorator $x.item.0.1 '|>' $x.item.0.0 $x.item.1 + } else { + do $decorator $x.item.0.1 '>' $x.item.0.0 $x.item.1 + } + } + | str join + } +} + +def right_prompt [segment] { + let decorator = decorator + let segment = $segment + | each {|x| + [$x.color (get_component $x)] + } + {|| + $segment + | reduce -f [] {|x,acc| + let y = do $x.1 $x.0 + if $y.1 == null { + $acc + } else { + $acc | append [$y] + } + } + | enumerate + | each {|x| + if $x.index == 0 { + do $decorator $x.item.1 '<<' $x.item.0 + } else { + do $decorator $x.item.1 '<' $x.item.0 + } + } + | str join + } +} + +def up_prompt [segment] { + let thunk = $segment + | each {|y| $y | each {|x| get_component $x } } + { || + let ss = $thunk + | each {|y| + $y + | reduce -f [] {|x, acc| + let y = (do $x null) + if $y.1 == null { + $acc + } else { + $acc | append $y.1 + } + } + | str join $'(ansi light_yellow)|' + } + # TODO: length of unicode char is 3 + let fl = (term size).columns - ($ss | str join ''| ansi strip | str length) | math abs + $ss | str join $"(ansi xterm_grey)('' | fill -c '-' -w $fl)(ansi reset)" + } +} + +export def default_env [name value] { + if $name in $env { + $env | get $name + } else { + $value + } +} + +export def --env init [] { + match $env.NU_POWER_FRAME { + 'default' => { + $env.PROMPT_COMMAND = (wraptime + 'left' + (left_prompt $env.NU_POWER_SCHEMA.0) + ) + $env.PROMPT_COMMAND_RIGHT = (wraptime + 'right' + (right_prompt $env.NU_POWER_SCHEMA.1) + ) + } + 'fill' => { + $env.PROMPT_COMMAND = (up_prompt $env.NU_POWER_SCHEMA) + } + } + + $env.PROMPT_INDICATOR = {|| + match $env.NU_POWER_DECORATOR { + 'plain' => { "> " } + _ => { " " } + } + } + $env.PROMPT_INDICATOR_VI_INSERT = {|| ": " } + $env.PROMPT_INDICATOR_VI_NORMAL = {|| "> " } + $env.PROMPT_MULTILINE_INDICATOR = {|| + match $env.NU_POWER_DECORATOR { + 'plain' => { "::: " } + _ => { $"(char haze) " } + } + } + + $env.config = ( $env.config | update menus ($env.config.menus + | each {|x| + if ($x.marker in $env.NU_POWER_MENU_MARKER) { + let c = ($env.NU_POWER_MENU_MARKER | get $x.marker) + $x | upsert marker $'(ansi -e {fg: $c})(char nf_left_segment_thin) ' + } else { + $x + } + } + )) + + hook +} + +export def --env set [name setup] { + $env.NU_POWER_THEME = (if ($setup.theme? | is-empty) { + $env.NU_POWER_THEME + } else { + let n = $setup.theme + | transpose k v + | reduce -f {} {|it, acc| + $acc | insert $it.k (ansi -e {fg: $it.v}) + } + let o = if $name in $env.NU_POWER_THEME { + $env.NU_POWER_THEME | get $name + } else { + {} + } + $env.NU_POWER_THEME + | upsert $name ($o | merge $n) + }) + + $env.NU_POWER_CONFIG = (if ($setup.config? | is-empty) { + $env.NU_POWER_CONFIG + } else { + let n = $setup.config + | transpose k v + | reduce -f {} {|it, acc| + $acc | insert $it.k $it.v + } + let o = if $name in $env.NU_POWER_CONFIG { + $env.NU_POWER_CONFIG | get $name + } else { + {} + } + $env.NU_POWER_CONFIG + | upsert $name ($o | merge $n) + }) +} + +export def --env register [name source setup] { + set $name $setup + + $env.NU_PROMPT_COMPONENTS = ( + $env.NU_PROMPT_COMPONENTS | upsert $name {|| $source } + ) +} + +export def --env inject [pos idx define setup?] { + let prev = $env.NU_POWER_SCHEMA | get $pos + let next = if $idx == 0 { + $prev | prepend $define + } else { + [ + ($prev | range 0..($idx - 1)) + $define + ($prev | range $idx..) + ] | flatten + } + + $env.NU_POWER_SCHEMA = ( + $env.NU_POWER_SCHEMA + | update $pos $next + ) + + let kind = $define.source + + if ($setup.theme? | is-not-empty) { + let prev_theme = $env.NU_POWER_THEME | get $kind + let prev_cols = $prev_theme | columns + let next_theme = $setup.theme | transpose k v + for n in $next_theme { + if $n.k in $prev_cols { + $env.NU_POWER_THEME = ( + $env.NU_POWER_THEME | update $kind {|conf| + $conf | get $kind | update $n.k (ansi -e {fg: $n.v}) + } + ) + } + } + } + + if ($setup.config? | is-not-empty) { + let prev_cols = $env.NU_POWER_CONFIG | get $kind | columns + for n in ($setup.config | transpose k v) { + if $n.k in $prev_cols { + $env.NU_POWER_CONFIG = ( + $env.NU_POWER_CONFIG | update $kind {|conf| + $conf | get $kind | update $n.k $n.v + } + ) + } + } + } +} + +export def --env eject [] { + "power eject not implement" +} + +export def --env hook [] { + $env.config = ( $env.config | upsert hooks.env_change { |config| + let init = [{|before, after| if ($before | is-not-empty) { init } }] + $config.hooks.env_change + | upsert NU_POWER_SCHEMA $init + | upsert NU_POWER_FRAME $init + | upsert NU_POWER_DECORATOR $init + | upsert NU_POWER_MENU_MARKER $init + | upsert NU_POWER_BENCHMARK [{ |before, after| + if ($before | is-not-empty) { + init + rm -f ~/.cache/nushell/power_time.log + } + }] + + # NU_POWER_THEME + }) +} + +export-env { + $env.NU_POWER_BENCHMARK = false + + $env.NU_POWER_SCHEMA = (default_env + NU_POWER_SCHEMA + [ + [ + {source: pwd, color: '#353230'} + ] + [ + {source: proxy, color: 'dark_gray'} + {source: host, color: '#504945'} + {source: time, color: '#353230'} + ] + ] + ) + + $env.NU_POWER_FRAME = (default_env + NU_POWER_FRAME + 'default' # default | fill + ) + + $env.NU_POWER_DECORATOR = (default_env + NU_POWER_DECORATOR + 'power' # power | plain + ) + + $env.NU_POWER_MENU_MARKER = (default_env + NU_POWER_MENU_MARKER + { + "| " : 'green' + ": " : 'yellow' + "# " : 'blue' + "? " : 'red' + } + ) + + $env.NU_POWER_THEME = (default_env + NU_POWER_THEME + { + pwd: { + default: (ansi xterm_green) + out_home: (ansi xterm_gold3b) + vcs: (ansi xterm_teal) + } + proxy: { + on: (ansi yellow) + } + host: { + is_admin: (ansi yellow) + default: (ansi blue) + } + time: { + fst: (ansi xterm_tan) + snd: (ansi xterm_aqua) + } + } + ) + + $env.NU_POWER_CONFIG = (default_env + NU_POWER_CONFIG + { + time: { + style: null + } + } + ) + + $env.NU_PROMPT_COMPONENTS = { + pwd: (pwd_abbr) + proxy: (proxy_stat) + host: (host_abbr) + time: (time_segment) + } +} + diff --git a/modules/prompt/powerline/plugin/atuin.nu b/modules/prompt/powerline/plugin/atuin.nu new file mode 100644 index 000000000..36be28dac --- /dev/null +++ b/modules/prompt/powerline/plugin/atuin.nu @@ -0,0 +1,18 @@ +export def atuin_stat [] { + {|bg| + let theme = $env.NU_POWER_THEME.atuin + if ($env.ATUIN_SESSION? | is-not-empty) { + [$bg ''] + } else { + ['#504945' ''] + } + } +} + +export-env { + power register atuin (atuin_stat) { + theme: { + on: white + } + } +} diff --git a/modules/prompt/powerline/plugin/git.nu b/modules/prompt/powerline/plugin/git.nu new file mode 100644 index 000000000..a687a4e73 --- /dev/null +++ b/modules/prompt/powerline/plugin/git.nu @@ -0,0 +1,157 @@ +### git +def _git_status [] { + # TODO: show-stash + let raw_status = (do -i { git --no-optional-locks status --porcelain=2 --branch | lines }) + + let stashes = (do -i { git stash list | lines | length }) + + mut status = { + idx_added_staged : 0 + idx_modified_staged : 0 + idx_deleted_staged : 0 + idx_renamed : 0 + idx_type_changed : 0 + wt_untracked : 0 + wt_modified : 0 + wt_deleted : 0 + wt_type_changed : 0 + wt_renamed : 0 + ignored : 0 + conflicts : 0 + ahead : 0 + behind : 0 + stashes : $stashes + repo_name : no_repository + tag : no_tag + branch : no_branch + remote : '' + } + + if ($raw_status | is-empty) { return $status } + + for s in $raw_status { + let r = ($s | split row ' ') + match $r.0 { + '#' => { + match ($r.1 | str substring 7..) { + 'oid' => { + $status.commit_hash = ($r.2 | str substring 0..<8) + } + 'head' => { + $status.branch = $r.2 + } + 'upstream' => { + $status.remote = $r.2 + } + 'ab' => { + $status.ahead = ($r.2 | into int) + $status.behind = ($r.3 | into int | math abs) + } + } + } + '1'|'2' => { + match ($r.1 | str substring 0..<1) { + 'A' => { + $status.idx_added_staged += 1 + } + 'M' => { + $status.idx_modified_staged += 1 + } + 'R' => { + $status.idx_renamed += 1 + } + 'D' => { + $status.idx_deleted_staged += 1 + } + 'T' => { + $status.idx_type_changed += 1 + } + } + match ($r.1 | str substring 1..<2) { + 'M' => { + $status.wt_modified += 1 + } + 'R' => { + $status.wt_renamed += 1 + } + 'D' => { + $status.wt_deleted += 1 + } + 'T' => { + $status.wt_type_changed += 1 + } + } + } + '?' => { + $status.wt_untracked += 1 + } + 'u' => { + $status.conflicts += 1 + } + } + } + + $status +} + +export def git_stat [] { + {|bg| + if (git rev-parse --is-inside-work-tree | complete).exit_code > 0 { + return [$bg ''] + } + + let status = _git_status + + if $status.branch == 'no_branch' { return [$bg ''] } + + let theme = $env.NU_POWER_THEME.git + let branch = if ($status.remote | is-empty) { + $'($theme.no_upstream)($status.branch)' + } else { + $'($theme.default)($status.branch)' + } + + let summary = ( + $env.NU_PROMPT_GIT_FORMATTER + | reduce -f "" {|x, acc| + let y = ($status | get $x.0) + if $y > 0 { + $acc + $"(ansi $'light_($x.2)_dimmed')($x.1)($y)" + } else { + $acc + } + }) + + [$bg $'($branch)($summary)'] + } +} + +export-env { + $env.NU_PROMPT_GIT_FORMATTER = (power default_env + NU_PROMPT_GIT_FORMATTER + [ + [behind (char branch_behind) yellow] + [ahead (char branch_ahead) yellow] + [conflicts ! red] + [ignored _ purple] + [idx_added_staged + green] + [idx_modified_staged ~ green] + [idx_deleted_staged - green] + [idx_renamed % green] + [idx_type_changed * green] + [wt_untracked + red] + [wt_modified ~ red] + [wt_deleted - red] + [wt_renamed % red] + [wt_type_changed * red] + [stashes = blue] + ] + ) + + power register git (git_stat) { + theme: { + default : blue + no_upstream: red + } + } +} diff --git a/modules/prompt/powerline/plugin/kube.nu b/modules/prompt/powerline/plugin/kube.nu new file mode 100644 index 000000000..d651b5526 --- /dev/null +++ b/modules/prompt/powerline/plugin/kube.nu @@ -0,0 +1,64 @@ +### kubernetes +export def ensure-cache [cache path action] { + let ts = (do -i { ls ($path | into glob) | sort-by modified | reverse | get 0.modified }) + if ($ts | is-empty) { return false } + let tc = (do -i { ls $cache | get 0.modified }) + if not (($cache | path exists) and ($ts < $tc)) { + mkdir ($cache | path dirname) + do $action | save -f $cache + } + open $cache +} + +def "kube ctx" [] { + mut cache = '' + mut file = '' + if ($env.KUBECONFIG? | is-empty) { + $cache = ([$nu.data-dir 'cache' 'power'] | path join 'kube.json') + $file = $"($env.HOME)/.kube/config" + } else { + $cache = ([$nu.data-dir 'cache' 'power'] | path join $"kube-($env.KUBECONFIG | str replace -a '/' ':').json") + $file = $env.KUBECONFIG + } + if not ($file | path exists) { return null } + ensure-cache $cache $file { + do -i { + kubectl config get-contexts + | from ssv -a + | filter {|x| $x.CURRENT | is-not-empty } + | get 0 + } + } +} + +def kube_stat [] { + {|bg| + let ctx = kube ctx + if ($ctx | is-empty) { + [$bg ""] + } else { + let theme = $env.NU_POWER_THEME.kube + let config = $env.NU_POWER_CONFIG.kube + let p = if $config.reverse { + $"($theme.namespace)($ctx.NAMESPACE)($theme.separator)($config.separator)($theme.context)($ctx.NAME)" + } else { + $"($theme.context)($ctx.NAME)($theme.separator)($config.separator)($theme.namespace)($ctx.NAMESPACE)" + } + [$bg $"($p)"] + } + } +} + +export-env { + power register kube (kube_stat) { + theme: { + context: cyan + separator: purple + namespace: yellow + } + config: { + reverse: false + separator: ':' + } + } +}