322 lines
17 KiB
Nix
322 lines
17 KiB
Nix
{
|
|
|
|
inputs.nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
|
|
|
outputs =
|
|
{ self, nixpkgs }:
|
|
let
|
|
forAllSystems =
|
|
f: nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (sys: f nixpkgs.legacyPackages.${sys});
|
|
in
|
|
{
|
|
devShells = forAllSystems (pkgs: {
|
|
default = pkgs.mkShell {
|
|
packages = with pkgs; [
|
|
rustc
|
|
cargo
|
|
rustfmt
|
|
rust-analyzer
|
|
clippy
|
|
];
|
|
};
|
|
});
|
|
|
|
packages = forAllSystems (pkgs: {
|
|
default = pkgs.rustPlatform.buildRustPackage {
|
|
pname = "inshellah";
|
|
version = "0.1.1";
|
|
src = pkgs.lib.cleanSource ./.;
|
|
cargoLock.lockFile = ./Cargo.lock;
|
|
meta = {
|
|
description = "nushell completion indexer";
|
|
mainProgram = "inshellah";
|
|
};
|
|
};
|
|
});
|
|
|
|
nixosModules.default =
|
|
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
{
|
|
imports = [ ./nix/module.nix ];
|
|
programs.inshellah.package = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
|
programs.inshellah.snippet = ''
|
|
let inshellah_complete = { |spans|
|
|
let completions = (^inshellah complete ...$spans) | from json
|
|
let span_len = ($spans | length)
|
|
let last_span = if $span_len > 0 { $spans | last } else { "" }
|
|
let prev_span = if $span_len >= 2 { $spans | get ($span_len - 2) } else { "" }
|
|
let sub = if $span_len >= 2 { $spans | get 1 } else { "" }
|
|
# dynamic completions — fire only when the static index has nothing.
|
|
let additional = if ($completions == null and $span_len > 0) {
|
|
match $spans.0 {
|
|
"nix" => {
|
|
$env.NIX_GET_COMPLETIONS = $span_len - 1
|
|
let nix_output = $spans | run-external $in | split row -r '\n' | str trim | skip 1
|
|
let entries = if (($nix_output | length) < 6 and
|
|
$last_span =~ "[a-zA-Z][a-zA-Z0-9_-]*#[a-zA-Z][a-zA-Z0-9_-]*") {
|
|
hide-env NIX_GET_COMPLETIONS
|
|
$env.NIX_ALLOW_UNFREE = 1
|
|
$env.NIX_ALLOW_BROKEN = 1
|
|
$nix_output | par-each { |e|
|
|
try {
|
|
{ value: $e, description: (^nix eval --impure $e --apply "f: f.meta.description" err> /dev/null) }
|
|
} catch {
|
|
{ value: $e, description: "" }
|
|
}
|
|
}
|
|
} else {
|
|
$nix_output | each { |e| { value: $e, description: "" } }
|
|
}
|
|
$entries
|
|
}
|
|
"systemctl" => {
|
|
if $span_len < 3 { null } else {
|
|
let scope = if ("--user" in $spans) { [--user] } else { [] }
|
|
^systemctl ...$scope list-units --all --no-pager --plain --full --no-legend $"($last_span)*"
|
|
| lines
|
|
| each { |l|
|
|
let parsed = $l | parse -r '(?P<unit>\S+)\s+\S+\s+\S+\s+\S+\s+(?P<desc>.*)'
|
|
if ($parsed | length) > 0 {
|
|
{value: $parsed.0.unit, description: ($parsed.0.desc | str trim)}
|
|
}
|
|
} | compact
|
|
}
|
|
}
|
|
"journalctl" => {
|
|
# unit-name completion after --unit / -u
|
|
if ($prev_span == "--unit" or $prev_span == "-u") {
|
|
let scope = if ("--user-unit" in $spans or "--user" in $spans) { [--user] } else { [] }
|
|
^systemctl ...$scope list-units --all --no-pager --plain --full --no-legend $"($last_span)*"
|
|
| lines
|
|
| each { |l|
|
|
let parsed = $l | parse -r '(?P<unit>\S+)\s+\S+\s+\S+\s+\S+\s+(?P<desc>.*)'
|
|
if ($parsed | length) > 0 {
|
|
{value: $parsed.0.unit, description: ($parsed.0.desc | str trim)}
|
|
}
|
|
} | compact
|
|
} else { null }
|
|
}
|
|
"coredumpctl" => {
|
|
# unit names (after dump/info/debug/list verbs) and pids
|
|
let unit_verbs = ["dump" "info" "debug" "list"]
|
|
if (($sub in $unit_verbs) and $span_len >= 3) {
|
|
let units = (try {
|
|
^systemctl list-units --all --no-pager --plain --full --no-legend $"($last_span)*"
|
|
| lines
|
|
| each { |l|
|
|
let p = $l | parse -r '(?P<unit>\S+)\s+\S+\s+\S+\s+\S+\s+(?P<desc>.*)'
|
|
if ($p | length) > 0 { { value: $p.0.unit, description: ($p.0.desc | str trim) } }
|
|
} | compact
|
|
} catch { [] })
|
|
let pids = (try {
|
|
^coredumpctl list --no-pager --no-legend
|
|
| lines
|
|
| each { |l|
|
|
let p = $l | split row -r '\s+'
|
|
if ($p | length) >= 5 { { value: $p.4, description: $"PID ($p.4) ($p | get 9? | default "")" } }
|
|
} | compact
|
|
} catch { [] })
|
|
$units | append $pids
|
|
} else { null }
|
|
}
|
|
"loginctl" => {
|
|
# user / session names for loginctl <verb> ...
|
|
let user_verbs = ["user-status" "show-user" "enable-linger" "disable-linger" "kill-user" "terminate-user"]
|
|
let session_verbs = ["session-status" "show-session" "activate" "lock-session" "unlock-session" "terminate-session" "kill-session"]
|
|
if (($sub in $user_verbs) and $span_len >= 3) {
|
|
try {
|
|
^loginctl list-users --no-pager --no-legend
|
|
| lines | each { |l|
|
|
let p = $l | str trim | split row -r '\s+'
|
|
if ($p | length) >= 2 { { value: $p.1, description: $"UID ($p.0)" } }
|
|
} | compact
|
|
} catch { null }
|
|
} else if (($sub in $session_verbs) and $span_len >= 3) {
|
|
try {
|
|
^loginctl list-sessions --no-pager --no-legend
|
|
| lines | each { |l|
|
|
let p = $l | str trim | split row -r '\s+'
|
|
if ($p | length) >= 3 { { value: $p.0, description: $"user ($p.2)" } }
|
|
} | compact
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"machinectl" => {
|
|
let machine_verbs = ["status" "show" "start" "login" "shell" "enable" "disable" "poweroff" "reboot" "terminate" "kill" "bind" "copy-to" "copy-from"]
|
|
if (($sub in $machine_verbs) and $span_len >= 3) {
|
|
try {
|
|
^machinectl list --no-pager --no-legend
|
|
| lines | each { |l|
|
|
let p = $l | str trim | split row -r '\s+'
|
|
if ($p | length) >= 1 { { value: $p.0, description: ($p | get 1? | default "") } }
|
|
} | compact
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"networkctl" => {
|
|
let link_verbs = ["status" "show" "up" "down" "renew" "forcerenew" "reconfigure" "delete"]
|
|
if (($sub in $link_verbs) and $span_len >= 3) {
|
|
try {
|
|
^networkctl list --no-pager --no-legend
|
|
| lines | each { |l|
|
|
let p = $l | str trim | split row -r '\s+'
|
|
if ($p | length) >= 4 { { value: $p.1, description: $"($p.2) ($p.3)" } }
|
|
} | compact
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"hostnamectl" | "timedatectl" | "localectl" => {
|
|
# mostly fixed verb sets — let the static index handle these.
|
|
# left here as an explicit no-op for documentation.
|
|
null
|
|
}
|
|
"ssh" | "scp" | "sftp" => {
|
|
# hostnames from ~/.ssh/config + ~/.ssh/known_hosts
|
|
let cfg_hosts = (try {
|
|
open ~/.ssh/config | lines | each { |l|
|
|
let m = $l | parse -r '(?i)^\s*Host\s+(?P<h>.+)$'
|
|
if ($m | length) > 0 { $m.0.h | split row -r '\s+' } else { [] }
|
|
} | flatten | where { |h| not ($h | str contains '*') and not ($h | is-empty) }
|
|
} catch { [] })
|
|
let known = (try {
|
|
open ~/.ssh/known_hosts | lines | each { |l|
|
|
($l | split row -r '\s+' | get 0? | default "") | split row ','
|
|
} | flatten | where { |h| (not ($h | is-empty)) and (not ($h | str starts-with '|')) and (not ($h | str starts-with '[')) }
|
|
} catch { [] })
|
|
$cfg_hosts | append $known | uniq | each { |h| { value: $h, description: "" } }
|
|
}
|
|
"docker" | "podman" => {
|
|
let need_container = ["exec" "logs" "inspect" "start" "stop" "restart" "rm" "kill" "attach" "cp" "top" "wait" "pause" "unpause" "port" "commit" "diff" "export"]
|
|
let need_image = ["run" "rmi" "tag" "push" "pull" "history" "save" "create"]
|
|
if ($sub in $need_container) {
|
|
try {
|
|
^($spans.0) ps -a --format '{{.Names}}\t{{.Image}}'
|
|
| lines | each { |l|
|
|
let p = $l | split row "\t"
|
|
if ($p | length) >= 2 { { value: $p.0, description: $p.1 } }
|
|
} | compact
|
|
} catch { null }
|
|
} else if ($sub in $need_image) {
|
|
try {
|
|
^($spans.0) images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}'
|
|
| lines | each { |l|
|
|
let p = $l | split row "\t"
|
|
if (($p | length) >= 2) and (not ($p.0 | str ends-with ':<none>')) {
|
|
{ value: $p.0, description: $p.1 }
|
|
}
|
|
} | compact
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"kubectl" => {
|
|
let need_resource = ["get" "describe" "delete" "edit" "logs" "exec" "port-forward" "rollout" "scale" "annotate" "label"]
|
|
if (($sub in $need_resource) and $span_len >= 4) {
|
|
let kind = $spans | get 2
|
|
try {
|
|
^kubectl get $kind --no-headers -o "custom-columns=NAME:.metadata.name"
|
|
| lines | str trim
|
|
| where { |n| not ($n | is-empty) }
|
|
| each { |n| { value: $n, description: $kind } }
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"git" => {
|
|
let need_ref = ["checkout" "switch" "merge" "rebase" "branch" "log" "diff" "show" "reset" "cherry-pick" "revert" "tag" "push" "pull" "blame" "bisect"]
|
|
if (($sub == "worktree") and $span_len >= 3) {
|
|
try {
|
|
^git worktree list --porcelain
|
|
| lines
|
|
| each { |l|
|
|
let m = $l | parse -r '^worktree\s+(?P<p>.+)$'
|
|
if ($m | length) > 0 { { value: $m.0.p, description: "" } }
|
|
} | compact
|
|
} catch { null }
|
|
} else if (($sub in $need_ref) and $span_len >= 3) {
|
|
try {
|
|
^git for-each-ref --format='%(refname:short)%09%(objecttype)%09%(contents:subject)' refs/heads refs/remotes refs/tags
|
|
| lines
|
|
| each { |l|
|
|
let p = $l | split row "\t"
|
|
if ($p | length) >= 3 { { value: $p.0, description: $p.2 } }
|
|
} | compact
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"npm" | "pnpm" | "yarn" => {
|
|
# script names from the nearest package.json
|
|
let wants = (($spans.0 == "yarn") and $span_len >= 2) or ($sub == "run") or ($sub == "run-script")
|
|
if $wants {
|
|
try {
|
|
open package.json | get scripts? | default {} | transpose name cmd
|
|
| each { |row| { value: $row.name, description: $row.cmd } }
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"make" => {
|
|
# Makefile targets — line-leading identifier followed by ':'
|
|
try {
|
|
open Makefile | lines
|
|
| each { |l|
|
|
let m = $l | parse -r '^(?P<t>[A-Za-z0-9_./-]+)\s*:'
|
|
if (($m | length) > 0) and (not ($m.0.t | str starts-with '.')) {
|
|
{ value: $m.0.t, description: "" }
|
|
}
|
|
} | compact | uniq-by value
|
|
} catch { null }
|
|
}
|
|
"just" => {
|
|
# just recipes via `just --list` (handles justfile / Justfile / .justfile)
|
|
try {
|
|
^just --list --unsorted
|
|
| lines | skip 1
|
|
| each { |l|
|
|
let m = $l | parse -r '^\s+(?P<t>[A-Za-z0-9_-]+)(?:\s+\S.*)?(?:\s*#\s*(?P<d>.*))?$'
|
|
if ($m | length) > 0 {
|
|
{ value: $m.0.t, description: ($m.0.d? | default "") }
|
|
}
|
|
} | compact
|
|
} catch { null }
|
|
}
|
|
"cargo" => {
|
|
let need_target = ["run" "test" "build" "bench" "check" "doc" "install"]
|
|
let target_flags = ["--bin" "-p" "--package" "--example" "--test" "--bench"]
|
|
if (($sub in $need_target) and ($prev_span in $target_flags)) {
|
|
try {
|
|
^cargo metadata --no-deps --format-version 1
|
|
| from json | get packages | each { |pkg|
|
|
$pkg.targets | each { |t| { value: $t.name, description: ($t.kind | str join ",") } }
|
|
} | flatten | uniq-by value
|
|
} catch { null }
|
|
} else { null }
|
|
}
|
|
"kill" | "pkill" => {
|
|
try {
|
|
^ps -eo pid,comm --no-headers
|
|
| lines
|
|
| each { |l|
|
|
let parts = $l | str trim | split row -r '\s+'
|
|
if ($parts | length) >= 2 {
|
|
let pid = $parts | get 0
|
|
let comm = $parts | skip 1 | str join " "
|
|
if ($spans.0 == "kill") { { value: $pid, description: $comm } }
|
|
else { { value: $comm, description: $pid } }
|
|
}
|
|
} | compact
|
|
} catch { null }
|
|
}
|
|
_ => { null }
|
|
}
|
|
} else { null }
|
|
let result = ($completions | default []) | append ($additional | default []) | compact
|
|
if ($result | is-empty) { null } else { $result }
|
|
}
|
|
$env.config.completions.external = {enable: true, max_results: 200, completer: $inshellah_complete}
|
|
'';
|
|
};
|
|
};
|
|
}
|