From b286387b4a51c709ec32ae485fe042ab4f44962c Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Wed, 16 Oct 2024 13:41:27 +0330 Subject: [PATCH] add powershell script --- cac.ts | 53 ++++++--- pnpm-lock.yaml | 303 ++++++++++++++++++++++++------------------------- powershell.ts | 281 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 462 insertions(+), 175 deletions(-) diff --git a/cac.ts b/cac.ts index 4a20679..6050b75 100644 --- a/cac.ts +++ b/cac.ts @@ -11,9 +11,22 @@ import { ShellCompDirective, } from "./shared"; +function quoteIfNeeded(path: string): string { + return path.includes(" ") ? `'${path}'` : path; +} + const execPath = process.execPath; const processArgs = process.argv.slice(1); -const x = `${execPath} ${process.execArgv.join(" ")} ${processArgs[0]}`; + +// Apply the quoting function to each part of x +// This ensures that paths like "Program Files" are quoted for PowerShell execution. +const quotedExecPath = quoteIfNeeded(execPath); +const quotedProcessArgs = processArgs.map(quoteIfNeeded); +const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); + +const x = `${quotedExecPath} ${quotedProcessExecArgs.join(" ")} ${ + quotedProcessArgs[0] +}`; export default function tab(instance: CAC): void { instance.command("complete [shell]").action(async (shell, extra) => { @@ -75,7 +88,7 @@ export default function tab(instance: CAC): void { function processOption() { const matchedOption = options.find((o) => - o.names.some((name) => name === flagName), + o.names.some((name) => name === flagName) ); if (matchedOption && !matchedOption.isBoolean) { @@ -120,21 +133,27 @@ export default function tab(instance: CAC): void { } else if (lastArg?.startsWith("--") && !endsWithSpace) { flagName = lastArg.slice(2); processOption(); - } else if (lastArg?.startsWith("-") && lastArg.length > 1 && !endsWithSpace) { + } else if ( + lastArg?.startsWith("-") && + lastArg.length > 1 && + !endsWithSpace + ) { flagName = lastArg.slice(2); processOption(); } if (isCompletingFlagValue) { const flagCompletionFn = flagMap.get( - `${command.name} ${option?.name}`, + `${command.name} ${option?.name}` ); if (flagCompletionFn) { // Call custom completion function for the flag const comps = await flagCompletionFn(previousArgs, toComplete); completions.push( - ...comps.map((comp) => `${comp.action}\t${comp.description ?? ''}`), + ...comps.map( + (comp) => `${comp.action}\t${comp.description ?? ""}` + ) ); directive = ShellCompDirective.ShellCompDirectiveNoFileComp; } else { @@ -164,7 +183,9 @@ export default function tab(instance: CAC): void { if (optionsToSuggest.length > 0) { completions.push( - ...optionsToSuggest.map((o) => `--${o.name}\t${o.description ?? ''}`), + ...optionsToSuggest.map( + (o) => `--${o.name}\t${o.description ?? ""}` + ) ); } @@ -174,7 +195,7 @@ export default function tab(instance: CAC): void { [execPath, processArgs[0], ...previousArgs, toComplete], { run: false, - }, + } ); const fullCommandName = args .filter((arg) => !arg.startsWith("-")) @@ -225,12 +246,12 @@ export default function tab(instance: CAC): void { if (part.value.variadic) { const comps = await positional.completion( previousArgs, - toComplete, + toComplete ); completions.push( ...comps.map( - (comp) => `${comp.action}\t${comp.description ?? ""}`, - ), + (comp) => `${comp.action}\t${comp.description ?? ""}` + ) ); break; } @@ -240,12 +261,12 @@ export default function tab(instance: CAC): void { // User is still typing this positional argument, provide completions const comps = await positional.completion( previousArgs, - toComplete, + toComplete ); completions.push( ...comps.map( - (comp) => `${comp.action}\t${comp.description ?? ""}`, - ), + (comp) => `${comp.action}\t${comp.description ?? ""}` + ) ); break; } else { @@ -257,12 +278,12 @@ export default function tab(instance: CAC): void { // User has not provided input for this positional argument const comps = await positional.completion( previousArgs, - toComplete, + toComplete ); completions.push( ...comps.map( - (comp) => `${comp.action}\t${comp.description ?? ""}`, - ), + (comp) => `${comp.action}\t${comp.description ?? ""}` + ) ); break; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2e96df..5631df9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,289 +1,259 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -importers: - - .: - dependencies: - mri: - specifier: ^1.2.0 - version: 1.2.0 - devDependencies: - '@types/node': - specifier: ^22.7.4 - version: 22.7.4 - cac: - specifier: ^6.7.14 - version: 6.7.14 - tsx: - specifier: ^4.19.1 - version: 4.19.1 +dependencies: + mri: + specifier: ^1.2.0 + version: 1.2.0 + +devDependencies: + '@types/node': + specifier: ^22.7.4 + version: 22.7.5 + cac: + specifier: ^6.7.14 + version: 6.7.14 + tsx: + specifier: ^4.19.1 + version: 4.19.1 packages: - '@esbuild/aix-ppc64@0.23.1': + /@esbuild/aix-ppc64@0.23.1: resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + requiresBuild: true + dev: true + optional: true - '@esbuild/android-arm64@0.23.1': + /@esbuild/android-arm64@0.23.1: resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} cpu: [arm64] os: [android] + requiresBuild: true + dev: true + optional: true - '@esbuild/android-arm@0.23.1': + /@esbuild/android-arm@0.23.1: resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} cpu: [arm] os: [android] + requiresBuild: true + dev: true + optional: true - '@esbuild/android-x64@0.23.1': + /@esbuild/android-x64@0.23.1: resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} cpu: [x64] os: [android] + requiresBuild: true + dev: true + optional: true - '@esbuild/darwin-arm64@0.23.1': + /@esbuild/darwin-arm64@0.23.1: resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + requiresBuild: true + dev: true + optional: true - '@esbuild/darwin-x64@0.23.1': + /@esbuild/darwin-x64@0.23.1: resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + requiresBuild: true + dev: true + optional: true - '@esbuild/freebsd-arm64@0.23.1': + /@esbuild/freebsd-arm64@0.23.1: resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/freebsd-x64@0.23.1': + /@esbuild/freebsd-x64@0.23.1: resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-arm64@0.23.1': + /@esbuild/linux-arm64@0.23.1: resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-arm@0.23.1': + /@esbuild/linux-arm@0.23.1: resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-ia32@0.23.1': + /@esbuild/linux-ia32@0.23.1: resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-loong64@0.23.1': + /@esbuild/linux-loong64@0.23.1: resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-mips64el@0.23.1': + /@esbuild/linux-mips64el@0.23.1: resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-ppc64@0.23.1': + /@esbuild/linux-ppc64@0.23.1: resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-riscv64@0.23.1': + /@esbuild/linux-riscv64@0.23.1: resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-s390x@0.23.1': + /@esbuild/linux-s390x@0.23.1: resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/linux-x64@0.23.1': + /@esbuild/linux-x64@0.23.1: resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] + requiresBuild: true + dev: true + optional: true - '@esbuild/netbsd-x64@0.23.1': + /@esbuild/netbsd-x64@0.23.1: resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/openbsd-arm64@0.23.1': + /@esbuild/openbsd-arm64@0.23.1: resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/openbsd-x64@0.23.1': + /@esbuild/openbsd-x64@0.23.1: resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + requiresBuild: true + dev: true + optional: true - '@esbuild/sunos-x64@0.23.1': + /@esbuild/sunos-x64@0.23.1: resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + requiresBuild: true + dev: true + optional: true - '@esbuild/win32-arm64@0.23.1': + /@esbuild/win32-arm64@0.23.1: resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + requiresBuild: true + dev: true + optional: true - '@esbuild/win32-ia32@0.23.1': + /@esbuild/win32-ia32@0.23.1: resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + requiresBuild: true + dev: true + optional: true - '@esbuild/win32-x64@0.23.1': + /@esbuild/win32-x64@0.23.1: resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} cpu: [x64] os: [win32] + requiresBuild: true + dev: true + optional: true - '@types/node@22.7.4': - resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} + /@types/node@22.7.5: + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + dependencies: + undici-types: 6.19.8 + dev: true - cac@6.7.14: + /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + dev: true - esbuild@0.23.1: + /esbuild@0.23.1: resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} hasBin: true - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - tsx@4.19.1: - resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} - engines: {node: '>=18.0.0'} - hasBin: true - - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - -snapshots: - - '@esbuild/aix-ppc64@0.23.1': - optional: true - - '@esbuild/android-arm64@0.23.1': - optional: true - - '@esbuild/android-arm@0.23.1': - optional: true - - '@esbuild/android-x64@0.23.1': - optional: true - - '@esbuild/darwin-arm64@0.23.1': - optional: true - - '@esbuild/darwin-x64@0.23.1': - optional: true - - '@esbuild/freebsd-arm64@0.23.1': - optional: true - - '@esbuild/freebsd-x64@0.23.1': - optional: true - - '@esbuild/linux-arm64@0.23.1': - optional: true - - '@esbuild/linux-arm@0.23.1': - optional: true - - '@esbuild/linux-ia32@0.23.1': - optional: true - - '@esbuild/linux-loong64@0.23.1': - optional: true - - '@esbuild/linux-mips64el@0.23.1': - optional: true - - '@esbuild/linux-ppc64@0.23.1': - optional: true - - '@esbuild/linux-riscv64@0.23.1': - optional: true - - '@esbuild/linux-s390x@0.23.1': - optional: true - - '@esbuild/linux-x64@0.23.1': - optional: true - - '@esbuild/netbsd-x64@0.23.1': - optional: true - - '@esbuild/openbsd-arm64@0.23.1': - optional: true - - '@esbuild/openbsd-x64@0.23.1': - optional: true - - '@esbuild/sunos-x64@0.23.1': - optional: true - - '@esbuild/win32-arm64@0.23.1': - optional: true - - '@esbuild/win32-ia32@0.23.1': - optional: true - - '@esbuild/win32-x64@0.23.1': - optional: true - - '@types/node@22.7.4': - dependencies: - undici-types: 6.19.8 - - cac@6.7.14: {} - - esbuild@0.23.1: + requiresBuild: true optionalDependencies: '@esbuild/aix-ppc64': 0.23.1 '@esbuild/android-arm': 0.23.1 @@ -309,23 +279,42 @@ snapshots: '@esbuild/win32-arm64': 0.23.1 '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + dev: true - fsevents@2.3.3: + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true optional: true - get-tsconfig@4.8.1: + /get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} dependencies: resolve-pkg-maps: 1.0.0 + dev: true - mri@1.2.0: {} + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: false - resolve-pkg-maps@1.0.0: {} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true - tsx@4.19.1: + /tsx@4.19.1: + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} + engines: {node: '>=18.0.0'} + hasBin: true dependencies: esbuild: 0.23.1 get-tsconfig: 4.8.1 optionalDependencies: fsevents: 2.3.3 + dev: true - undici-types@6.19.8: {} + /undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + dev: true diff --git a/powershell.ts b/powershell.ts index 3f6a4f1..205780f 100644 --- a/powershell.ts +++ b/powershell.ts @@ -1,2 +1,279 @@ -export function generate(name: string, exec: string) { -} +import { ShellCompDirective } from "./shared"; + +export function generate( + name: string, + exec: string, + includeDesc = false +): string { + // Replace '-' and ':' with '_' for variable names + const nameForVar = name.replace(/[-:]/g, "_"); + + // Determine the completion command + // const compCmd = includeDesc ? "complete" : "complete"; + + // Shell completion directives + const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError; + const ShellCompDirectiveNoSpace = + ShellCompDirective.ShellCompDirectiveNoSpace; + const ShellCompDirectiveNoFileComp = + ShellCompDirective.ShellCompDirectiveNoFileComp; + const ShellCompDirectiveFilterFileExt = + ShellCompDirective.ShellCompDirectiveFilterFileExt; + const ShellCompDirectiveFilterDirs = + ShellCompDirective.ShellCompDirectiveFilterDirs; + const ShellCompDirectiveKeepOrder = + ShellCompDirective.ShellCompDirectiveKeepOrder; + + return `# powershell completion for ${name} -*- shell-script -*- + + function __${name}_debug { + if ($env:BASH_COMP_DEBUG_FILE) { + "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE" + } + } + + filter __${name}_escapeStringWithSpecialChars { + $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|"|\\||<|>|&','\`$&' + } + +[scriptblock]$__${nameForVar}CompleterBlock = { + param( + $WordToComplete, + $CommandAst, + $CursorPosition + ) + + # Get the current command line and convert into a string + $Command = $CommandAst.CommandElements + $Command = "$Command" + + __${name}_debug "" + __${name}_debug "========= starting completion logic ==========" + __${name}_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CursorPosition location, so we need + # to truncate the command-line ($Command) up to the $CursorPosition location. + # Make sure the $Command is longer then the $CursorPosition before we truncate. + # This happens because the $Command does not include the last space. + if ($Command.Length -gt $CursorPosition) { + $Command=$Command.Substring(0,$CursorPosition) + } + __${name}_debug "Truncated command: $Command" + + $ShellCompDirectiveError=${ShellCompDirectiveError} + $ShellCompDirectiveNoSpace=${ShellCompDirectiveNoSpace} + $ShellCompDirectiveNoFileComp=${ShellCompDirectiveNoFileComp} + $ShellCompDirectiveFilterFileExt=${ShellCompDirectiveFilterFileExt} + $ShellCompDirectiveFilterDirs=${ShellCompDirectiveFilterDirs} + $ShellCompDirectiveKeepOrder=${ShellCompDirectiveKeepOrder} + + # Original exec value + $originalExec = "${exec}" + + # Split the exec string by spaces to handle each part + $execParts = $originalExec -split ' ' + + __${name}_debug "Value of exec: ${exec}" + + # Prepare the command to request completions for the program. + # Split the command at the first space to separate the program and arguments. + $Program,$Arguments = $Command.Split(" ",2) + + $RequestComp = "& ${exec} complete -- $Arguments" + __${name}_debug "RequestComp: $RequestComp" + + # We cannot use $WordToComplete because it + # has the wrong values if the cursor was moved + # so use the last argument + if ($WordToComplete -ne "" ) { + $WordToComplete = $Arguments.Split(" ")[-1] + } + __${name}_debug "New WordToComplete: $WordToComplete" + + # Check for flag with equal sign + $IsEqualFlag = ($WordToComplete -Like "--*=*" ) + if ( $IsEqualFlag ) { + __${name}_debug "Completing equal sign flag" + # Remove the flag part + $Flag,$WordToComplete = $WordToComplete.Split("=",2) + } + + if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) { + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the program. + __${name}_debug "Adding extra empty parameter" + # PowerShell 7.2+ changed the way how the arguments are passed to executables, + # so for pre-7.2 or when Legacy argument passing is enabled we need to use + # \`"\`" to pass an empty argument, a "" or '' does not work!!! + if ($PSVersionTable.PsVersion -lt [version]'7.2.0' -or + ($PSVersionTable.PsVersion -lt [version]'7.3.0' -and -not [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -or + (($PSVersionTable.PsVersion -ge [version]'7.3.0' -or [ExperimentalFeature]::IsEnabled("PSNativeCommandArgumentPassing")) -and + $PSNativeCommandArgumentPassing -eq 'Legacy')) { + $RequestComp="$RequestComp" + ' \`"\`"' + } else { + $RequestComp="$RequestComp" + ' ""' + } + } + + __${name}_debug "Calling: $RequestComp" + # First disable ActiveHelp which is not supported for PowerShell + + # Call the command, store the output in $out, and redirect stderr and stdout to null + # $Out is an array containing each line per element + Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null + + # Get directive from last line + [int]$Directive = $Out[-1].TrimStart(':') + if ($Directive -eq "") { + # There is no directive specified + $Directive = 0 + } + __${name}_debug "The completion directive is: $Directive" + + # Remove directive (last element) from out + $Out = $Out | Where-Object { $_ -ne $Out[-1] } + __${name}_debug "The completions are: $Out" + + if (($Directive -band $ShellCompDirectiveError) -ne 0 ) { + # Error code. No completion. + __${name}_debug "Received error from custom completion code" + return + } + + $Longest = 0 + [Array]$Values = $Out | ForEach-Object { + # Split the output into name and description + $Name, $Description = $_.Split("t",2) + __${name}_debug "Name: $Name Description: $Description" + + # Look for the longest completion to format things nicely + if ($Longest -lt $Name.Length) { + $Longest = $Name.Length + } + + # Set the description to a one-space string if none is set + # This is needed because CompletionResult does not accept an empty string + if (-Not $Description) { + $Description = " " + } + @{Name="$Name";Description="$Description"} + } + + $Space = " " + if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) { + # Remove the space here + __${name}_debug "ShellCompDirectiveNoSpace is called" + $Space = "" + } + + if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or + (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) { + __${name}_debug "ShellCompDirectiveFilterFileExt and ShellCompDirectiveFilterDirs are not supported" + + # Return here to prevent the completion of the extensions + return + } + + $Values = $Values | Where-Object { + # Filter the result + $_.Name -like "$WordToComplete*" + + # Join the flag back if we have an equal sign flag + if ( $IsEqualFlag ) { + __${name}_debug "Join the equal sign flag back to the completion value" + $_.Name = $Flag + "=" + $_.Name + } + } + + # Sort the values in ascending order by name if keep order isn't passed + if (($Directive -band $ShellCompDirectiveKeepOrder) -eq 0 ) { + $Values = $Values | Sort-Object -Property Name + } + + if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { + __${name}_debug "ShellCompDirectiveNoFileComp is called" + + if ($Values.Length -eq 0) { + # Just print an empty string here so the shell does not start to complete paths + # We cannot use CompletionResult here because it does not accept an empty string + "" + return + } + } + + # Get the current mode + $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function + __${name}_debug "Mode: $Mode" + + $Values | ForEach-Object { + + # Store temporary because switch will overwrite $_ + $comp = $_ + + # PowerShell supports three different completion modes + # - TabCompleteNext (default Windows style) + # - Complete (works like bash) + # - MenuComplete (works like zsh) + # Set the mode with Set-PSReadLineKeyHandler -Key Tab -Function + + # CompletionResult Arguments: + # 1) CompletionText: text to be used as the auto-completion result + # 2) ListItemText: text to be displayed in the suggestion list + # 3) ResultType: type of completion result + # 4) ToolTip: text for the tooltip with details about the object + + switch ($Mode) { + + # Bash-like + "Complete" { + + if ($Values.Length -eq 1) { + __${name}_debug "Only one completion left" + + # Insert space after value + [System.Management.Automation.CompletionResult]::new($($comp.Name | __${name}_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + + } else { + # Add the proper number of spaces to align the descriptions + while($comp.Name.Length -lt $Longest) { + $comp.Name = $comp.Name + " " + } + + # Check for empty description and only add parentheses if needed + if ($($comp.Description) -eq " " ) { + $Description = "" + } else { + $Description = " ($($comp.Description))" + } + + [System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)") + } + } + + # Zsh-like + "MenuComplete" { + # Insert space after value + # MenuComplete will automatically show the ToolTip + [System.Management.Automation.CompletionResult]::new($($comp.Name | __${name}_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + + # Default and TabCompleteNext + Default { + # Like MenuComplete but without adding a space + # Description will not be shown as it's not possible with TabCompleteNext + [System.Management.Automation.CompletionResult]::new($($comp.Name | __${name}_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + } + + } +} + +$Values | ForEach-Object { + __${name}_debug "Completion suggestion: $($_.Name)" +} + + +Register-ArgumentCompleter -CommandName '${name}' -ScriptBlock $__${nameForVar}CompleterBlock +`; +} -- 2.51.2