PSLocationHistory.psm1
Set-StrictMode -Version Latest $script:IsEnabled = $false; 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 | ? { $_.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, [switch][Alias("b")]$Back, [switch][Alias("f")]$Forward, [switch][Alias("rl")]$RecentLocation # later implement [switch][Alias("fl")]$FrequentLocation ) $currentPath = (Get-Location).ProviderPath; if ($Path) { if (-not (Test-Path $Path)) { "'$Path' does not exist", "" | Write-Host -ForegroundColor Red } else { Set-Location $Path Add-LocationToHistoryStack (Get-Location).ProviderPath; } } 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; } |