diff --git a/dot_config/nushell/config.nu.tmpl b/dot_config/nushell/config.nu.tmpl new file mode 100644 index 0000000..808a3ce --- /dev/null +++ b/dot_config/nushell/config.nu.tmpl @@ -0,0 +1,104 @@ +let carapace_completer = {|spans: list| + ^carapace $spans.0 nushell ...$spans + | from json + | if ($in | default [] | where value =~ '^-.*ERR$' | is-empty) { $in } else { null } +} + +$env.config = { + show_banner: false + buffer_editor: "nvim" + edit_mode: vi + + cursor_shape: { + vi_insert: line + vi_normal: block + } + + history: { + max_size: 100_000 + sync_on_enter: true + file_format: "plaintext" + } + + completions: { + case_sensitive: false + quick: true + partial: true + algorithm: "fuzzy" + external: { + enable: true + completer: $carapace_completer + } + } +} + +plugin use gstat +plugin use polars +plugin use formats +plugin use query +plugin use skim +plugin use dns +plugin use audio +plugin use highlight +plugin use file +plugin use ls_colorize +plugin use regex +plugin use json_path +plugin use semver +plugin use parquet + +# All files including hidden, sorted by type then name +def ll [path?: path] { + ls -a ($path | default .) | sort-by type name +} + +# Files only, sorted by size descending +def lf [path?: path] { + ls -a ($path | default .) | where type == file | sort-by size -r +} + +# Directories only, sorted by name +def ld [path?: path] { + ls -a ($path | default .) | where type == dir | sort-by name +} + +# Listing with per-file git status column +def lg [path?: path] { + let files = ls -a ($path | default .) | sort-by type name + let dirty = try { + ^git status --porcelain + | lines + | each {|l| {name: ($l | str substring 3..), git: ($l | str substring 0..2 | str trim)}} + } catch { [] } + if ($dirty | is-empty) { $files } else { + $files | join $dirty name --left + } +} + +# Repo-level git status as a structured record +def gs [] { gstat } + +# Show all custom commands with their descriptions +def nu-help [] { + help commands + | where command_type == "custom" + | select name description + | sort-by name +} + +alias ez = ^eza --icons +alias ezl = ^eza -la --git --icons + +alias cat = bat --paging=never + +source ($nu.default-config-dir | path join "zoxide.nu") + +source ($nu.default-config-dir | path join "k8s.nu") +source ($nu.default-config-dir | path join "pkg.nu") +source ($nu.default-config-dir | path join "git.nu") +source ($nu.default-config-dir | path join "sys.nu") +source ($nu.default-config-dir | path join "hypr.nu") +source ($nu.default-config-dir | path join "dev.nu") + +use ~/.config/nushell/themes/{{ .chezmoi.config.data.theme }}.nu +source ($nu.default-config-dir | path join "prompt.nu") diff --git a/dot_config/nushell/dev.nu b/dot_config/nushell/dev.nu new file mode 100644 index 0000000..56e9ae6 --- /dev/null +++ b/dot_config/nushell/dev.nu @@ -0,0 +1,9 @@ +# Cargo workspace crates as a table — run from workspace root +def cargo-ws [] { + ^cargo metadata --format-version 1 --no-deps 2>/dev/null + | from json + | get packages + | select name version manifest_path + | each {|p| $p | upsert path ($p.manifest_path | path dirname) | reject manifest_path} + | sort-by name +} diff --git a/dot_config/nushell/env.nu b/dot_config/nushell/env.nu new file mode 100644 index 0000000..4b7f967 --- /dev/null +++ b/dot_config/nushell/env.nu @@ -0,0 +1,2 @@ +$env.PNPM_HOME = ($env.HOME | path join ".local" "share" "pnpm") +$env.PATH = ($env.PATH | prepend $env.PNPM_HOME) diff --git a/dot_config/nushell/git.nu b/dot_config/nushell/git.nu new file mode 100644 index 0000000..04e167c --- /dev/null +++ b/dot_config/nushell/git.nu @@ -0,0 +1,44 @@ +# List branches with upstream, last commit date and subject +def gb [] { + ^git for-each-ref --format "%(refname:short)\t%(upstream:short)\t%(creatordate:relative)\t%(subject)" refs/heads + | lines + | each {|l| + let p = $l | split row "\t" + { + branch: $p.0 + upstream: (if ($p.1 | is-empty) { null } else { $p.1 }) + date: $p.2 + subject: $p.3 + } + } +} + +# Git log as a table — default 20 entries, pass n to change +def gl [n: int = 20] { + ^git log --format "%H\t%as\t%an\t%s" -n $n + | lines + | each {|l| + let p = $l | split row "\t" + { + hash: ($p.0 | str substring 0..7) + date: $p.1 + author: $p.2 + subject: $p.3 + } + } +} + +# Diff stats as a table (unstaged by default; --cached for staged) +def gd [--cached] { + let flags = if $cached { [--cached] } else { [] } + ^git diff --numstat ...$flags + | lines + | each {|l| + let p = $l | split row "\t" + { + ins: ($p.0 | into int) + del: ($p.1 | into int) + file: $p.2 + } + } +} diff --git a/dot_config/nushell/hypr.nu b/dot_config/nushell/hypr.nu new file mode 100644 index 0000000..a23271f --- /dev/null +++ b/dot_config/nushell/hypr.nu @@ -0,0 +1,27 @@ +# All open windows as a table (class, title, workspace, floating, pid, pos, size) +def wins [] { + ^hyprctl clients -j | from json + | each {|w| { + class: $w.class + title: $w.title + workspace: $w.workspace.name + floating: $w.floating + pid: $w.pid + pos: $"($w.at.0),($w.at.1)" + size: $"($w.size.0)x($w.size.1)" + }} +} + +# Workspaces with window count, sorted by id +def wsps [] { + ^hyprctl workspaces -j | from json + | select id name monitor windows hasfullscreen + | sort-by id +} + +# Currently focused window as a record +def hypr-active [] { + ^hyprctl activewindow -j | from json + | select class title workspace floating pid + | upsert workspace { get workspace | get name } +} diff --git a/dot_config/nushell/k8s.nu b/dot_config/nushell/k8s.nu new file mode 100644 index 0000000..5ef3eb5 --- /dev/null +++ b/dot_config/nushell/k8s.nu @@ -0,0 +1,87 @@ +# Pods as a table — optional namespace (-n) or all namespaces (-A) +def kgp [namespace?: string, --all (-A)] { + let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] } + ^kubectl get pods ...$flags -o json | from json | get items + | each {|p| + let cs = $p.status.containerStatuses? | default [] + { + name: $p.metadata.name + namespace: $p.metadata.namespace + phase: $p.status.phase + ready: $"(($cs | where ready == true | length))/(($cs | length))" + restarts: ($cs | each { $in.restartCount } | math sum) + age: ((date now) - ($p.metadata.creationTimestamp | into datetime)) + } + } +} + +# Deployments as a table — optional namespace (-n) or all namespaces (-A) +def kgd [namespace?: string, --all (-A)] { + let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] } + ^kubectl get deployments ...$flags -o json | from json | get items + | each {|d| { + name: $d.metadata.name + namespace: $d.metadata.namespace + ready: $"($d.status.readyReplicas? | default 0)/($d.spec.replicas)" + up-to-date: ($d.status.updatedReplicas? | default 0) + available: ($d.status.availableReplicas? | default 0) + age: ((date now) - ($d.metadata.creationTimestamp | into datetime)) + }} +} + +# Services as a table — optional namespace (-n) or all namespaces (-A) +def kgs [namespace?: string, --all (-A)] { + let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] } + ^kubectl get services ...$flags -o json | from json | get items + | each {|s| { + name: $s.metadata.name + namespace: $s.metadata.namespace + type: $s.spec.type + cluster-ip: $s.spec.clusterIP + ports: ($s.spec.ports | each { $"($in.port):($in.targetPort)/($in.protocol)" } | str join ",") + age: ((date now) - ($s.metadata.creationTimestamp | into datetime)) + }} +} + +# Nodes as a table with roles and kubelet version +def kgn [] { + ^kubectl get nodes -o json | from json | get items + | each {|n| + let ready = ($n.status.conditions | where type == "Ready" | first | get status) + let roles = ($n.metadata.labels | transpose key value + | where ($in.key | str starts-with "node-role.kubernetes.io/") + | each { $in.key | str replace "node-role.kubernetes.io/" "" } + | str join ",") + { + name: $n.metadata.name + status: (if $ready == "True" { "Ready" } else { "NotReady" }) + roles: (if ($roles | is-empty) { "" } else { $roles }) + version: $n.status.nodeInfo.kubeletVersion + age: ((date now) - ($n.metadata.creationTimestamp | into datetime)) + } + } +} + +# Kubectl contexts as a table, current marked with * +def ctx [] { + let current = (^kubectl config current-context | str trim) + ^kubectl config view -o json | from json | get contexts + | each {|c| { + current: (if $c.name == $current { "*" } else { "" }) + name: $c.name + cluster: $c.context.cluster + user: $c.context.user + namespace: ($c.context.namespace? | default "default") + }} +} + +# Switch kubectl context +def ctx-use [name: string] { + ^kubectl config use-context $name +} + +# Helm releases as a table — optional namespace (-n) or all namespaces (-A) +def helm-ls [namespace?: string, --all (-A)] { + let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] } + ^helm list ...$flags -o json | from json +} diff --git a/dot_config/nushell/pkg.nu b/dot_config/nushell/pkg.nu new file mode 100644 index 0000000..7767b25 --- /dev/null +++ b/dot_config/nushell/pkg.nu @@ -0,0 +1,57 @@ +# Installed packages as a table, optionally filtered by name +def pkgl [query?: string] { + ^paru -Q --color never + | lines + | each {|l| let p = $l | split row ' '; {name: $p.0, version: $p.1}} + | if $query != null { where name =~ $query } else { $in } +} + +# Packages with available upgrades (name, current, new) +def pkgu [] { + ^paru -Qu --color never + | lines + | each {|l| + let p = $l | split row ' ' + {name: $p.0, current: $p.1, new: $p.3} + } +} + +# Search repos and AUR (repo, name, version, desc) +def pkgs [query: string] { + ^paru -Ss --color never $query + | lines + | chunks 2 + | each {|pair| + let header = $pair.0 + let desc = ($pair.1? | default "" | str trim) + let parts = $header | split row '/' + let name_ver = ($parts.1 | split row ' ') + { + repo: $parts.0 + name: $name_ver.0 + version: $name_ver.1 + desc: $desc + } + } +} + +# Package info as a structured record +def pkgi [pkg: string] { + ^paru -Qi --color never $pkg + | lines + | each {|l| + let parts = $l | split row ':' + if ($parts | length) >= 2 { + {key: ($parts.0 | str trim), value: ($parts | skip 1 | str join ':' | str trim)} + } + } + | compact + | transpose -r -d +} + +# Which package owns a file +def pkgo [file: string] { + ^paru -Qo --color never $file + | parse '{file} is owned by {name} {version}' + | first +} diff --git a/dot_config/nushell/prompt.nu.tmpl b/dot_config/nushell/prompt.nu.tmpl new file mode 100644 index 0000000..a780ded --- /dev/null +++ b/dot_config/nushell/prompt.nu.tmpl @@ -0,0 +1,18 @@ +$env.PROMPT_COMMAND = {|| + let dir = ($env.PWD | str replace $env.HOME "~") + let branch = (try { ^git rev-parse --abbrev-ref HEAD err> /dev/null | str trim } catch { "" }) + let dirty = (try { + if (^git status --porcelain err> /dev/null | str trim | is-empty) { "" } else { "*" } + } catch { "" }) + + let git_seg = if ($branch | is-empty) { "" } else { + $" (ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#00ff99{{- else }}#00b377{{- end }}' })($branch)(ansi { fg: '#ff0044' })($dirty)(ansi reset)" + } + $"(ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#00eaff{{- else }}#007a88{{- end }}' })($dir)(ansi reset)($git_seg)" +} + +$env.PROMPT_INDICATOR_VI_INSERT = $" (ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#00ff99{{- else }}#00b377{{- end }}' })❯(ansi reset) " +$env.PROMPT_INDICATOR_VI_NORMAL = $" (ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#ffb700{{- else }}#d18f00{{- end }}' })❮(ansi reset) " +$env.TRANSIENT_PROMPT_COMMAND = $"(ansi { fg: '#737373' })❯(ansi reset) " +$env.TRANSIENT_PROMPT_INDICATOR_VI_INSERT = "" +$env.TRANSIENT_PROMPT_INDICATOR_VI_NORMAL = "" diff --git a/dot_config/nushell/sys.nu b/dot_config/nushell/sys.nu new file mode 100644 index 0000000..8b31573 --- /dev/null +++ b/dot_config/nushell/sys.nu @@ -0,0 +1,22 @@ +# Processes sorted by CPU, optionally filtered by name +def psk [query?: string] { + ps | sort-by cpu -r + | if $query != null { where name =~ $query } else { $in } +} + +# Listening TCP ports with process info +def ports [] { + ^ss -tlnp + | detect columns + | rename state recv-q send-q local peer process + | each {|r| $r | upsert port (try { $r.local | split row ":" | last | into int } catch { null })} + | select state local port process + | sort-by port +} + +# Environment variables as a filterable sorted table +def envs [query?: string] { + $env | transpose key value + | if $query != null { where key =~ $query } else { $in } + | sort-by key +} diff --git a/dot_config/nushell/themes/apex-aeon.nu b/dot_config/nushell/themes/apex-aeon.nu new file mode 100644 index 0000000..fdc9ba4 --- /dev/null +++ b/dot_config/nushell/themes/apex-aeon.nu @@ -0,0 +1,68 @@ +# Apex Aeon — Nushell color config +# Usage: add `use /path/to/apex-aeon.nu` to config.nu + +export-env { + $env.config.color_config = { + + # --- Chrome --- + separator: "#737373" + leading_trailing_space_bg: { bg: "#737373" } + header: { fg: "#ff0044" attr: b } + empty: "#737373" + row_index: { fg: "#737373" attr: b } + hints: "#a0a0a0" + + # --- Primitive types --- + string: { fg: "#00b377" } + int: { fg: "#007a88" } + float: { fg: "#007a88" } + bool: { fg: "#d18f00" } + filesize: { fg: "#007a88" } + duration: { fg: "#007a88" } + datetime: { fg: "#007a88" } + range: { fg: "#0a0a0a" } + record: { fg: "#0a0a0a" } + list: { fg: "#0a0a0a" } + block: { fg: "#7a3cff" } + nothing: { fg: "#737373" } + binary: { fg: "#007a88" } + + # --- Shapes (REPL syntax highlighting) --- + shape_string: { fg: "#00b377" } + shape_string_interpolation: { fg: "#00b377" attr: b } + shape_int: { fg: "#007a88" } + shape_float: { fg: "#007a88" } + shape_bool: { fg: "#d18f00" } + shape_datetime: { fg: "#007a88" } + shape_binary: { fg: "#007a88" } + shape_filepath: { fg: "#007a88" } + shape_directory: { fg: "#007a88" } + shape_globpattern: { fg: "#007a88" } + shape_glob_interpolation: { fg: "#007a88" } + shape_nothing: { fg: "#737373" } + shape_variable: { fg: "#0a0a0a" } + shape_vardecl: { fg: "#0a0a0a" } + shape_operator: { fg: "#0a0a0a" } + shape_external: { fg: "#005577" } + shape_externalarg: { fg: "#0a0a0a" } + shape_internalcall: { fg: "#005577" attr: b } + shape_signature: { fg: "#005577" } + shape_flag: { fg: "#d18f00" } + shape_pipe: { fg: "#d18f00" attr: b } + shape_redirection: { fg: "#d18f00" } + shape_and: { fg: "#7a3cff" attr: b } + shape_or: { fg: "#7a3cff" attr: b } + shape_keyword: { fg: "#7a3cff" attr: b } + shape_block: { fg: "#7a3cff" } + shape_closure: { fg: "#7a3cff" } + shape_list: { fg: "#0a0a0a" } + shape_record: { fg: "#0a0a0a" } + shape_table: { fg: "#0a0a0a" } + shape_range: { fg: "#0a0a0a" } + shape_literal: { fg: "#0a0a0a" } + shape_custom: { fg: "#0a0a0a" } + shape_match_pattern: { fg: "#d18f00" } + shape_matching_brackets: { attr: u } + shape_garbage: { fg: "#ff0044" attr: b } + } +} \ No newline at end of file diff --git a/dot_config/nushell/themes/apex-neon.nu b/dot_config/nushell/themes/apex-neon.nu new file mode 100644 index 0000000..5f0fc08 --- /dev/null +++ b/dot_config/nushell/themes/apex-neon.nu @@ -0,0 +1,68 @@ +# Apex Neon — Nushell color config +# Usage: add `use /path/to/apex-neon.nu` to config.nu + +export-env { + $env.config.color_config = { + + # --- Chrome --- + separator: "#737373" + leading_trailing_space_bg: { bg: "#262626" } + header: { fg: "#ff0044" attr: b } + empty: "#737373" + row_index: { fg: "#737373" attr: b } + hints: "#404040" + + # --- Primitive types --- + string: { fg: "#00ff99" } + int: { fg: "#00eaff" } + float: { fg: "#00eaff" } + bool: { fg: "#ffb700" } + filesize: { fg: "#00eaff" } + duration: { fg: "#00eaff" } + datetime: { fg: "#00eaff" } + range: { fg: "#ededed" } + record: { fg: "#ededed" } + list: { fg: "#ededed" } + block: { fg: "#9d00ff" } + nothing: { fg: "#737373" } + binary: { fg: "#00eaff" } + + # --- Shapes (REPL syntax highlighting) --- + shape_string: { fg: "#00ff99" } + shape_string_interpolation: { fg: "#00ff99" attr: b } + shape_int: { fg: "#00eaff" } + shape_float: { fg: "#00eaff" } + shape_bool: { fg: "#ffb700" } + shape_datetime: { fg: "#00eaff" } + shape_binary: { fg: "#00eaff" } + shape_filepath: { fg: "#00eaff" } + shape_directory: { fg: "#00eaff" } + shape_globpattern: { fg: "#00eaff" } + shape_glob_interpolation: { fg: "#00eaff" } + shape_nothing: { fg: "#737373" } + shape_variable: { fg: "#ededed" } + shape_vardecl: { fg: "#ededed" } + shape_operator: { fg: "#ededed" } + shape_external: { fg: "#0088cc" } + shape_externalarg: { fg: "#ededed" } + shape_internalcall: { fg: "#0088cc" attr: b } + shape_signature: { fg: "#0088cc" } + shape_flag: { fg: "#ffb700" } + shape_pipe: { fg: "#ffb700" attr: b } + shape_redirection: { fg: "#ffb700" } + shape_and: { fg: "#9d00ff" attr: b } + shape_or: { fg: "#9d00ff" attr: b } + shape_keyword: { fg: "#9d00ff" attr: b } + shape_block: { fg: "#9d00ff" } + shape_closure: { fg: "#9d00ff" } + shape_list: { fg: "#ededed" } + shape_record: { fg: "#ededed" } + shape_table: { fg: "#ededed" } + shape_range: { fg: "#ededed" } + shape_literal: { fg: "#ededed" } + shape_custom: { fg: "#ededed" } + shape_match_pattern: { fg: "#ffb700" } + shape_matching_brackets: { attr: u } + shape_garbage: { fg: "#ff0044" attr: b } + } +} \ No newline at end of file diff --git a/dot_config/nushell/zoxide.nu b/dot_config/nushell/zoxide.nu new file mode 100644 index 0000000..11f08c0 --- /dev/null +++ b/dot_config/nushell/zoxide.nu @@ -0,0 +1,70 @@ +# Code generated by zoxide. DO NOT EDIT. + +# ============================================================================= +# +# Hook configuration for zoxide. +# + +# Initialize hook to add new entries to the database. +export-env { + $env.config = ( + $env.config? + | default {} + | upsert hooks { default {} } + | upsert hooks.env_change { default {} } + | upsert hooks.env_change.PWD { default [] } + ) + let __zoxide_hooked = ( + $env.config.hooks.env_change.PWD | any { try { get __zoxide_hook } catch { false } } + ) + if not $__zoxide_hooked { + $env.config.hooks.env_change.PWD = ($env.config.hooks.env_change.PWD | append { + __zoxide_hook: true, + code: {|_, dir| ^zoxide add -- $dir} + }) + } +} + +# ============================================================================= +# +# When using zoxide with --no-cmd, alias these internal functions as desired. +# + +# Jump to a directory using only keywords. +def --env --wrapped __zoxide_z [...rest: string] { + let path = match $rest { + [] => {'~'}, + [ '-' ] => {'-'}, + [ $arg ] if ($arg | path expand | path type) == 'dir' => {$arg} + _ => { + ^zoxide query --exclude $env.PWD -- ...$rest | str trim -r -c "\n" + } + } + cd $path +} + +# Jump to a directory using interactive search. +def --env --wrapped __zoxide_zi [...rest:string] { + cd $'(^zoxide query --interactive -- ...$rest | str trim -r -c "\n")' +} + +# ============================================================================= +# +# Commands for zoxide. Disable these using --no-cmd. +# + +alias z = __zoxide_z +alias zi = __zoxide_zi + +# ============================================================================= +# +# Add this to your env file (find it by running `$nu.env-path` in Nushell): +# +# zoxide init nushell | save -f ~/.zoxide.nu +# +# Now, add this to the end of your config file (find it by running +# `$nu.config-path` in Nushell): +# +# source ~/.zoxide.nu +# +# Note: zoxide only supports Nushell v0.89.0+.