Last active
October 6, 2023 20:05
-
-
Save jhoneill/47f5151b22a1dabb4ddc79c083162f77 to your computer and use it in GitHub Desktop.
PowerShell to make a nicer CD.. Just add to profile.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Profile addition to redefine cd as a proxy for push location (and cd- for Pop). | |
# cd ... is transformed into into cd ..\.. (extra dot is an extra level), | |
# cd ^ is transformed into cd <<script i.e. profile>> directory | |
# cd \*doc is transformed into cd \*\Doc*\ | |
# cd = is transformed into an item from the stack. = is the first each extra = goes one deeper in the stack | |
# cd - will not tab expand but will pop an item from the location stack. 3 or more - will do an extra pop. | |
# -- means "all the rest are a strings", so two levels needs -- -- | |
# cd ~ Now supports tab expansion | |
# cd ~~ Tab completes "Special" folders (e.g. MyDocuments, Desktop, ProgramFiles) | |
# cd ~~Name\ Transorms to or tab completes the special folder | |
# cd\ & cd.. (without space) do push instead of set and cd~ (no space) has been added | |
# cd HK[tab] will expand to HKCU: and HKLM: and similarly for other drives. | |
Remove-Item -Path Alias:\cd -ErrorAction SilentlyContinue | |
class PathTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute { | |
[object] Transform([System.Management.Automation.EngineIntrinsics]$EngineIntrinsics, [object] $InputData) { | |
switch -regex ($InputData) { | |
"^=+" {return ($inputData -replace "^=+", (Get-Location -Stack).ToArray()[$Matches[0].Length -1].Path); break} | |
"^\^" {return ($inputData -replace "^\^", $PSScriptRoot) ; break } | |
"^\\\*|^/\*" {return ($pwd.path -replace "^(.*$($InputData.substring(2)).*?)[/\\].*$",'$1') ; break } | |
"^\.{3}" {return ($InputData -replace "(?<=^\.[.\\]*)(?=\.{2,}(\\|$))", ".\") ; break} | |
"^~~[a-z]+" {try {return ($InputData -replace "~~\w+", ( [System.Environment]::GetFolderPath(($InputData -replace "~~([a-z]+).*$",'$1'))) )} catch {};break} | |
} | |
return ($InputData) | |
} | |
} | |
class ValidatePathAttribute : System.Management.Automation.ValidateArgumentsAttribute { | |
[string]$Exemption = "" #"^-+$" | |
[boolean]$ContainersOnly = $false | |
[int]$MaxItems = -1 | |
[void] Validate([object] $arguments , [System.Management.Automation.EngineIntrinsics]$EngineIntrinsics) { | |
if ($this.Exemption -and $arguments -match $this.Exemption) {return} #Exempt some things eg "-"" or "----"" | |
elseif ($arguments -match "^(\w+):\\?" -and (Get-PSDrive $Matches[1] -ErrorAction SilentlyContinue) ) {return} #Allow drives | |
else { | |
if ($this.ContainersOnly) {$count = (Get-Item -Path $arguments -ErrorAction SilentlyContinue).where({$_.psIscontainer}).count} | |
else {$count = (Get-Item -Path $arguments -ErrorAction SilentlyContinue).count } | |
if ($count -eq 0 -and $this.maxitems -ge 0) { | |
throw [System.Management.Automation.ValidationMetadataException]::new("'$arguments' does not exist.") | |
} | |
elseif ($this.Maxitems -ge 0 -and $count -gt $this.maxitems) { | |
throw [System.Management.Automation.ValidationMetadataException]::new("'$arguments' resolved to multiple $count items. Maximum allowed is $($this.Maxitems)") | |
} | |
} | |
return | |
} | |
} | |
class PathCompleter : System.Management.Automation.IArgumentCompleter { | |
[System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] CompleteArgument( | |
[string]$CommandName, [string]$ParameterName, [string]$WordToComplete, | |
[System.Management.Automation.Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters | |
) | |
{ | |
$results = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() | |
$dots = [regex]"^\.\.(\.*)(\\|$|/)" #find two dots, any more dots (captured), followed by / \ or end of string | |
$sep = [system.io.path]::DirectorySeparatorChar | |
$wtc = "" | |
switch -regex ($wordToComplete) { | |
#.. alone doesn't expand, expand .. followed by n dots (and possibly \ or /) to ..\ n+1 times | |
$dots {$newPath = "..$Sep" * (1 + $dots.Matches($wordToComplete)[0].Groups[1].Length) | |
$wtc = $dots.Replace($wordtocomplete,$newPath) ; break } | |
"^\^$" {$wtc = $PSScriptRoot ; break } # ^ [tab] ==> PS profile dir | |
"^\.$" {$wtc = "" ; break } # . and ~ alone don't expand. | |
"^~$" {$wtc = $env:USERPROFILE ; break } | |
#for 1 = sign tab through the location stack. | |
"^=$" { foreach ($stackPath in (Get-Location -Stack).ToArray().Path) { | |
if ($stackpath -match "[ ']") {$stackpath = '"' + $stackPath + '"'} | |
$results.Add([System.Management.Automation.CompletionResult]::new($stackPath)) | |
} | |
return $results ; continue | |
} | |
#replace string of = signs with the item that many up the location stack | |
"^=+$" {$wtc = (Get-Location -Stack).ToArray()[$wordToComplete.Length -1].Path ; continue } | |
#if path is c:\here\there\everywhere\stuff convert "\*the" to "c:\here\there" | |
"^\\\*|^/\*" {$wtc = $pwd.path -replace "^(.*$($WordToComplete.substring(2)).*?)[/\\].*$",'$1' ; continue } | |
"^~~[a-z]+[\\/]" { | |
try {$wtc = [System.Environment]::GetFolderPath(($wordToComplete -replace "~~([a-z]+).*$",'$1')) + ($wordToComplete -replace "~~[a-z]+(.*$)",'$1') } | |
catch {} | |
break | |
} | |
"^~~" { [enum]::GetNames([System.Environment+SpecialFolder]) | | |
Where-Object {$_ -match "^\w+$" -and [System.Environment]::GetFolderPath($_) -and $_ -like "$($wordToComplete.substring(2))*" } | | |
Sort-Object | | |
ForEach-Object {$results.Add([System.Management.Automation.CompletionResult]::new("~~$_")) } | |
return $results ; continue | |
} | |
default {$wtc = $wordToComplete} | |
} | |
foreach ($result in [System.Management.Automation.CompletionCompleters]::CompleteFilename($wtc) ) { | |
if ($result.resultType -eq "ProviderContainer" -or $CommandName -notin @("cd","dir")) {$results.Add($result)} | |
} | |
foreach ($result in $Global:ExecutionContext.SessionState.Drive.GetAll().name -like "$wordTocomplete*") { | |
$results.Add([System.Management.Automation.CompletionResult]::new("$result`:")) | |
} | |
return $results | |
} | |
} | |
function cd { | |
<# | |
.ForwardHelpTargetName Microsoft.PowerShell.Management\Push-Location | |
.ForwardHelpCategory Cmdlet | |
#> | |
[CmdletBinding(DefaultParameterSetName='Path')] | |
param( | |
[Parameter(ParameterSetName='Path', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] | |
[PathTransform()] | |
[ArgumentCompleter([PathCompleter])] | |
[ValidatePath(Exemption="^-+$",ContainersOnly=$true,MaxItems=1)] | |
[string]$Path, | |
[Parameter(ParameterSetName='LiteralPath', ValueFromPipelineByPropertyName=$true)] | |
[Alias('PSPath','LP')] | |
[string]$LiteralPath, | |
[switch]$PassThru, | |
[Parameter(ValueFromPipelineByPropertyName=$true)] | |
[string]$StackName | |
) | |
process { | |
if ($Path -match "^-+$") {foreach ($i in (1..$Path.Length)) {Pop-Location }} | |
elseif ($Path -or $LiteralPath) {Push-Location @PSBoundParameters } | |
} | |
} | |
Set-Alias -Name "cd-" -Value Pop-Location | |
Function cd.. {Push-Location -Path ..} | |
Function cd\ {Push-Location -Path \ } | |
Function cd~ {Push-Location -Path ~} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment