PSLocationHistory.psm1
Set-StrictMode -Version Latest $script:IsEnabled = $false; # Event Handler lists $script:LocationChangedEventHandlers = New-Object System.Collections.ArrayList Function DirectoryIsNonTrivial { param([string]$Path) return ($Path -notmatch '^[a-z]:\\$'); } Function Get-RecentLocation { $script:RecentLocations; } Function Update-RecentLocation { param([string]$Path) if (DirectoryIsNonTrivial $Path) { $script:RecentLocations[$Path] = $script:RecentLocations[$Path] + 1; } } Function Add-LocationToHistoryStack { param([string]$Path) if ($script:LocationHistoryPointer -eq ($script:LocationHistory.Count - 1)) { $script:LocationHistoryPointer += 1; $script:LocationHistory.Add($Path) | Out-Null } else { $script:LocationHistory.RemoveRange($script:LocationHistoryPointer + 1, $script:LocationHistory.Count - 1 - $script:LocationHistoryPointer) $script:LocationHistoryPointer = $script:LocationHistory.Count; # $script:LocationHistory.Add($Path) | Out-Null } } Function Enter-RecentLocation { $DATA_SIZE = 100; $data = @($script:RecentLocations.Keys | sort { $script:RecentLocations[$_] } -Descending | Select -First ($DATA_SIZE + 1) | ? { $_ -ne (Get-Location).ProviderPath } | Select -First $DATA_SIZE); if (-not $data) { Write-Host "No Recent Locations"; } else { $PAGE_SIZE = 10; $totalPages = [int][Math]::Ceiling($data.Count / $PAGE_SIZE); $indexedData = @(); for ($pageNumber = 1; $pageNumber -le $totalPages; $pageNumber++) { $startIndex = ($pageNumber - 1) * $PAGE_SIZE; $endIndex = & { $offset = if ($pageNumber -lt $totalPages) { $PAGE_SIZE; } else { $data.Count % $PAGE_SIZE; }; return $startIndex + $offset - 1; } $pageData = $data[$startIndex..$endIndex] | sort { $_ }; $userStartIndex = $startIndex + 1; $userEndIndex = $endIndex + 1; $indexedPage = $pageData | % { $i = $userStartIndex; } { [pscustomobject]@{ Index = $i; Path = $_; }; $i++; }; $indexedData += $indexedPage; $indexedPage | Format-Table -Wrap | Out-String | Write-Host -NoNewLine $userMessage = "Enter index of path (ctrl+c to cancel$(if ($pageNumber -ne $totalPages) { ", <Enter> for more" }))"; $inputStr = Read-Host $userMessage; $selection = & { if ($inputStr -match '^\d+$') { $inputInt = [int]$inputStr; if (($inputInt -ge 1) -and ($inputInt -le $userEndIndex)) { return $inputInt; } } return $null; } if ($selection -is [int]) { Set-LocationWithHistory ($indexedData | Where-Object { $_.Index -eq $selection }).Path return; } elseif ($inputStr) { Write-Host "Invalid selection" -ForegroundColor Red; return; } } } } Function Go-Forward { if (($script:LocationHistoryPointer -eq 0 -and ($script:LocationHistory.Count -eq 0)) -or ($script:LocationHistoryPointer -eq ($script:LocationHistory.Count - 1))) { Write-Host 'No "Forward" location'; } else { $script:LocationHistoryPointer = $script:LocationHistoryPointer + 1; Set-Location $script:LocationHistory[$script:LocationHistoryPointer]; } } Function Go-Back { if ($script:LocationHistoryPointer -eq 0) { Write-Host 'No "Back" location'; } else { $script:LocationHistoryPointer = $script:LocationHistoryPointer - 1; Set-Location $script:LocationHistory[$script:LocationHistoryPointer]; } } Function Set-LocationWithHistory { [CmdletBinding()] [OutputType()] param ( [string]$Path, [string]$Path0, [string]$Path1, [string]$Path2, [string]$Path3, [string]$Path4, [string]$Path5, [string]$Path6, [string]$Path7, [string]$Path8, [string]$Path9, [switch][Alias("b")]$Back, [switch][Alias("f")]$Forward, [switch][Alias("rl")]$RecentLocation # later implement [switch][Alias("fl")]$FrequentLocation ) $Path = ($Path, $Path0, $Path1, $Path2, $Path3, $Path4, $Path5, $Path6, $Path7, $Path8, $Path9 | ? { -not [string]::IsNullOrWhiteSpace($_) }) -join " "; if ($Path) { if (-not (Test-Path $Path)) { "'$Path' does not exist", "" | Write-Host -ForegroundColor Red } else { Set-Location $Path $newLocation = (Get-Location).ProviderPath; Add-LocationToHistoryStack $newLocation; $script:LocationChangedEventHandlers | ForEach-Object { & $_ $newLocation; } } } elseif ($Back) { Go-Back } elseif ($Forward) { Go-Forward } elseif ($RecentLocation) { Enter-RecentLocation } } Function Set-LocationWithHistoryPrompt() { Update-RecentLocation (Get-Location).ProviderPath; __prompt; } Function Enable-LocationHistory { if ($script:IsEnabled) { Write-Host "LocationHistory is already enabled"; } else { $script:LocationHistory = [System.Collections.ArrayList]@(); $script:LocationHistory.Add((Get-Location).ProviderPath) | Out-Null; $script:LocationHistoryPointer = 0; $script:RecentLocations = New-Object 'System.Collections.Generic.Dictionary[string,int]' Rename-Item function:\prompt global:__prompt; Rename-Item function:\Set-LocationWithHistoryPrompt global:prompt; Set-Alias -Name __cd -Value (Get-Alias cd).Definition -Scope Global -Option AllScope; Set-Alias -Name cd -Value Set-LocationWithHistory -Scope Global -Option AllScope; $script:IsEnabled = $true; } } Function Disable-LocationHistory { Remove-Item Function:\prompt Rename-Item function:\__prompt global:prompt; Set-Alias -Name cd -Value (Get-Alias __cd).Definition -Scope Global -Option AllScope; Get-Item Alias:\__cd | Remove-Item -Force $script:IsEnabled = $false; } # following this naming convention # https://stackoverflow.com/a/980364/288393 # https://en.oxforddictionaries.com/spelling/verb-tenses-adding-ed-and-ing # <EventName> ::= <NounPhrase><VerbForm> # <VerbForm> ::= <VerbPresentParticle> | <VerbPastTense> Function Add-PSLocationHistoryEventHandler { param($LocationChanged) if ($LocationChanged) { if ($LocationChanged -is [scriptblock]) { # handle scriptblocks for now $script:LocationChangedEventHandlers.Add($LocationChanged) | Out-Null } } } <# Function ChangeDirEventHandler_ListItems { Param([string]$Path) Function ShortenLongString { param([string]$Str, [int]$MaxLength) if ($Str -le $MaxLength) { return $Str; } $splitPos = 10; return $Str.Substring(0, $MaxLength - $splitPos - 3) + "..." + $Str.Substring($Str.Length - $splitPos, $splitPos); } $terms = @(Get-ChildItem $Path | Foreach-Object { if ($_.PSIsContainer) { $_.Name + "\" } else { $_.Name } }) if ($terms) { $columnWidth = ($terms | % Length | measure -Maximum).Maximum + 2; $bufferWidth = [Console]::BufferWidth; $numColumns = [Math]::Max(1, [int][Math]::Floor( $bufferWidth / $columnWidth)) if ($numColumns -eq 1) { $terms | % { ShortenLongString $_ $bufferWidth } | Write-Host } else { $numRows = ( $terms.Length + $numColumns - 1 ) / $numColumns; (0..($numRows - 1)) | % { $rowNum = $_; $start = $rowNum * $numColumns; $end = $start + $numColumns - 1; $lineTerms = $terms[$start..$end]; $printableTerms = $lineTerms | % { $_.PadRight($columnWidth, ' ') } Write-Host ($printableTerms -join '') } } } } Add-PSLocationHistoryEventHandler -LocationChanged { param($x) ChangeDirEventHandler_ListItems $x } #> |