├── .editorconfig ├── .gitattributes ├── .github ├── scripts │ ├── Build.ps1 │ ├── Deploy.ps1 │ ├── Install.ps1 │ └── Tests.ps1 └── workflows │ ├── CI.yml │ └── Deploy.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── LICENSE ├── PSFzf-Binary ├── CompletionHelpers.cs ├── PSFzf-Binary.csproj ├── PSFzf.deps.json └── ReverseLineReader.cs ├── PSFzf.Base.ps1 ├── PSFzf.Functions.ps1 ├── PSFzf.Git.ps1 ├── PSFzf.TabExpansion.ps1 ├── PSFzf.psd1 ├── PSFzf.pssproj ├── PSFzf.sln ├── PSFzf.tests.ps1 ├── PSScriptAnalyzerSettings.psd1 ├── README.md ├── docs ├── Invoke-FuzzyEdit.md ├── Invoke-FuzzyFasd.md ├── Invoke-FuzzyGitStatus.md ├── Invoke-FuzzyHistory.md ├── Invoke-FuzzyKillProcess.md ├── Invoke-FuzzyScoop.md ├── Invoke-FuzzySetLocation.md ├── Invoke-FuzzyZLocation.md ├── Invoke-Fzf.md ├── Invoke-PsFzfRipgrep.md ├── PSFzfExample.gif ├── Set-LocationFuzzyEverything.md └── Set-PsFzfOption.md ├── en-US └── about_PSFzf.help.txt ├── helpers ├── GetProcessesList.ps1 ├── Join-ModuleFiles.ps1 ├── PSFzfGitBranches.ps1 ├── PsFzfGitBranches-Preview.sh ├── PsFzfGitBranches.sh ├── PsFzfGitFiles-GitAdd.sh ├── PsFzfGitFiles-GitReset.sh ├── PsFzfGitFiles-Preview.sh ├── PsFzfGitHashes-Preview.sh ├── PsFzfTabExpansion-Parameter.ps1 └── PsFzfTabExpansion-Preview.ps1 └── nuget.config /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{psd1, ps1,.cs}] 4 | indent_style = tab 5 | indent_size = 4 6 | trim_trailing_whitespace = true 7 | 8 | [*.sh] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf -------------------------------------------------------------------------------- /.github/scripts/Build.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 4 | dotnet build --configuration Release PSFzf.sln 5 | $dllPaths = Get-ChildItem PSFzf.dll -Recurse 6 | if ($null -eq $dllPaths) { 7 | throw 'Unable to find PSFzf.dll' 8 | } 9 | Copy-Item $dllPaths[0].FullName . -Force -Verbose 10 | 11 | # construct the module file: 12 | ./helpers/Join-ModuleFiles.ps1 -------------------------------------------------------------------------------- /.github/scripts/Deploy.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | Write-Host "Triggered from ${env:GITHUB_REF}" 3 | 4 | $psdir = $env:GITHUB_WORKSPACE 5 | $installdir = Join-Path $psdir 'PSFzf' 6 | new-item $installdir -ItemType Directory -verbose 7 | 8 | new-item $(Join-Path $installdir 'helpers') -ItemType Directory -verbose 9 | copy-item $(Join-Path $psdir 'helpers' '*.*') $(Join-Path $installdir 'helpers') -verbose 10 | copy-item $(Join-Path $psdir '*.ps*') $installdir -verbose 11 | copy-item $(Join-Path $psdir 'PSFzf.dll') $installdir -verbose 12 | 13 | # generate documentation: 14 | $docdir = Join-Path $installdir 'en-US' 15 | new-item $docdir -ItemType Directory 16 | Import-Module platyPS 17 | platyPS\New-ExternalHelp (Join-Path $psdir 'docs') -Force -OutputPath $docdir 18 | copy-item $(Join-Path $psdir 'en-US' '*.txt') $docdir -verbose 19 | 20 | # get contents of current psd, update version and save it back out in the publish directory: 21 | $psdFilePath = Join-Path $installdir 'PSFzf.psd1' 22 | 23 | # update prerelease: 24 | $isPrerelease = "${env:GITHUB_PRERELEASE}" -eq 'true' 25 | if ($isPrerelease) { 26 | $psdStr = Get-Content $psdFilePath | Out-String 27 | $psdStr = $psdStr.Replace('# Prerelease =', ' Prerelease =') 28 | Set-Content -Path $psdFilePath -Value $psdStr 29 | } 30 | 31 | $version = $env:GITHUB_REF 32 | if ($version -eq '' -or $null -eq $version) { 33 | throw 'Version not found in $GITHUB_REF' 34 | } 35 | $version = $version.Split('/')[-1].Replace('v', '') 36 | Update-ModuleManifest $psdFilePath -ModuleVersion $version 37 | 38 | if ($isPrerelease) { 39 | write-host ("publishing prerelease version {0}-alpha" -f $version) 40 | } 41 | else { 42 | write-host ("publishing version {0}" -f $version) 43 | } 44 | Publish-Module -NugetApiKey $env:POWERSHELLGALLERY_APIKEY -Path $installdir -Verbose 45 | 46 | -------------------------------------------------------------------------------- /.github/scripts/Install.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # Force bootstrap of the Nuget PackageManagement Provider; Reference: http://www.powershellgallery.com/GettingStarted?section=Get%20Started 4 | Get-PackageProvider -Name NuGet -Force -Verbose 5 | 6 | # get fzf: 7 | if ($IsLinux -or $IsMacOS) { 8 | if ($IsLinux) { 9 | $prefix = 'linux' 10 | } else { 11 | $prefix = 'darwin' 12 | } 13 | Invoke-WebRequest https://github.com/junegunn/fzf/releases/download/${env:FZF_VERSION}/fzf-${env:FZF_VERSION}-${prefix}_amd64.tar.gz -OutFile fzf.tgz -Verbose 14 | mkdir ./fzf/ 15 | tar -xvf ./fzf.tgz -C ./fzf/ 16 | } else { 17 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 18 | Invoke-WebRequest https://github.com/junegunn/fzf/releases/download/${env:FZF_VERSION}/fzf-${env:FZF_VERSION}-windows_amd64.zip -OutFile fzf.zip -Verbose 19 | Expand-Archive fzf.zip 20 | } 21 | 22 | $modules = @('Pester', $null), @('platyPS', $null), @('PSScriptAnalyzer', $null) 23 | $modules | ForEach-Object { 24 | $module = $_[0] 25 | $version = $_[1] 26 | if ($null -ne $version) { 27 | Install-Module -Name $module -Scope CurrentUser -RequiredVersion $version -Force -Verbose 28 | } else { 29 | Install-Module -Name $module -Scope CurrentUser -Force -Verbose 30 | } 31 | Import-Module -Name $module -Verbose 32 | } 33 | -------------------------------------------------------------------------------- /.github/scripts/Tests.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | # make sure fzf is in our path: 4 | $env:PATH = '{0}{1}{2}' -f $env:PATH,[IO.Path]::PathSeparator,"./fzf" 5 | 6 | $testResultsFile = "./TestsResults.xml" 7 | $res = Invoke-Pester -Script ./PSFzf.tests.ps1 -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru 8 | #(New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) 9 | if ($res.FailedCount -gt 0) { 10 | throw "$($res.FailedCount) tests failed." 11 | } 12 | 13 | Invoke-ScriptAnalyzer -Path ./PSFzf.psm1 -Settings ./PSScriptAnalyzerSettings.psd1 -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | linux: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Install needed software 13 | run: ./.github/scripts/Install.ps1 14 | env: 15 | FZF_VERSION: 0.24.3 16 | shell: pwsh 17 | - name: Build PSFzf-Binary 18 | run: ./.github/scripts/Build.ps1 19 | shell: pwsh 20 | - name: Run tests 21 | run: ./.github/scripts/Tests.ps1 22 | shell: pwsh 23 | 24 | 25 | Mac: 26 | 27 | runs-on: macOS-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v1 31 | - name: Install needed software 32 | run: ./.github/scripts/Install.ps1 33 | env: 34 | FZF_VERSION: 0.24.3 35 | shell: pwsh 36 | - name: Build PSFzf-Binary 37 | run: ./.github/scripts/Build.ps1 38 | shell: pwsh 39 | - name: Run tests 40 | run: ./.github/scripts/Tests.ps1 41 | shell: pwsh 42 | 43 | windows: 44 | 45 | runs-on: windows-latest 46 | 47 | steps: 48 | - uses: actions/checkout@v1 49 | - name: Install needed software 50 | run: ./.github/scripts/Install.ps1 51 | env: 52 | FZF_VERSION: 0.24.3 53 | shell: powershell 54 | - name: Build PSFzf-Binary 55 | run: | 56 | dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org --configfile $env:APPDATA\NuGet\NuGet.Config 57 | ./.github/scripts/Build.ps1 58 | shell: powershell 59 | - name: Run tests 60 | run: ./.github/scripts/Tests.ps1 61 | shell: powershell 62 | 63 | -------------------------------------------------------------------------------- /.github/workflows/Deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | windows: 9 | 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Install needed software 15 | run: ./.github/scripts/Install.ps1 16 | env: 17 | FZF_VERSION: 0.24.3 18 | shell: pwsh 19 | - name: Build PSFzf-Binary 20 | run: ./.github/scripts/Build.ps1 21 | shell: pwsh 22 | - name: Publish 23 | run: ./.github/scripts/Deploy.ps1 24 | shell: pwsh 25 | env: 26 | POWERSHELLGALLERY_APIKEY: ${{ secrets.POWERSHELLGALLERY_APIKEY }} 27 | GITHUB_PRERELEASE: ${{ github.event.release.prerelease }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | PSFzf.dll 255 | # this is autogenerated, so ignore it: 256 | PSFzf.psm1 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "powershell.scriptAnalysis.settingsPath": "../PSScriptAnalyzerSettings.psd1", 3 | "files.trimTrailingWhitespace": true, 4 | "[markdown]": { 5 | "files.trimTrailingWhitespace": false 6 | }, 7 | "prettier.useEditorConfig": true, 8 | "editor.formatOnSave": true 9 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "command": "powershell", 6 | "tasks": [ 7 | { 8 | "label": "Test", 9 | "type": "shell", 10 | "args": [ 11 | "-ExecutionPolicy", 12 | "RemoteSigned", 13 | "-Command", 14 | "${workspaceRoot}\\RunPester.ps1" 15 | ], 16 | "problemMatcher": { 17 | "owner": "pester", 18 | "pattern": { 19 | "regexp": "(.*);(.*);(.*)", 20 | "file": 1, 21 | "line": 2, 22 | "message": 3 23 | } 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Michael Kelley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PSFzf-Binary/CompletionHelpers.cs: -------------------------------------------------------------------------------- 1 | // borrowed from https://github.com/PowerShell/PSReadLine/blob/a6b0e762b658f5a2a3dd4944597c4204736f97bc/PSReadLine/Completion.cs 2 | /* 3 | Copyright (c) 2013, Jason Shirk 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | using System.Collections.ObjectModel; 27 | using System.Linq; 28 | using System.Management.Automation; 29 | 30 | namespace PSFzf.IO 31 | { 32 | /// 33 | /// Takes an encoding (defaulting to UTF-8) and a function which produces a seekable stream 34 | /// (or a filename for convenience) and yields lines from the end of the stream backwards. 35 | /// Only single byte encodings, and UTF-8 and Unicode, are supported. The stream 36 | /// returned by the function must be seekable. 37 | /// 38 | public sealed class CompletionHelpers 39 | { 40 | private static bool IsQuoted(string s) 41 | { 42 | if (s.Length >= 2) 43 | { 44 | //consider possible '& ' prefix 45 | var first = (s.Length > 4 && s.StartsWith("& ")) ? s[2] : s[0]; 46 | var last = s[s.Length - 1]; 47 | 48 | return (IsSingleQuote(first) && IsSingleQuote(last)) || 49 | (IsDoubleQuote(first) && IsDoubleQuote(last)); 50 | } 51 | return false; 52 | } 53 | 54 | private static bool IsSingleQuote(char c) => c == '\'' || c == (char)8216 || c == (char)8217 || c == (char)8218 || c == (char)8219; 55 | private static bool IsDoubleQuote(char c) => c == '"' || c == (char)8220 || c == (char)8221 || c == (char)8222; 56 | 57 | // variable can be "quoted" like ${env:CommonProgramFiles(x86)} 58 | private static bool IsQuotedVariable(string s) => s.Length > 2 && s[1] == '{' && s[s.Length - 1] == '}'; 59 | 60 | private static string GetUnquotedText(string s, bool consistentQuoting) 61 | { 62 | if (!consistentQuoting && IsQuoted(s)) 63 | { 64 | //consider possible '& ' prefix 65 | int startindex = s.StartsWith("& ") ? 3 : 1; 66 | s = s.Substring(startindex, s.Length - startindex - 1); 67 | } 68 | return s; 69 | } 70 | 71 | private static string GetUnquotedText(CompletionResult match, bool consistentQuoting) 72 | { 73 | var s = match.CompletionText; 74 | if (match.ResultType == CompletionResultType.Variable) 75 | { 76 | if (IsQuotedVariable(s)) 77 | { 78 | return s[0] + s.Substring(2, s.Length - 3); 79 | } 80 | return s; 81 | } 82 | return GetUnquotedText(s, consistentQuoting); 83 | } 84 | 85 | private bool IsConsistentQuoting(Collection matches) 86 | { 87 | int quotedCompletions = matches.Count(match => IsQuoted(match.CompletionText)); 88 | return 89 | quotedCompletions == 0 || 90 | (quotedCompletions == matches.Count && 91 | quotedCompletions == matches.Count( 92 | m => m.CompletionText[0] == matches[0].CompletionText[0])); 93 | } 94 | 95 | public string GetUnambiguousPrefix(Collection matches, out bool ambiguous) 96 | { 97 | // Find the longest unambiguous prefix. This might be the empty 98 | // string, in which case we don't want to remove any of the users input, 99 | // instead we'll immediately show possible completions. 100 | // For the purposes of unambiguous prefix, we'll ignore quotes if 101 | // some completions aren't quoted. 102 | ambiguous = false; 103 | var firstResult = matches[0]; 104 | bool consistentQuoting = IsConsistentQuoting(matches); 105 | 106 | var replacementText = GetUnquotedText(firstResult, consistentQuoting); 107 | foreach (var match in matches.Skip(1)) 108 | { 109 | var matchText = GetUnquotedText(match, consistentQuoting); 110 | for (int i = 0; i < replacementText.Length; i++) 111 | { 112 | if (i == matchText.Length 113 | || replacementText[i] != matchText[i]) 114 | { 115 | ambiguous = true; 116 | replacementText = replacementText.Substring(0, i); 117 | break; 118 | } 119 | } 120 | if (replacementText.Length == 0) 121 | { 122 | break; 123 | } 124 | } 125 | if (replacementText.Length == 0) 126 | { 127 | replacementText = firstResult.ListItemText; 128 | foreach (var match in matches.Skip(1)) 129 | { 130 | var matchText = match.ListItemText; 131 | for (int i = 0; i < replacementText.Length; i++) 132 | { 133 | if (i == matchText.Length 134 | || replacementText[i] != matchText[i]) 135 | { 136 | ambiguous = true; 137 | replacementText = replacementText.Substring(0, i); 138 | break; 139 | } 140 | } 141 | if (replacementText.Length == 0) 142 | { 143 | break; 144 | } 145 | } 146 | } 147 | return replacementText; 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /PSFzf-Binary/PSFzf-Binary.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | PSFzf_Binary 6 | PSFzf 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /PSFzf-Binary/PSFzf.deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeTarget": { 3 | "name": ".NETStandard,Version=v2.0/", 4 | "signature": "cfe1dc2a80602aef150a12815387068463a61a0d" 5 | }, 6 | "compilationOptions": {}, 7 | "targets": { 8 | ".NETStandard,Version=v2.0": {}, 9 | ".NETStandard,Version=v2.0/": { 10 | "PSFzf/1.0.0": { 11 | "dependencies": { 12 | "NETStandard.Library": "2.0.3" 13 | }, 14 | "runtime": { 15 | "PSFzf.dll": {} 16 | } 17 | }, 18 | "Microsoft.NETCore.Platforms/1.1.0": {}, 19 | "NETStandard.Library/2.0.3": { 20 | "dependencies": { 21 | "Microsoft.NETCore.Platforms": "1.1.0" 22 | } 23 | } 24 | } 25 | }, 26 | "libraries": { 27 | "PSFzf/1.0.0": { 28 | "type": "project", 29 | "serviceable": false, 30 | "sha512": "" 31 | }, 32 | "Microsoft.NETCore.Platforms/1.1.0": { 33 | "type": "package", 34 | "serviceable": true, 35 | "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", 36 | "path": "microsoft.netcore.platforms/1.1.0", 37 | "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" 38 | }, 39 | "NETStandard.Library/2.0.3": { 40 | "type": "package", 41 | "serviceable": true, 42 | "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", 43 | "path": "netstandard.library/2.0.3", 44 | "hashPath": "netstandard.library.2.0.3.nupkg.sha512" 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /PSFzf-Binary/ReverseLineReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | // borrowed from https://stackoverflow.com/a/452945 8 | namespace PSFzf.IO 9 | { 10 | /// 11 | /// Takes an encoding (defaulting to UTF-8) and a function which produces a seekable stream 12 | /// (or a filename for convenience) and yields lines from the end of the stream backwards. 13 | /// Only single byte encodings, and UTF-8 and Unicode, are supported. The stream 14 | /// returned by the function must be seekable. 15 | /// 16 | public sealed class ReverseLineReader : IEnumerable 17 | { 18 | /// 19 | /// Buffer size to use by default. Classes with internal access can specify 20 | /// a different buffer size - this is useful for testing. 21 | /// 22 | private const int DefaultBufferSize = 4096; 23 | 24 | /// 25 | /// Means of creating a Stream to read from. 26 | /// 27 | private readonly Func streamSource; 28 | 29 | /// 30 | /// Encoding to use when converting bytes to text 31 | /// 32 | private readonly Encoding encoding; 33 | 34 | /// 35 | /// Size of buffer (in bytes) to read each time we read from the 36 | /// stream. This must be at least as big as the maximum number of 37 | /// bytes for a single character. 38 | /// 39 | private readonly int bufferSize; 40 | 41 | /// 42 | /// Function which, when given a position within a file and a byte, states whether 43 | /// or not the byte represents the start of a character. 44 | /// 45 | private Func characterStartDetector; 46 | 47 | /// 48 | /// Creates a LineReader from a stream source. The delegate is only 49 | /// called when the enumerator is fetched. UTF-8 is used to decode 50 | /// the stream into text. 51 | /// 52 | /// Data source 53 | public ReverseLineReader(Func streamSource) 54 | : this(streamSource, Encoding.UTF8) 55 | { 56 | } 57 | 58 | /// 59 | /// Creates a LineReader from a filename. The file is only opened 60 | /// (or even checked for existence) when the enumerator is fetched. 61 | /// UTF8 is used to decode the file into text. 62 | /// 63 | /// File to read from 64 | public ReverseLineReader(string filename) 65 | : this(filename, Encoding.UTF8) 66 | { 67 | } 68 | 69 | /// 70 | /// Creates a LineReader from a filename. The file is only opened 71 | /// (or even checked for existence) when the enumerator is fetched. 72 | /// 73 | /// File to read from 74 | /// Encoding to use to decode the file into text 75 | public ReverseLineReader(string filename, Encoding encoding) 76 | : this(() => File.OpenRead(filename), encoding) 77 | { 78 | } 79 | 80 | /// 81 | /// Creates a LineReader from a stream source. The delegate is only 82 | /// called when the enumerator is fetched. 83 | /// 84 | /// Data source 85 | /// Encoding to use to decode the stream into text 86 | public ReverseLineReader(Func streamSource, Encoding encoding) 87 | : this(streamSource, encoding, DefaultBufferSize) 88 | { 89 | } 90 | 91 | internal ReverseLineReader(Func streamSource, Encoding encoding, int bufferSize) 92 | { 93 | this.streamSource = streamSource; 94 | this.encoding = encoding; 95 | this.bufferSize = bufferSize; 96 | if (encoding.IsSingleByte) 97 | { 98 | // For a single byte encoding, every byte is the start (and end) of a character 99 | characterStartDetector = (pos, data) => true; 100 | } 101 | else if (encoding is UnicodeEncoding) 102 | { 103 | // For UTF-16, even-numbered positions are the start of a character. 104 | // TODO: This assumes no surrogate pairs. More work required 105 | // to handle that. 106 | characterStartDetector = (pos, data) => (pos & 1) == 0; 107 | } 108 | else if (encoding is UTF8Encoding) 109 | { 110 | // For UTF-8, bytes with the top bit clear or the second bit set are the start of a character 111 | // See http://www.cl.cam.ac.uk/~mgk25/unicode.html 112 | characterStartDetector = (pos, data) => (data & 0x80) == 0 || (data & 0x40) != 0; 113 | } 114 | else 115 | { 116 | throw new ArgumentException("Only single byte, UTF-8 and Unicode encodings are permitted"); 117 | } 118 | } 119 | 120 | private Stream currStream; 121 | 122 | /// 123 | /// Returns the enumerator reading strings backwards. If this method discovers that 124 | /// the returned stream is either unreadable or unseekable, a NotSupportedException is thrown. 125 | /// 126 | public IEnumerator GetEnumerator() 127 | { 128 | Stream stream = streamSource(); 129 | currStream = stream; 130 | if (!stream.CanSeek) 131 | { 132 | stream.Dispose(); 133 | throw new NotSupportedException("Unable to seek within stream"); 134 | } 135 | if (!stream.CanRead) 136 | { 137 | stream.Dispose(); 138 | throw new NotSupportedException("Unable to read within stream"); 139 | } 140 | return GetEnumeratorImpl(stream); 141 | } 142 | 143 | private IEnumerator GetEnumeratorImpl(Stream stream) 144 | { 145 | try 146 | { 147 | long position = stream.Length; 148 | 149 | if (encoding is UnicodeEncoding && (position & 1) != 0) 150 | { 151 | throw new InvalidDataException("UTF-16 encoding provided, but stream has odd length."); 152 | } 153 | 154 | // Allow up to two bytes for data from the start of the previous 155 | // read which didn't quite make it as full characters 156 | byte[] buffer = new byte[bufferSize + 2]; 157 | char[] charBuffer = new char[encoding.GetMaxCharCount(buffer.Length)]; 158 | int leftOverData = 0; 159 | String previousEnd = null; 160 | // TextReader doesn't return an empty string if there's line break at the end 161 | // of the data. Therefore we don't return an empty string if it's our *first* 162 | // return. 163 | bool firstYield = true; 164 | 165 | // A line-feed at the start of the previous buffer means we need to swallow 166 | // the carriage-return at the end of this buffer - hence this needs declaring 167 | // way up here! 168 | bool swallowCarriageReturn = false; 169 | 170 | while (position > 0) 171 | { 172 | int bytesToRead = Math.Min(position > int.MaxValue ? bufferSize : (int)position, bufferSize); 173 | 174 | position -= bytesToRead; 175 | stream.Position = position; 176 | StreamUtil.ReadExactly(stream, buffer, bytesToRead); 177 | // If we haven't read a full buffer, but we had bytes left 178 | // over from before, copy them to the end of the buffer 179 | if (leftOverData > 0 && bytesToRead != bufferSize) 180 | { 181 | // Buffer.BlockCopy doesn't document its behaviour with respect 182 | // to overlapping data: we *might* just have read 7 bytes instead of 183 | // 8, and have two bytes to copy... 184 | Array.Copy(buffer, bufferSize, buffer, bytesToRead, leftOverData); 185 | } 186 | // We've now *effectively* read this much data. 187 | bytesToRead += leftOverData; 188 | 189 | int firstCharPosition = 0; 190 | while (!characterStartDetector(position + firstCharPosition, buffer[firstCharPosition])) 191 | { 192 | firstCharPosition++; 193 | // Bad UTF-8 sequences could trigger this. For UTF-8 we should always 194 | // see a valid character start in every 3 bytes, and if this is the start of the file 195 | // so we've done a short read, we should have the character start 196 | // somewhere in the usable buffer. 197 | if (firstCharPosition == 3 || firstCharPosition == bytesToRead) 198 | { 199 | throw new InvalidDataException("Invalid UTF-8 data"); 200 | } 201 | } 202 | leftOverData = firstCharPosition; 203 | 204 | int charsRead = encoding.GetChars(buffer, firstCharPosition, bytesToRead - firstCharPosition, charBuffer, 0); 205 | int endExclusive = charsRead; 206 | 207 | for (int i = charsRead - 1; i >= 0; i--) 208 | { 209 | char lookingAt = charBuffer[i]; 210 | if (swallowCarriageReturn) 211 | { 212 | swallowCarriageReturn = false; 213 | if (lookingAt == '\r') 214 | { 215 | endExclusive--; 216 | continue; 217 | } 218 | } 219 | // Anything non-line-breaking, just keep looking backwards 220 | if (lookingAt != '\n' && lookingAt != '\r') 221 | { 222 | continue; 223 | } 224 | // End of CRLF? Swallow the preceding CR 225 | if (lookingAt == '\n') 226 | { 227 | swallowCarriageReturn = true; 228 | } 229 | int start = i + 1; 230 | string bufferContents = new string(charBuffer, start, endExclusive - start); 231 | endExclusive = i; 232 | string stringToYield = previousEnd == null ? bufferContents : bufferContents + previousEnd; 233 | if (!firstYield || stringToYield.Length != 0) 234 | { 235 | yield return stringToYield; 236 | } 237 | firstYield = false; 238 | previousEnd = null; 239 | } 240 | 241 | previousEnd = endExclusive == 0 ? null : (new string(charBuffer, 0, endExclusive) + previousEnd); 242 | 243 | // If we didn't decode the start of the array, put it at the end for next time 244 | if (leftOverData != 0) 245 | { 246 | Buffer.BlockCopy(buffer, 0, buffer, bufferSize, leftOverData); 247 | } 248 | } 249 | if (leftOverData != 0) 250 | { 251 | // At the start of the final buffer, we had the end of another character. 252 | throw new InvalidDataException("Invalid UTF-8 data at start of stream"); 253 | } 254 | if (firstYield && string.IsNullOrEmpty(previousEnd)) 255 | { 256 | yield break; 257 | } 258 | yield return previousEnd ?? ""; 259 | } 260 | finally 261 | { 262 | stream.Dispose(); 263 | } 264 | } 265 | 266 | IEnumerator IEnumerable.GetEnumerator() 267 | { 268 | return GetEnumerator(); 269 | } 270 | 271 | public void Dispose() 272 | { 273 | if (currStream!=null) 274 | { 275 | currStream.Dispose(); 276 | currStream = null; 277 | } 278 | } 279 | } 280 | } 281 | 282 | 283 | // StreamUtil.cs: 284 | public static class StreamUtil 285 | { 286 | public static void ReadExactly(Stream input, byte[] buffer, int bytesToRead) 287 | { 288 | int index = 0; 289 | while (index < bytesToRead) 290 | { 291 | int read = input.Read(buffer, index, bytesToRead - index); 292 | if (read == 0) 293 | { 294 | throw new EndOfStreamException 295 | (String.Format("End of stream reached with {0} byte{1} left to read.", 296 | bytesToRead - index, 297 | bytesToRead - index == 1 ? "s" : "")); 298 | } 299 | index += read; 300 | } 301 | } 302 | } -------------------------------------------------------------------------------- /PSFzf.Base.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [parameter(Position = 0, Mandatory = $false)][string]$PSReadlineChordProvider = 'Ctrl+t', 3 | [parameter(Position = 1, Mandatory = $false)][string]$PSReadlineChordReverseHistory = 'Ctrl+r', 4 | [parameter(Position = 2, Mandatory = $false)][string]$PSReadlineChordSetLocation = 'Alt+c', 5 | [parameter(Position = 3, Mandatory = $false)][string]$PSReadlineChordReverseHistoryArgs = 'Alt+a') 6 | 7 | $script:IsWindows = ($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows 8 | if ($script:IsWindows) { 9 | $script:ShellCmd = 'cmd.exe /S /C {0}' 10 | $script:DefaultFileSystemCmd = @" 11 | dir /s/b "{0}" 12 | "@ 13 | $script:DefaultFileSystemCmdDirOnly = @" 14 | dir /s/b/ad "{0}" 15 | "@ 16 | } 17 | else { 18 | $script:ShellCmd = '/bin/sh -c "{0}"' 19 | $script:DefaultFileSystemCmd = @" 20 | find -L '{0}' -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null 21 | "@ 22 | $script:DefaultFileSystemCmdDirOnly = @" 23 | find -L '{0}' -path '*/\.*' -prune -o -type d -print 2> /dev/null 24 | "@ 25 | } 26 | 27 | $script:RunningInWindowsTerminal = [bool]($env:WT_Session) -or [bool]($env:ConEmuANSI) 28 | if ($script:RunningInWindowsTerminal) { 29 | $script:DefaultFileSystemFdCmd = "fd.exe --color always . --full-path `"{0}`" --fixed-strings" 30 | } 31 | else { 32 | $script:DefaultFileSystemFdCmd = "fd.exe . --full-path `"{0}`" --fixed-strings" 33 | } 34 | 35 | $script:UseFd = $false 36 | $script:AltCCommand = [ScriptBlock] { 37 | param($Location) 38 | Set-Location $Location 39 | } 40 | 41 | function Get-FileSystemCmd { 42 | param($dir, [switch]$dirOnly = $false) 43 | 44 | # Note that there is no way to know how to list only directories using 45 | # FZF_DEFAULT_COMMAND, so we never use it in that case. 46 | if ($dirOnly -or [string]::IsNullOrWhiteSpace($env:FZF_DEFAULT_COMMAND)) { 47 | if ($script:UseFd) { 48 | if ($dirOnly) { 49 | "$($script:DefaultFileSystemFdCmd -f $dir) --type directory" 50 | } 51 | else { 52 | $script:DefaultFileSystemFdCmd -f $dir 53 | } 54 | } 55 | else { 56 | $cmd = $script:DefaultFileSystemCmd 57 | if ($dirOnly) { 58 | $cmd = $script:DefaultFileSystemCmdDirOnly 59 | } 60 | $script:ShellCmd -f ($cmd -f $dir) 61 | } 62 | } 63 | else { 64 | $script:ShellCmd -f ($env:FZF_DEFAULT_COMMAND -f $dir) 65 | } 66 | } 67 | 68 | class FzfDefaultOpts { 69 | [bool]$UsePsFzfOpts 70 | [string]$PrevEnv 71 | [bool]$Restored 72 | 73 | FzfDefaultOpts([string]$tempVal) { 74 | $this.UsePsFzfOpts = -not [string]::IsNullOrWhiteSpace($env:_PSFZF_FZF_DEFAULT_OPTS) 75 | $this.PrevEnv = $env:FZF_DEFAULT_OPTS 76 | $env:FZF_DEFAULT_OPTS = $this.Get() + " " + $tempVal 77 | } 78 | 79 | [string]Get() { 80 | if ($this.UsePsFzfOpts) { 81 | return $env:_PSFZF_FZF_DEFAULT_OPTS; 82 | } 83 | else { 84 | return $env:FZF_DEFAULT_OPTS; 85 | } 86 | } 87 | 88 | [void]Restore() { 89 | $env:FZF_DEFAULT_OPTS = $this.PrevEnv 90 | } 91 | } 92 | 93 | class FzfDefaultCmd { 94 | [string]$PrevEnv 95 | 96 | FzfDefaultCmd([string]$overrideVal) { 97 | $this.PrevEnv = $env:FZF_DEFAULT_COMMAND 98 | $env:FZF_DEFAULT_COMMAND = $overrideVal 99 | } 100 | 101 | [void]Restore() { 102 | $env:FZF_DEFAULT_COMMAND = $this.PrevEnv 103 | } 104 | } 105 | 106 | function FixCompletionResult($str) { 107 | if ([string]::IsNullOrEmpty($str)) { 108 | return "" 109 | } 110 | elseif ($str.Contains(" ") -or $str.Contains("`t")) { 111 | $str = $str.Replace("`r`n", "") 112 | # check if already quoted 113 | if (($str.StartsWith("'") -and $str.EndsWith("'")) -or ` 114 | ($str.StartsWith("""") -and $str.EndsWith(""""))) { 115 | return $str 116 | } 117 | else { 118 | return """{0}""" -f $str 119 | } 120 | 121 | } 122 | else { 123 | return $str.Replace("`r`n", "") 124 | } 125 | } 126 | 127 | 128 | 129 | #HACK: workaround for fact that PSReadLine seems to clear screen 130 | # after keyboard shortcut action is executed, and to work around a UTF8 131 | # PSReadLine issue (GitHub PSFZF issue #71) 132 | function InvokePromptHack() { 133 | $previousOutputEncoding = [Console]::OutputEncoding 134 | [Console]::OutputEncoding = [Text.Encoding]::UTF8 135 | 136 | try { 137 | [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt() 138 | } 139 | finally { 140 | [Console]::OutputEncoding = $previousOutputEncoding 141 | } 142 | } 143 | 144 | $script:FzfLocation = $null 145 | $script:OverrideFzfDefaults = $null 146 | $script:PSReadlineHandlerChords = @() 147 | $script:TabContinuousTrigger = [IO.Path]::DirectorySeparatorChar.ToString() 148 | 149 | $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = 150 | { 151 | $PsReadlineShortcuts.Values | Where-Object Chord | ForEach-Object { 152 | Remove-PSReadlineKeyHandler $_.Chord 153 | } 154 | RemovePsFzfAliases 155 | 156 | RemoveGitKeyBindings 157 | } 158 | 159 | # if the quoted string ends with a '\', and we need to escape it for Windows: 160 | function script:PrepareArg($argStr) { 161 | if (-not $argStr.EndsWith("\\") -and $argStr.EndsWith('\')) { 162 | return $argStr + '\' 163 | } 164 | else { 165 | return $argStr 166 | } 167 | } 168 | 169 | function Set-PsFzfOption { 170 | param( 171 | [switch] 172 | $TabExpansion, 173 | [string] 174 | $PSReadlineChordProvider, 175 | [string] 176 | $PSReadlineChordReverseHistory, 177 | [string] 178 | $PSReadlineChordSetLocation, 179 | [string] 180 | $PSReadlineChordReverseHistoryArgs, 181 | [switch] 182 | $GitKeyBindings, 183 | [switch] 184 | $EnableAliasFuzzyEdit, 185 | [switch] 186 | $EnableAliasFuzzyFasd, 187 | [switch] 188 | $EnableAliasFuzzyHistory, 189 | [switch] 190 | $EnableAliasFuzzyKillProcess, 191 | [switch] 192 | $EnableAliasFuzzySetLocation, 193 | [switch] 194 | $EnableAliasFuzzyScoop, 195 | [switch] 196 | $EnableAliasFuzzySetEverything, 197 | [switch] 198 | $EnableAliasFuzzyZLocation, 199 | [switch] 200 | $EnableAliasFuzzyGitStatus, 201 | [switch] 202 | $EnableFd, 203 | [string] 204 | $TabContinuousTrigger, 205 | [ScriptBlock] 206 | $AltCCommand 207 | ) 208 | if ($PSBoundParameters.ContainsKey('TabExpansion')) { 209 | SetTabExpansion $TabExpansion 210 | } 211 | 212 | if ($PSBoundParameters.ContainsKey('GitKeyBindings')) { 213 | SetGitKeyBindings $GitKeyBindings 214 | } 215 | 216 | $PsReadlineShortcuts.GetEnumerator() | ForEach-Object { 217 | if ($PSBoundParameters.ContainsKey($_.key)) { 218 | $info = $_.value 219 | $newChord = $PSBoundParameters[$_.key] 220 | $result = SetPsReadlineShortcut $newChord -Override $info.BriefDesc $info.Desc $info.ScriptBlock 221 | if ($result) { 222 | if (($null -ne $info.Chord) -and ($info.Chord.ToLower() -ne $newChord.ToLower())) { 223 | Remove-PSReadLineKeyHandler $info.Chord 224 | } 225 | $info.Chord = $newChord 226 | } 227 | } 228 | } 229 | 230 | if ($EnableAliasFuzzyEdit) { SetPsFzfAlias "fe" Invoke-FuzzyEdit } 231 | if ($EnableAliasFuzzyFasd) { SetPsFzfAlias "ff" Invoke-FuzzyFasd } 232 | if ($EnableAliasFuzzyHistory) { SetPsFzfAlias "fh" Invoke-FuzzyHistory } 233 | if ($EnableAliasFuzzyKillProcess) { SetPsFzfAlias "fkill" Invoke-FuzzyKillProcess } 234 | if ($EnableAliasFuzzySetLocation) { SetPsFzfAlias "fd" Invoke-FuzzySetLocation } 235 | if ($EnableAliasFuzzyZLocation) { SetPsFzfAlias "fz" Invoke-FuzzyZLocation } 236 | if ($EnableAliasFuzzyGitStatus) { SetPsFzfAlias "fgs" Invoke-FuzzyGitStatus } 237 | if ($EnableAliasFuzzyScoop) { SetPsFzfAlias "fs" Invoke-FuzzyScoop } 238 | if ($EnableAliasFuzzySetEverything) { 239 | if (${function:Set-LocationFuzzyEverything}) { 240 | SetPsFzfAlias "cde" Set-LocationFuzzyEverything 241 | } 242 | } 243 | if ($PSBoundParameters.ContainsKey('EnableFd')) { 244 | $script:UseFd = $EnableFd 245 | } 246 | if ($PSBoundParameters.ContainsKey('TabContinuousTrigger')) { 247 | $script:TabContinuousTrigger = $TabContinuousTrigger 248 | } 249 | 250 | if ($PSBoundParameters.ContainsKey('AltCCommand')) { 251 | $script:AltCCommand = $AltCCommand 252 | } 253 | } 254 | 255 | function Stop-Pipeline { 256 | # borrowed from https://stackoverflow.com/a/34800670: 257 | (Add-Type -Passthru -TypeDefinition ' 258 | using System.Management.Automation; 259 | namespace PSFzf.IO { 260 | public static class CustomPipelineStopper { 261 | public static void Stop(Cmdlet cmdlet) { 262 | throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet); 263 | } 264 | } 265 | }')::Stop($PSCmdlet) 266 | } 267 | 268 | function Invoke-Fzf { 269 | param( 270 | # Search 271 | [Alias("x")] 272 | [switch]$Extended, 273 | [Alias('e')] 274 | [switch]$Exact, 275 | [Alias('i')] 276 | [switch]$CaseInsensitive, 277 | [switch]$CaseSensitive, 278 | [ValidateSet('default', 'path', 'history')] 279 | [string] 280 | $Scheme = $null, 281 | [Alias('d')] 282 | [string]$Delimiter, 283 | [switch]$NoSort, 284 | [Alias('tac')] 285 | [switch]$ReverseInput, 286 | [switch]$Phony, 287 | [ValidateSet('length', 'begin', 'end', 'index')] 288 | [string] 289 | $Tiebreak = $null, 290 | [switch]$Disabled, 291 | 292 | # Interface 293 | [Alias('m')] 294 | [switch]$Multi, 295 | [switch]$HighlightLine, 296 | [switch]$NoMouse, 297 | [string[]]$Bind, 298 | [switch]$Cycle, 299 | [switch]$KeepRight, 300 | [switch]$NoHScroll, 301 | [switch]$FilepathWord, 302 | 303 | # Layout 304 | [ValidatePattern("^[1-9]+[0-9]+$|^[1-9][0-9]?%?$|^100%?$")] 305 | [string]$Height, 306 | [ValidateRange(1, [int]::MaxValue)] 307 | [int]$MinHeight, 308 | [ValidateSet('default', 'reverse', 'reverse-list')] 309 | [string]$Layout = $null, 310 | [switch]$Border, 311 | [ValidateSet('rounded', 'sharp', 'bold', 'block', 'double', 'horizontal', 'vertical', 'top', 'bottom', 'left', 'right', 'none')] 312 | [string]$BorderStyle, 313 | [string]$BorderLabel, 314 | [ValidateSet('default', 'inline', 'hidden')] 315 | [string]$Info = $null, 316 | [string]$Prompt, 317 | [string]$Pointer, 318 | [string]$Marker, 319 | [string]$Header, 320 | [int]$HeaderLines = -1, 321 | 322 | # Display 323 | [switch]$Read0, 324 | [switch]$Ansi, 325 | [int]$Tabstop = 8, 326 | [string]$Color, 327 | [switch]$NoBold, 328 | 329 | # History 330 | [string]$History, 331 | [int]$HistorySize = -1, 332 | 333 | #Preview 334 | [string]$Preview, 335 | [string]$PreviewWindow, 336 | 337 | # Scripting 338 | [Alias('q')] 339 | [string]$Query, 340 | [Alias('s1')] 341 | [switch]$Select1, 342 | [Alias('e0')] 343 | [switch]$Exit0, 344 | [Alias('f')] 345 | [string]$Filter, 346 | [switch]$PrintQuery, 347 | [string]$Expect, 348 | 349 | [Parameter(ValueFromPipeline = $True)] 350 | [object[]]$Input 351 | ) 352 | 353 | Begin { 354 | # process parameters: 355 | $arguments = '' 356 | $WriteLine = $true 357 | if ($PSBoundParameters.ContainsKey('Extended') -and $Extended) { $arguments += '--extended ' } 358 | if ($PSBoundParameters.ContainsKey('Exact') -and $Exact) { $arguments += '--exact ' } 359 | if ($PSBoundParameters.ContainsKey('CaseInsensitive') -and $CaseInsensitive) { $arguments += '-i ' } 360 | if ($PSBoundParameters.ContainsKey('CaseSensitive') -and $CaseSensitive) { $arguments += '+i ' } 361 | if ($PSBoundParameters.ContainsKey('Scheme') -and ![string]::IsNullOrWhiteSpace($Scheme)) { $arguments += "--scheme=$Scheme " } 362 | if ($PSBoundParameters.ContainsKey('Delimiter') -and ![string]::IsNullOrWhiteSpace($Delimiter)) { $arguments += "--delimiter=$Delimiter " } 363 | if ($PSBoundParameters.ContainsKey('NoSort') -and $NoSort) { $arguments += '--no-sort ' } 364 | if ($PSBoundParameters.ContainsKey('ReverseInput') -and $ReverseInput) { $arguments += '--tac ' } 365 | if ($PSBoundParameters.ContainsKey('Phony') -and $Phony) { $arguments += '--phony ' } 366 | if ($PSBoundParameters.ContainsKey('Tiebreak') -and ![string]::IsNullOrWhiteSpace($Tiebreak)) { $arguments += "--tiebreak=$Tiebreak " } 367 | if ($PSBoundParameters.ContainsKey('Disabled') -and $Disabled) { $arguments += '--disabled ' } 368 | if ($PSBoundParameters.ContainsKey('Multi') -and $Multi) { $arguments += '--multi ' } 369 | if ($PSBoundParameters.ContainsKey('Highlightline') -and $Highlightline) { $arguments += '--highlight-line ' } 370 | if ($PSBoundParameters.ContainsKey('NoMouse') -and $NoMouse) { $arguments += '--no-mouse ' } 371 | if ($PSBoundParameters.ContainsKey('Bind') -and $Bind.Length -ge 1) { $Bind | ForEach-Object { $arguments += "--bind=""$_"" " } } 372 | if ($PSBoundParameters.ContainsKey('Reverse') -and $Reverse) { $arguments += '--reverse ' } 373 | if ($PSBoundParameters.ContainsKey('Cycle') -and $Cycle) { $arguments += '--cycle ' } 374 | if ($PSBoundParameters.ContainsKey('KeepRight') -and $KeepRight) { $arguments += '--keep-right ' } 375 | if ($PSBoundParameters.ContainsKey('NoHScroll') -and $NoHScroll) { $arguments += '--no-hscroll ' } 376 | if ($PSBoundParameters.ContainsKey('FilepathWord') -and $FilepathWord) { $arguments += '--filepath-word ' } 377 | if ($PSBoundParameters.ContainsKey('Height') -and ![string]::IsNullOrWhiteSpace($Height)) { $arguments += "--height=$height " } 378 | if ($PSBoundParameters.ContainsKey('MinHeight') -and $MinHeight -ge 0) { $arguments += "--min-height=$MinHeight " } 379 | if ($PSBoundParameters.ContainsKey('Layout') -and ![string]::IsNullOrWhiteSpace($Layout)) { $arguments += "--layout=$Layout " } 380 | if ($PSBoundParameters.ContainsKey('Border') -and $Border) { $arguments += '--border ' } 381 | if ($PSBoundParameters.ContainsKey('BorderLabel') -and ![string]::IsNullOrWhiteSpace($BorderLabel)) { $arguments += "--border-label=""$BorderLabel"" " } 382 | if ($PSBoundParameters.ContainsKey('BorderStyle') -and ![string]::IsNullOrWhiteSpace($BorderStyle)) { $arguments += "--border=$BorderStyle " } 383 | if ($PSBoundParameters.ContainsKey('Info') -and ![string]::IsNullOrWhiteSpace($Info)) { $arguments += "--info=$Info " } 384 | if ($PSBoundParameters.ContainsKey('Prompt') -and ![string]::IsNullOrWhiteSpace($Prompt)) { $arguments += "--prompt=""$Prompt"" " } 385 | if ($PSBoundParameters.ContainsKey('Pointer') -and ![string]::IsNullOrWhiteSpace($Pointer)) { $arguments += "--pointer=""$Pointer"" " } 386 | if ($PSBoundParameters.ContainsKey('Marker') -and ![string]::IsNullOrWhiteSpace($Marker)) { $arguments += "--marker=""$Marker"" " } 387 | if ($PSBoundParameters.ContainsKey('Header') -and ![string]::IsNullOrWhiteSpace($Header)) { $arguments += "--header=""$Header"" " } 388 | if ($PSBoundParameters.ContainsKey('HeaderLines') -and $HeaderLines -ge 0) { $arguments += "--header-lines=$HeaderLines " } 389 | if ($PSBoundParameters.ContainsKey('Read0') -and $Read0) { $arguments += '--read0 ' ; $WriteLine = $false } 390 | if ($PSBoundParameters.ContainsKey('Ansi') -and $Ansi) { $arguments += '--ansi ' } 391 | if ($PSBoundParameters.ContainsKey('Tabstop') -and $Tabstop -ge 0) { $arguments += "--tabstop=$Tabstop " } 392 | if ($PSBoundParameters.ContainsKey('Color') -and ![string]::IsNullOrWhiteSpace($Color)) { $arguments += "--color=""$Color"" " } 393 | if ($PSBoundParameters.ContainsKey('NoBold') -and $NoBold) { $arguments += '--no-bold ' } 394 | if ($PSBoundParameters.ContainsKey('History') -and $History) { $arguments += "--history=""$History"" " } 395 | if ($PSBoundParameters.ContainsKey('HistorySize') -and $HistorySize -ge 1) { $arguments += "--history-size=$HistorySize " } 396 | if ($PSBoundParameters.ContainsKey('Preview') -and ![string]::IsNullOrWhiteSpace($Preview)) { $arguments += "--preview=""$Preview"" " } 397 | if ($PSBoundParameters.ContainsKey('PreviewWindow') -and ![string]::IsNullOrWhiteSpace($PreviewWindow)) { $arguments += "--preview-window=""$PreviewWindow"" " } 398 | if ($PSBoundParameters.ContainsKey('Query') -and ![string]::IsNullOrWhiteSpace($Query)) { $arguments += "--query=""{0}"" " -f $(PrepareArg $Query) } 399 | if ($PSBoundParameters.ContainsKey('Select1') -and $Select1) { $arguments += '--select-1 ' } 400 | if ($PSBoundParameters.ContainsKey('Exit0') -and $Exit0) { $arguments += '--exit-0 ' } 401 | if ($PSBoundParameters.ContainsKey('Filter') -and ![string]::IsNullOrEmpty($Filter)) { $arguments += "--filter=$Filter " } 402 | if ($PSBoundParameters.ContainsKey('PrintQuery') -and $PrintQuery) { $arguments += '--print-query ' } 403 | if ($PSBoundParameters.ContainsKey('Expect') -and ![string]::IsNullOrWhiteSpace($Expect)) { $arguments += "--expect=""$Expect"" " } 404 | 405 | if (!$script:OverrideFzfDefaults) { 406 | $script:OverrideFzfDefaults = [FzfDefaultOpts]::new("") 407 | } 408 | 409 | if ($script:UseHeightOption -and [string]::IsNullOrWhiteSpace($Height) -and ` 410 | ([string]::IsNullOrWhiteSpace($script:OverrideFzfDefaults.Get()) -or ` 411 | (-not $script:OverrideFzfDefaults.Get().Contains('--height')))) { 412 | $arguments += "--height=40% " 413 | } 414 | 415 | if ($Border -eq $true -and -not [string]::IsNullOrWhiteSpace($BorderStyle)) { 416 | throw '-Border and -BorderStyle are mutally exclusive' 417 | } 418 | if ($script:UseFd -and $script:RunningInWindowsTerminal -and -not $arguments.Contains('--ansi')) { 419 | $arguments += "--ansi " 420 | } 421 | 422 | # prepare to start process: 423 | $process = New-Object System.Diagnostics.Process 424 | $process.StartInfo.FileName = $script:FzfLocation 425 | $process.StartInfo.Arguments = $arguments 426 | $process.StartInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8 427 | $process.StartInfo.RedirectStandardInput = $true 428 | $process.StartInfo.RedirectStandardOutput = $true 429 | $process.StartInfo.UseShellExecute = $false 430 | if ($pwd.Provider.Name -eq 'FileSystem') { 431 | $process.StartInfo.WorkingDirectory = $pwd.ProviderPath 432 | } 433 | 434 | # Adding event handers for stdout: 435 | $stdOutEventId = "PsFzfStdOutEh-" + [System.Guid]::NewGuid() 436 | $stdOutEvent = Register-ObjectEvent -InputObject $process ` 437 | -EventName 'OutputDataReceived' ` 438 | -SourceIdentifier $stdOutEventId 439 | 440 | $processHasExited = new-object psobject -property @{flag = $false } 441 | # register on exit: 442 | $scriptBlockExited = { 443 | $Event.MessageData.flag = $true 444 | } 445 | $exitedEventId = "PsFzfExitedEh-" + [System.Guid]::NewGuid() 446 | $exitedEvent = Register-ObjectEvent -InputObject $process ` 447 | -Action $scriptBlockExited -EventName 'Exited' ` 448 | -SourceIdentifier $exitedEventId ` 449 | -MessageData $processHasExited 450 | 451 | $process.Start() | Out-Null 452 | $process.BeginOutputReadLine() | Out-Null 453 | 454 | $utf8Encoding = New-Object System.Text.UTF8Encoding -ArgumentList $false 455 | $script:utf8Stream = New-Object System.IO.StreamWriter -ArgumentList $process.StandardInput.BaseStream, $utf8Encoding 456 | 457 | $cleanup = [scriptblock] { 458 | if ($script:OverrideFzfDefaults) { 459 | $script:OverrideFzfDefaults.Restore() 460 | $script:OverrideFzfDefaults = $null 461 | } 462 | 463 | try { 464 | $process.StandardInput.Close() | Out-Null 465 | $process.WaitForExit() 466 | } 467 | catch { 468 | # do nothing 469 | } 470 | 471 | try { 472 | #$stdOutEventId,$exitedEventId | ForEach-Object { 473 | # Unregister-Event $_ -ErrorAction SilentlyContinue 474 | #} 475 | 476 | $stdOutEvent, $exitedEvent | ForEach-Object { 477 | Stop-Job $_ -ErrorAction SilentlyContinue 478 | Remove-Job $_ -Force -ErrorAction SilentlyContinue 479 | } 480 | } 481 | catch { 482 | 483 | } 484 | 485 | # events seem to be generated out of order - therefore, we need sort by time created. For example, 486 | # -print-query and -expect and will be outputted first if specified on the command line. 487 | Get-Event -SourceIdentifier $stdOutEventId | ` 488 | Sort-Object -Property TimeGenerated | ` 489 | Where-Object { $null -ne $_.SourceEventArgs.Data } | ForEach-Object { 490 | Write-Output $_.SourceEventArgs.Data 491 | Remove-Event -EventIdentifier $_.EventIdentifier 492 | } 493 | } 494 | $checkProcessStatus = [scriptblock] { 495 | if ($processHasExited.flag -or $process.HasExited) { 496 | $script:utf8stream = $null 497 | & $cleanup 498 | Stop-Pipeline 499 | } 500 | } 501 | } 502 | 503 | Process { 504 | $hasInput = $PSBoundParameters.ContainsKey('Input') 505 | 506 | # handle no piped input: 507 | if (!$hasInput) { 508 | # optimization for filesystem provider: 509 | if ($PWD.Provider.Name -eq 'FileSystem') { 510 | Invoke-Expression (Get-FileSystemCmd $PWD.ProviderPath) | ForEach-Object { 511 | try { 512 | if ($WriteLine) { 513 | $utf8Stream.WriteLine($_) 514 | } 515 | else { 516 | $utf8Stream.Write($_) 517 | } 518 | } 519 | catch [System.Management.Automation.MethodInvocationException] { 520 | # Possibly broken pipe. Next clause will handle graceful shutdown. 521 | } 522 | finally { 523 | & $checkProcessStatus 524 | } 525 | } 526 | } 527 | else { 528 | Get-ChildItem . -Recurse -ErrorAction SilentlyContinue | ForEach-Object { 529 | $item = $_ 530 | if ($item -is [System.String]) { 531 | $str = $item 532 | } 533 | else { 534 | # search through common properties: 535 | $str = $item.FullName 536 | if ($null -eq $str) { 537 | $str = $item.Name 538 | if ($null -eq $str) { 539 | $str = $item.ToString() 540 | } 541 | } 542 | } 543 | try { 544 | if ($WriteLine) { 545 | $utf8Stream.WriteLine($str) 546 | } 547 | else { 548 | $utf8Stream.Write($str) 549 | } 550 | } 551 | catch [System.Management.Automation.MethodInvocationException] { 552 | # Possibly broken pipe. We will shutdown the pipe below. 553 | } 554 | & $checkProcessStatus 555 | } 556 | } 557 | 558 | } 559 | else { 560 | foreach ($item in $Input) { 561 | if ($item -is [System.String]) { 562 | $str = $item 563 | } 564 | else { 565 | # search through common properties: 566 | $str = $item.FullName 567 | if ($null -eq $str) { 568 | $str = $item.Name 569 | if ($null -eq $str) { 570 | $str = $item.ToString() 571 | } 572 | } 573 | } 574 | try { 575 | if ($WriteLine) { 576 | $utf8Stream.WriteLine($str) 577 | } 578 | else { 579 | $utf8Stream.Write($str) 580 | } 581 | } 582 | catch [System.Management.Automation.MethodInvocationException] { 583 | # Possibly broken pipe. We will shutdown the pipe below. 584 | } 585 | & $checkProcessStatus 586 | } 587 | } 588 | if ($null -ne $utf8Stream) { 589 | try { 590 | $utf8Stream.Flush() 591 | } 592 | catch [System.Management.Automation.MethodInvocationException] { 593 | # Possibly broken pipe, check process status. 594 | & $checkProcessStatus 595 | } 596 | } 597 | } 598 | 599 | End { 600 | & $cleanup 601 | } 602 | } 603 | 604 | function Find-CurrentPath { 605 | param([string]$line, [int]$cursor, [ref]$leftCursor, [ref]$rightCursor) 606 | 607 | if ($line.Length -eq 0) { 608 | $leftCursor.Value = $rightCursor.Value = 0 609 | return $null 610 | } 611 | 612 | if ($cursor -ge $line.Length) { 613 | $leftCursorTmp = $cursor - 1 614 | } 615 | else { 616 | $leftCursorTmp = $cursor 617 | } 618 | :leftSearch for (; $leftCursorTmp -ge 0; $leftCursorTmp--) { 619 | if ([string]::IsNullOrWhiteSpace($line[$leftCursorTmp])) { 620 | if (($leftCursorTmp -lt $cursor) -and ($leftCursorTmp -lt $line.Length - 1)) { 621 | $leftCursorTmpQuote = $leftCursorTmp - 1 622 | $leftCursorTmp = $leftCursorTmp + 1 623 | } 624 | else { 625 | $leftCursorTmpQuote = $leftCursorTmp 626 | } 627 | for (; $leftCursorTmpQuote -ge 0; $leftCursorTmpQuote--) { 628 | if (($line[$leftCursorTmpQuote] -eq '"') -and (($leftCursorTmpQuote -le 0) -or ($line[$leftCursorTmpQuote - 1] -ne '"'))) { 629 | $leftCursorTmp = $leftCursorTmpQuote 630 | break leftSearch 631 | } 632 | elseif (($line[$leftCursorTmpQuote] -eq "'") -and (($leftCursorTmpQuote -le 0) -or ($line[$leftCursorTmpQuote - 1] -ne "'"))) { 633 | $leftCursorTmp = $leftCursorTmpQuote 634 | break leftSearch 635 | } 636 | } 637 | break leftSearch 638 | } 639 | } 640 | :rightSearch for ($rightCursorTmp = $cursor; $rightCursorTmp -lt $line.Length; $rightCursorTmp++) { 641 | if ([string]::IsNullOrWhiteSpace($line[$rightCursorTmp])) { 642 | if ($rightCursorTmp -gt $cursor) { 643 | $rightCursorTmp = $rightCursorTmp - 1 644 | } 645 | for ($rightCursorTmpQuote = $rightCursorTmp + 1; $rightCursorTmpQuote -lt $line.Length; $rightCursorTmpQuote++) { 646 | if (($line[$rightCursorTmpQuote] -eq '"') -and (($rightCursorTmpQuote -gt $line.Length) -or ($line[$rightCursorTmpQuote + 1] -ne '"'))) { 647 | $rightCursorTmp = $rightCursorTmpQuote 648 | break rightSearch 649 | } 650 | elseif (($line[$rightCursorTmpQuote] -eq "'") -and (($rightCursorTmpQuote -gt $line.Length) -or ($line[$rightCursorTmpQuote + 1] -ne "'"))) { 651 | $rightCursorTmp = $rightCursorTmpQuote 652 | break rightSearch 653 | } 654 | } 655 | break rightSearch 656 | } 657 | } 658 | if ($leftCursorTmp -lt 0 -or $leftCursorTmp -gt $line.Length - 1) { $leftCursorTmp = 0 } 659 | if ($rightCursorTmp -ge $line.Length) { $rightCursorTmp = $line.Length - 1 } 660 | $leftCursor.Value = $leftCursorTmp 661 | $rightCursor.Value = $rightCursorTmp 662 | $str = -join ($line[$leftCursorTmp..$rightCursorTmp]) 663 | return $str.Trim("'").Trim('"') 664 | } 665 | 666 | function Invoke-FzfDefaultSystem { 667 | param($ProviderPath, $DefaultOpts) 668 | 669 | $script:OverrideFzfDefaultOpts = [FzfDefaultOpts]::new($DefaultOpts) 670 | $arguments = '' 671 | if (-not $script:OverrideFzfDefaultOpts.Get().Contains('--height')) { 672 | $arguments += "--height=40% " 673 | } 674 | 675 | if ($script:UseFd -and $script:RunningInWindowsTerminal -and -not $script:OverrideFzfDefaultOpts.Get().Contains('--ansi')) { 676 | $arguments += "--ansi " 677 | } 678 | if ($script:UseWalker -and -not $script:OverrideFzfDefaultOpts.Get().Contains('--walker')) { 679 | $arguments += "--walker=file,dir " 680 | } 681 | 682 | $script:OverrideFzfDefaultCommand = [FzfDefaultCmd]::new('') 683 | try { 684 | # native filesystem walking is MUCH faster with native Go code: 685 | $env:FZF_DEFAULT_COMMAND = "" 686 | 687 | $result = @() 688 | 689 | # --height doesn't work with Invoke-Expression - not sure why. Thus, we need to use 690 | # System.Diagnostics.Process: 691 | $process = New-Object System.Diagnostics.Process 692 | $process.StartInfo.FileName = $script:FzfLocation 693 | $process.StartInfo.Arguments = $arguments 694 | $process.StartInfo.RedirectStandardInput = $false 695 | $process.StartInfo.RedirectStandardOutput = $true 696 | $process.StartInfo.UseShellExecute = $false 697 | $process.StartInfo.WorkingDirectory = $ProviderPath 698 | 699 | # Adding event handers for stdout: 700 | $stdOutEventId = "Invoke-FzfDefaultSystem-PsFzfStdOutEh-" + [System.Guid]::NewGuid() 701 | $stdOutEvent = Register-ObjectEvent -InputObject $process ` 702 | -EventName 'OutputDataReceived' ` 703 | -SourceIdentifier $stdOutEventId 704 | 705 | $process.Start() | Out-Null 706 | $process.BeginOutputReadLine() | Out-Null 707 | $process.WaitForExit() 708 | 709 | Get-Event -SourceIdentifier $stdOutEventId | ` 710 | Sort-Object -Property TimeGenerated | ` 711 | Where-Object { $null -ne $_.SourceEventArgs.Data } | ForEach-Object { 712 | $result += $_.SourceEventArgs.Data 713 | Remove-Event -EventIdentifier $_.EventIdentifier 714 | } 715 | Remove-Event -SourceIdentifier $stdOutEventId 716 | } 717 | catch { 718 | # ignore errors 719 | } 720 | finally { 721 | if ($script:OverrideFzfDefaultCommand) { 722 | $script:OverrideFzfDefaultCommand.Restore() 723 | $script:OverrideFzfDefaultCommand = $null 724 | } 725 | if ($script:OverrideFzfDefaultOpts) { 726 | $script:OverrideFzfDefaultOpts.Restore() 727 | $script:OverrideFzfDefaultOpts = $null 728 | } 729 | } 730 | 731 | return $result 732 | } 733 | 734 | function Invoke-FzfPsReadlineHandlerProvider { 735 | $leftCursor = $null 736 | $rightCursor = $null 737 | $line = $null 738 | $cursor = $null 739 | [Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor) 740 | $currentPath = Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) 741 | $addSpace = $null -ne $currentPath -and $currentPath.StartsWith(" ") 742 | if ([String]::IsNullOrWhitespace($currentPath) -or !(Test-Path $currentPath)) { 743 | $currentPath = $PWD 744 | } 745 | $isUsingPath = -not [string]::IsNullOrWhiteSpace($currentPath) 746 | 747 | $result = @() 748 | try { 749 | $script:OverrideFzfDefaults = [FzfDefaultOpts]::new($env:FZF_CTRL_T_OPTS) 750 | 751 | if (-not [System.String]::IsNullOrWhiteSpace($env:FZF_CTRL_T_COMMAND)) { 752 | Invoke-Expression ($env:FZF_CTRL_T_COMMAND) | Invoke-Fzf -Multi | ForEach-Object { $result += $_ } 753 | } 754 | else { 755 | if (-not $isUsingPath) { 756 | Invoke-Fzf -Multi | ForEach-Object { $result += $_ } 757 | } 758 | else { 759 | $resolvedPath = Resolve-Path $currentPath -ErrorAction SilentlyContinue 760 | $providerName = $null 761 | if ($null -ne $resolvedPath) { 762 | $providerName = $resolvedPath.Provider.Name 763 | } 764 | switch ($providerName) { 765 | # Get-ChildItem is way too slow - we optimize using our own function for calling fzf directly (Invoke-FzfDefaultSystem): 766 | 'FileSystem' { 767 | if (-not $script:UseFd) { 768 | $result = Invoke-FzfDefaultSystem $resolvedPath.ProviderPath '--multi' 769 | } 770 | else { 771 | Invoke-Expression (Get-FileSystemCmd $resolvedPath.ProviderPath) | Invoke-Fzf -Multi | ForEach-Object { $result += $_ } 772 | } 773 | } 774 | 'Registry' { Get-ChildItem $currentPath -Recurse -ErrorAction SilentlyContinue | Select-Object Name -ExpandProperty Name | Invoke-Fzf -Multi | ForEach-Object { $result += $_ } } 775 | $null { Get-ChildItem $currentPath -Recurse -ErrorAction SilentlyContinue | Select-Object FullName -ExpandProperty FullName | Invoke-Fzf -Multi | ForEach-Object { $result += $_ } } 776 | Default {} 777 | } 778 | } 779 | } 780 | } 781 | catch { 782 | # catch custom exception 783 | } 784 | finally { 785 | if ($script:OverrideFzfDefaults) { 786 | $script:OverrideFzfDefaults.Restore() 787 | $script:OverrideFzfDefaults = $null 788 | } 789 | } 790 | 791 | InvokePromptHack 792 | 793 | if ($null -ne $result) { 794 | # quote strings if we need to: 795 | if ($result -is [system.array]) { 796 | for ($i = 0; $i -lt $result.Length; $i++) { 797 | if ($isUsingPath) { 798 | $resultFull = Join-Path $currentPath $result[$i] 799 | } 800 | else { 801 | $resultFull = $result[$i] 802 | } 803 | $result[$i] = FixCompletionResult $resultFull 804 | } 805 | } 806 | else { 807 | if ($isUsingPath) { 808 | $result = Join-Path $currentPath $result 809 | } 810 | $result = FixCompletionResult $result 811 | } 812 | 813 | $str = $result -join ',' 814 | if ($addSpace) { 815 | $str = ' ' + $str 816 | } 817 | $replaceLen = $rightCursor - $leftCursor 818 | if ($rightCursor -eq 0 -and $leftCursor -eq 0) { 819 | [Microsoft.PowerShell.PSConsoleReadLine]::Insert($str) 820 | } 821 | else { 822 | [Microsoft.PowerShell.PSConsoleReadLine]::Replace($leftCursor, $replaceLen + 1, $str) 823 | } 824 | } 825 | } 826 | 827 | function Get-PickedHistory($Query = '', [switch]$UsePSReadLineHistory) { 828 | try { 829 | $script:OverrideFzfDefaults = [FzfDefaultOpts]::new($env:FZF_CTRL_R_OPTS) 830 | 831 | $fileHist = @{} 832 | if ($UsePSReadLineHistory) { 833 | $reader = New-Object PSFzf.IO.ReverseLineReader -ArgumentList $((Get-PSReadlineOption).HistorySavePath) 834 | 835 | $result = $reader.GetEnumerator() | ForEach-Object ` 836 | -Begin { $lines = @() } ` 837 | -Process { 838 | if ([string]::IsNullOrWhiteSpace($_)) { 839 | # do nothing 840 | } 841 | elseif ($lines.Count -eq 0) { 842 | $lines = @($_) # start collecting lines 843 | } 844 | elseif ($_.EndsWith('`')) { 845 | $lines += $_.TrimEnd("`n").TrimEnd('`') # continue collecting lines with backtick 846 | } 847 | else { 848 | if ($lines.Length -eq 1) { 849 | $lines = $lines[0] 850 | } 851 | else { 852 | $lines = $lines[-1.. - ($lines.Length)] -join "`n" 853 | } 854 | # found a new line, so emit and start over: 855 | if (-not $fileHist.ContainsKey($lines)) { 856 | $fileHist.Add($lines, $true) 857 | $lines + [char]0 858 | } 859 | 860 | $lines = @($_) 861 | } 862 | } ` 863 | -End { 864 | if ($lines.Length -eq 1) { 865 | $lines = $lines[0] 866 | } 867 | else { 868 | $lines = $lines[-1.. - ($lines.Length)] -join "`n" 869 | } 870 | # found a new line, so emit and start over: 871 | if (-not $fileHist.ContainsKey($lines)) { 872 | $fileHist.Add($lines, $true) 873 | $lines + [char]0 874 | } 875 | } | Invoke-Fzf -Query "$Query" -Bind ctrl-r:toggle-sort, ctrl-z:ignore -Scheme history -Read0 -HighlightLine 876 | } 877 | else { 878 | $result = Get-History | ForEach-Object { $_.CommandLine } | ForEach-Object { 879 | if (-not $fileHist.ContainsKey($_)) { 880 | $fileHist.Add($_, $true) 881 | $_ 882 | } 883 | } | Invoke-Fzf -Query "$Query" -Reverse -Scheme history 884 | } 885 | 886 | } 887 | catch { 888 | # catch custom exception 889 | } 890 | finally { 891 | if ($script:OverrideFzfDefaults) { 892 | $script:OverrideFzfDefaults.Restore() 893 | $script:OverrideFzfDefaults = $null 894 | } 895 | 896 | # ensure that stream is closed: 897 | if ($reader) { 898 | $reader.Dispose() 899 | } 900 | } 901 | 902 | $result 903 | } 904 | function Invoke-FzfPsReadlineHandlerHistory { 905 | $result = $null 906 | $line = $null 907 | $cursor = $null 908 | [Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor) 909 | 910 | $result = Get-PickedHistory -Query $line -UsePSReadLineHistory 911 | 912 | InvokePromptHack 913 | 914 | if (-not [string]::IsNullOrEmpty($result)) { 915 | [Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, $result) 916 | } 917 | } 918 | 919 | function Invoke-FzfPsReadlineHandlerHistoryArgs { 920 | $result = @() 921 | try { 922 | $line = $null 923 | $cursor = $null 924 | [Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor) 925 | $line = $line.Insert($cursor, "{}") # add marker for fzf 926 | 927 | $contentTable = @{} 928 | $reader = New-Object PSFzf.IO.ReverseLineReader -ArgumentList $((Get-PSReadlineOption).HistorySavePath) 929 | 930 | $fileHist = @{} 931 | $reader.GetEnumerator() | ForEach-Object { 932 | if (-not $fileHist.ContainsKey($_)) { 933 | $fileHist.Add($_, $true) 934 | [System.Management.Automation.PsParser]::Tokenize($_, [ref] $null) 935 | } 936 | } | Where-Object { $_.type -eq "commandargument" -or $_.type -eq "string" } | 937 | ForEach-Object { 938 | if (!$contentTable.ContainsKey($_.Content)) { $_.Content ; $contentTable[$_.Content] = $true } 939 | } | Invoke-Fzf -Multi | ForEach-Object { $result += $_ } 940 | } 941 | catch { 942 | # catch custom exception 943 | } 944 | finally { 945 | $reader.Dispose() 946 | } 947 | 948 | InvokePromptHack 949 | 950 | [array]$result = $result | ForEach-Object { 951 | # add quotes: 952 | if ($_.Contains(" ") -or $_.Contains("`t")) { 953 | "'{0}'" -f $_.Replace("'", "''") 954 | } 955 | else { 956 | $_ 957 | } 958 | } 959 | if ($result.Length -ge 0) { 960 | [Microsoft.PowerShell.PSConsoleReadLine]::Replace($cursor, 0, ($result -join ' ')) 961 | } 962 | } 963 | 964 | function Invoke-FzfPsReadlineHandlerSetLocation { 965 | $result = $null 966 | try { 967 | $script:OverrideFzfDefaults = [FzfDefaultOpts]::new($env:FZF_ALT_C_OPTS) 968 | 969 | if ($null -eq $env:FZF_ALT_C_COMMAND) { 970 | Invoke-Expression (Get-FileSystemCmd . -dirOnly) | Invoke-Fzf | ForEach-Object { $result = $_ } 971 | } 972 | else { 973 | Invoke-Expression ($env:FZF_ALT_C_COMMAND) | Invoke-Fzf | ForEach-Object { $result = $_ } 974 | } 975 | } 976 | catch { 977 | # catch custom exception 978 | } 979 | finally { 980 | if ($script:OverrideFzfDefaults) { 981 | $script:OverrideFzfDefaults.Restore() 982 | $script:OverrideFzfDefaults = $null 983 | } 984 | } 985 | if (-not [string]::IsNullOrEmpty($result)) { 986 | & $script:AltCCommand -Location $result 987 | [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() 988 | } 989 | else { 990 | InvokePromptHack 991 | } 992 | } 993 | 994 | function SetPsReadlineShortcut($Chord, [switch]$Override, $BriefDesc, $Desc, [scriptblock]$scriptBlock) { 995 | if ([string]::IsNullOrEmpty($Chord)) { 996 | return $false 997 | } 998 | if ((Get-PSReadlineKeyHandler -Bound | Where-Object { $_.Key.ToLower() -eq $Chord }) -and -not $Override) { 999 | return $false 1000 | } 1001 | else { 1002 | Set-PSReadlineKeyHandler -Key $Chord -Description $Desc -BriefDescription $BriefDesc -ScriptBlock $scriptBlock 1003 | if ($(Get-PSReadLineOption).EditMode -eq [Microsoft.PowerShell.EditMode]::Vi) { 1004 | Set-PSReadlineKeyHandler -Key $Chord -ViMode Command -Description $Desc -BriefDescription $BriefDesc -ScriptBlock $scriptBlock 1005 | } 1006 | return $true 1007 | } 1008 | } 1009 | 1010 | 1011 | function FindFzf() { 1012 | if ($script:IsWindows) { 1013 | $AppNames = @('fzf-*-windows_*.exe', 'fzf.exe') 1014 | } 1015 | else { 1016 | if ($IsMacOS) { 1017 | $AppNames = @('fzf-*-darwin_*', 'fzf') 1018 | } 1019 | elseif ($IsLinux) { 1020 | $AppNames = @('fzf-*-linux_*', 'fzf') 1021 | } 1022 | else { 1023 | throw 'Unknown OS' 1024 | } 1025 | } 1026 | 1027 | # find it in our path: 1028 | $script:FzfLocation = $null 1029 | $AppNames | ForEach-Object { 1030 | if ($null -eq $script:FzfLocation) { 1031 | $result = Get-Command $_ -ErrorAction Ignore 1032 | $result | ForEach-Object { 1033 | $script:FzfLocation = Resolve-Path $_.Source 1034 | } 1035 | } 1036 | } 1037 | 1038 | if ($null -eq $script:FzfLocation) { 1039 | throw 'Failed to find fzf binary in PATH. You can download a binary from this page: https://github.com/junegunn/fzf/releases' 1040 | } 1041 | } 1042 | 1043 | $PsReadlineShortcuts = @{ 1044 | PSReadlineChordProvider = [PSCustomObject]@{ 1045 | 'Chord' = "$PSReadlineChordProvider" 1046 | 'BriefDesc' = 'Fzf Provider Select' 1047 | 'Desc' = 'Run fzf for current provider based on current token' 1048 | 'ScriptBlock' = { Invoke-FzfPsReadlineHandlerProvider } 1049 | }; 1050 | PSReadlineChordReverseHistory = [PsCustomObject]@{ 1051 | 'Chord' = "$PSReadlineChordReverseHistory" 1052 | 'BriefDesc' = 'Fzf Reverse History Select' 1053 | 'Desc' = 'Run fzf to search through PSReadline history' 1054 | 'ScriptBlock' = { Invoke-FzfPsReadlineHandlerHistory } 1055 | }; 1056 | PSReadlineChordSetLocation = @{ 1057 | 'Chord' = "$PSReadlineChordSetLocation" 1058 | 'BriefDesc' = 'Fzf Set Location' 1059 | 'Desc' = 'Run fzf to select directory to set current location' 1060 | 'ScriptBlock' = { Invoke-FzfPsReadlineHandlerSetLocation } 1061 | }; 1062 | PSReadlineChordReverseHistoryArgs = @{ 1063 | 'Chord' = "$PSReadlineChordReverseHistoryArgs" 1064 | 'BriefDesc' = 'Fzf Reverse History Arg Select' 1065 | 'Desc' = 'Run fzf to search through command line arguments in PSReadline history' 1066 | 'ScriptBlock' = { Invoke-FzfPsReadlineHandlerHistoryArgs } 1067 | }; 1068 | PSReadlineChordTabCompletion = [PSCustomObject]@{ 1069 | 'Chord' = "Tab" 1070 | 'BriefDesc' = 'Fzf Tab Completion' 1071 | 'Desc' = 'Invoke Fzf for tab completion' 1072 | 'ScriptBlock' = { Invoke-TabCompletion } 1073 | }; 1074 | } 1075 | if (Get-Module -ListAvailable -Name PSReadline) { 1076 | $PsReadlineShortcuts.GetEnumerator() | ForEach-Object { 1077 | $info = $_.Value 1078 | $result = SetPsReadlineShortcut $info.Chord -Override:$PSBoundParameters.ContainsKey($_.Key) $info.BriefDesc $info.Desc $info.ScriptBlock 1079 | # store that the chord is not activated: 1080 | if (-not $result) { 1081 | $info.Chord = $null 1082 | } 1083 | } 1084 | } 1085 | else { 1086 | Write-Warning "PSReadline module not found - keyboard handlers not installed" 1087 | } 1088 | 1089 | FindFzf 1090 | 1091 | try { 1092 | $fzfVersion = $(& $script:FzfLocation --version).Replace(' (devel)', '').Split('.') 1093 | $script:UseHeightOption = $fzfVersion.length -ge 2 -and ` 1094 | ([int]$fzfVersion[0] -gt 0 -or ` 1095 | [int]$fzfVersion[1] -ge 21) -and ` 1096 | $script:RunningInWindowsTerminal 1097 | $script:UseWalker = $fzfVersion.length -ge 2 -and ` 1098 | ([int]$fzfVersion[0] -gt 0 -or ` 1099 | [int]$fzfVersion[1] -ge 48) 1100 | } 1101 | catch { 1102 | # continue 1103 | } 1104 | 1105 | # check if we're running on Windows PowerShell. This method is faster than Get-Command: 1106 | if ($(get-host).Version.Major -le 5) { 1107 | $script:PowershellCmd = 'powershell' 1108 | } 1109 | else { 1110 | $script:PowershellCmd = 'pwsh' 1111 | } 1112 | -------------------------------------------------------------------------------- /PSFzf.Functions.ps1: -------------------------------------------------------------------------------- 1 | #.ExternalHelp PSFzf.psm1-help.xml 2 | 3 | $addedAliases = @() 4 | 5 | function script:SetPsFzfAlias { 6 | param($Name, $Function) 7 | 8 | New-Alias -Name $Name -Scope Global -Value $Function -ErrorAction Ignore 9 | $addedAliases += $Name 10 | } 11 | function script:SetPsFzfAliasCheck { 12 | param($Name, $Function) 13 | 14 | # prevent Get-Command from loading PSFzf 15 | $script:PSModuleAutoLoadingPreferencePrev = $PSModuleAutoLoadingPreference 16 | $PSModuleAutoLoadingPreference = 'None' 17 | 18 | if (-not (Get-Command -Name $Name -ErrorAction Ignore)) { 19 | SetPsFzfAlias $Name $Function 20 | } 21 | 22 | # restore module auto loading 23 | $PSModuleAutoLoadingPreference = $script:PSModuleAutoLoadingPreferencePrev 24 | } 25 | 26 | function script:RemovePsFzfAliases { 27 | $addedAliases | ForEach-Object { 28 | Remove-Item -Path Alias:$_ 29 | } 30 | } 31 | 32 | function Get-EditorLaunch() { 33 | param($FileList, $LineNum = 0) 34 | # HACK to check to see if we're running under Visual Studio Code. 35 | # If so, reuse Visual Studio Code currently open windows: 36 | $editorOptions = '' 37 | if (-not [string]::IsNullOrEmpty($env:PSFZF_EDITOR_OPTIONS)) { 38 | $editorOptions += ' ' + $env:PSFZF_EDITOR_OPTIONS 39 | } 40 | if ($null -ne $env:VSCODE_PID) { 41 | $editor = 'code' 42 | $editorOptions += ' --reuse-window' 43 | } 44 | else { 45 | $editor = if ($ENV:VISUAL) { $ENV:VISUAL }elseif ($ENV:EDITOR) { $ENV:EDITOR } 46 | if ($null -eq $editor) { 47 | if (!$IsWindows) { 48 | $editor = 'vim' 49 | } 50 | else { 51 | $editor = 'code' 52 | } 53 | } 54 | } 55 | 56 | if ($editor -eq 'code' -or $editor -eq 'code-insiders' -or $editor -eq 'codium') { 57 | if ($FileList -is [array] -and $FileList.length -gt 1) { 58 | for ($i = 0; $i -lt $FileList.Count; $i++) { 59 | $FileList[$i] = '"{0}"' -f $(Resolve-Path $FileList[$i].Trim('"')) 60 | } 61 | "$editor$editorOptions {0}" -f ($FileList -join ' ') 62 | } 63 | else { 64 | "$editor$editorOptions --goto ""{0}:{1}""" -f $(Resolve-Path $FileList.Trim('"')), $LineNum 65 | } 66 | } 67 | elseif ($editor -match '[gn]?vi[m]?') { 68 | if ($FileList -is [array] -and $FileList.length -gt 1) { 69 | for ($i = 0; $i -lt $FileList.Count; $i++) { 70 | $FileList[$i] = '"{0}"' -f $(Resolve-Path $FileList[$i].Trim('"')) 71 | } 72 | "$editor$editorOptions {0}" -f ($FileList -join ' ') 73 | } 74 | else { 75 | "$editor$editorOptions ""{0}"" +{1}" -f $(Resolve-Path $FileList.Trim('"')), $LineNum 76 | } 77 | } 78 | elseif ($editor -eq 'nano') { 79 | if ($FileList -is [array] -and $FileList.length -gt 1) { 80 | for ($i = 0; $i -lt $FileList.Count; $i++) { 81 | $FileList[$i] = '"{0}"' -f $(Resolve-Path $FileList[$i].Trim('"')) 82 | } 83 | "$editor$editorOptions {0}" -f ($FileList -join ' ') 84 | } 85 | else { 86 | "$editor$editorOptions +{1} ""{0}""" -f $(Resolve-Path $FileList.Trim('"')), $LineNum 87 | } 88 | } 89 | else { 90 | # select the first file as we don't know if the editor supports opening multiple files from the cmd line 91 | if ($FileList -is [array] -and $FileList.length -gt 1) { 92 | "$editor$editorOptions ""{0}""" -f $(Resolve-Path $FileList[0].Trim('"')) 93 | } 94 | else { 95 | "$editor$editorOptions ""{0}""" -f $(Resolve-Path $FileList.Trim('"')) 96 | } 97 | } 98 | } 99 | function Invoke-FuzzyEdit() { 100 | param($Directory = ".", [switch]$Wait) 101 | 102 | $files = @() 103 | try { 104 | if ( Test-Path $Directory) { 105 | if ( (Get-Item $Directory).PsIsContainer ) { 106 | $prevDir = $PWD.ProviderPath 107 | cd $Directory 108 | Invoke-Expression (Get-FileSystemCmd .) | Invoke-Fzf -Multi | ForEach-Object { $files += "$_" } 109 | } 110 | else { 111 | $files += $Directory 112 | $Directory = Split-Path -Parent $Directory 113 | } 114 | } 115 | } 116 | catch { 117 | } 118 | finally { 119 | if ($prevDir) { 120 | cd $prevDir 121 | } 122 | } 123 | 124 | 125 | 126 | if ($files.Count -gt 0) { 127 | try { 128 | if ($Directory) { 129 | $prevDir = $PWD.Path 130 | cd $Directory 131 | } 132 | # Not sure if being passed relative or absolute path 133 | $cmd = Get-EditorLaunch -FileList $files 134 | Write-Host "Executing '$cmd'..." 135 | ($Editor, $Arguments) = $cmd.Split(' ') 136 | Start-Process $Editor -ArgumentList $Arguments -Wait:$Wait -NoNewWindow 137 | } 138 | catch { 139 | } 140 | finally { 141 | if ($prevDir) { 142 | cd $prevDir 143 | } 144 | } 145 | } 146 | } 147 | 148 | 149 | #.ExternalHelp PSFzf.psm1-help.xml 150 | function Invoke-FuzzyFasd() { 151 | $result = $null 152 | try { 153 | if (Get-Command Get-Frecents -ErrorAction Ignore) { 154 | Get-Frecents | ForEach-Object { $_.FullPath } | Invoke-Fzf -ReverseInput -NoSort | ForEach-Object { $result = $_ } 155 | } 156 | elseif (Get-Command fasd -ErrorAction Ignore) { 157 | fasd -l | Invoke-Fzf -ReverseInput -NoSort | ForEach-Object { $result = $_ } 158 | } 159 | } 160 | catch { 161 | 162 | } 163 | if ($null -ne $result) { 164 | # use cd in case it's aliased to something else: 165 | cd $result 166 | } 167 | } 168 | 169 | 170 | #.ExternalHelp PSFzf.psm1-help.xml 171 | function Invoke-FuzzyHistory() { 172 | $result = Get-PickedHistory -UsePSReadLineHistory:$($null -ne $(Get-Command Get-PSReadLineOption -ErrorAction Ignore)) 173 | if ($null -ne $result) { 174 | Write-Output "Invoking '$result'`n" 175 | Invoke-Expression "$result" -Verbose 176 | } 177 | } 178 | 179 | 180 | # needs to match helpers/GetProcessesList.ps1 181 | function GetProcessesList() { 182 | Get-Process | ` 183 | Where-Object { ![string]::IsNullOrEmpty($_.ProcessName) } | ` 184 | ForEach-Object { 185 | $pmSize = $_.PM / 1MB 186 | $cpu = $_.CPU 187 | # make sure we display a value so we can correctly parse selections: 188 | if ($null -eq $cpu) { 189 | $cpu = 0.0 190 | } 191 | "{0,-8:n2} {1,-8:n2} {2,-8} {3}" -f $pmSize, $cpu, $_.Id, $_.ProcessName } 192 | } 193 | 194 | function GetProcessSelection() { 195 | param( 196 | [scriptblock] 197 | $ResultAction 198 | ) 199 | 200 | $previewScript = $(Join-Path $PsScriptRoot 'helpers/GetProcessesList.ps1') 201 | $cmd = $($script:PowershellCmd + " -NoProfile -NonInteractive -File \""$previewScript\""") 202 | 203 | $header = "`n" + ` 204 | "`nCTRL+R-Reload`tCTRL+A-Select All`tCTRL+D-Deselect All`tCTRL+T-Toggle All`n`n" + ` 205 | $("{0,-8} {1,-8} {2,-8} PROCESS NAME" -f "PM(M)", "CPU", "ID") + "`n" + ` 206 | "{0,-8} {1,-8} {2,-8} {3,-12}" -f "-----", "---", "--", "------------" 207 | 208 | $arguments = @{ 209 | Bind = @("ctrl-r:reload($cmd)", "ctrl-a:select-all", "ctrl-d:deselect-all", "ctrl-t:toggle-all") 210 | Header = $header 211 | Multi = $true 212 | Preview = "echo {}" 213 | PreviewWindow = """down,3,wrap""" 214 | Layout = 'reverse' 215 | Height = '80%' 216 | } 217 | 218 | $result = GetProcessesList | ` 219 | Invoke-Fzf @arguments 220 | $result | ForEach-Object { 221 | &$ResultAction $_ 222 | } 223 | } 224 | 225 | #.ExternalHelp PSFzf.psm1-help.xml 226 | function Invoke-FuzzyKillProcess() { 227 | GetProcessSelection -ResultAction { 228 | param($result) 229 | $resultSplit = $result.split(' ', [System.StringSplitOptions]::RemoveEmptyEntries) 230 | $processIdIdx = 2 231 | $id = $resultSplit[$processIdIdx] 232 | Stop-Process -Id $id -Verbose 233 | } 234 | } 235 | 236 | #.ExternalHelp PSFzf.psm1-help.xml 237 | function Invoke-FuzzySetLocation() { 238 | param($Directory = $null) 239 | 240 | if ($null -eq $Directory) { $Directory = $PWD.ProviderPath } 241 | $result = $null 242 | try { 243 | Get-ChildItem $Directory -Recurse -ErrorAction Ignore | Where-Object { $_.PSIsContainer } | Invoke-Fzf | ForEach-Object { $result = $_ } 244 | } 245 | catch { 246 | 247 | } 248 | 249 | if ($null -ne $result) { 250 | Set-Location $result 251 | } 252 | } 253 | 254 | if ((-not $IsLinux) -and (-not $IsMacOS)) { 255 | #.ExternalHelp PSFzf.psm1-help.xml 256 | function Set-LocationFuzzyEverything() { 257 | param($Directory = $null) 258 | if ($null -eq $Directory) { 259 | $Directory = $PWD.ProviderPath 260 | $Global = $False 261 | } 262 | else { 263 | $Global = $True 264 | } 265 | $result = $null 266 | try { 267 | Search-Everything -Global:$Global -PathInclude $Directory -FolderInclude @('') | Invoke-Fzf | ForEach-Object { $result = $_ } 268 | } 269 | catch { 270 | 271 | } 272 | if ($null -ne $result) { 273 | # use cd in case it's aliased to something else: 274 | cd $result 275 | } 276 | } 277 | } 278 | 279 | #.ExternalHelp PSFzf.psm1-help.xml 280 | function Invoke-FuzzyZLocation() { 281 | $result = $null 282 | try { 283 | (Get-ZLocation).GetEnumerator() | Sort-Object { $_.Value } -Descending | ForEach-Object { $_.Key } | Invoke-Fzf -NoSort | ForEach-Object { $result = $_ } 284 | } 285 | catch { 286 | 287 | } 288 | if ($null -ne $result) { 289 | # use cd in case it's aliased to something else: 290 | cd $result 291 | } 292 | } 293 | 294 | 295 | #.ExternalHelp PSFzf.psm1-help.xml 296 | function Invoke-FuzzyScoop() { 297 | param( 298 | [string]$subcommand = "install", 299 | [string]$subcommandflags = "" 300 | ) 301 | 302 | $result = $null 303 | $scoopexists = Get-Command scoop -ErrorAction Ignore 304 | if ($scoopexists) { 305 | $apps = New-Object System.Collections.ArrayList 306 | Get-ChildItem "$(Split-Path $scoopexists.Path)\..\buckets" | ForEach-Object { 307 | $bucket = $_.Name 308 | Get-ChildItem "$($_.FullName)\bucket" | ForEach-Object { 309 | $apps.Add($bucket + '/' + ($_.Name -replace '.json', '')) > $null 310 | } 311 | } 312 | 313 | $result = $apps | Invoke-Fzf -Header "Scoop Applications" -Multi -Preview "scoop info {}" -PreviewWindow wrap 314 | } 315 | 316 | if ($null -ne $result) { 317 | Invoke-Expression "scoop $subcommand $($result -join ' ') $subcommandflags" 318 | } 319 | } 320 | 321 | 322 | #.ExternalHelp PSFzf.psm1-help.xml 323 | function Invoke-FuzzyGitStatus() { 324 | Invoke-PsFzfGitFiles 325 | } 326 | 327 | function Invoke-PsFzfRipgrep() { 328 | # this function is adapted from https://github.com/junegunn/fzf/blob/master/ADVANCED.md#switching-between-ripgrep-mode-and-fzf-mode 329 | param([Parameter(Mandatory)]$SearchString, [switch]$NoEditor) 330 | 331 | $RG_PREFIX = "rg --column --line-number --no-heading --color=always --smart-case " 332 | $INITIAL_QUERY = $SearchString 333 | 334 | $script:OverrideFzfDefaultCommand = [FzfDefaultCmd]::new('') 335 | try { 336 | if ($script:IsWindows) { 337 | $sleepCmd = '' 338 | $trueCmd = 'cd .' 339 | $env:FZF_DEFAULT_COMMAND = "$RG_PREFIX ""$INITIAL_QUERY""" 340 | } 341 | else { 342 | $sleepCmd = 'sleep 0.1;' 343 | $trueCmd = 'true' 344 | $env:FZF_DEFAULT_COMMAND = '{0} $(printf %q "{1}")' -f $RG_PREFIX, $INITIAL_QUERY 345 | } 346 | $fzfArguments = @{ 347 | color = "hl:-1:underline,hl+:-1:underline:reverse" 348 | query = $INITIAL_QUERY 349 | prompt = 'ripgrep> ' 350 | delimiter = ':' 351 | header = '? CTRL-R (Ripgrep mode) ? CTRL -F (fzf mode) ?' 352 | preview = 'bat --no-config --color=always {1} --highlight-line {2}' 353 | 'preview-window' = 'up,60%,border-bottom,+{2}+3/3,~3' 354 | } 355 | 356 | $fzfArgs = ($fzfArguments.GetEnumerator() | foreach-object { "--{0}=""{1}""" -f $_.Key, $_.Value }) -join ' ' 357 | 358 | $Bind = @( 359 | 'ctrl-r:unbind(change,ctrl-r)+change-prompt(ripgrep> )' + "+disable-search+reload($RG_PREFIX {q} || $trueCmd)+rebind(change,ctrl-f)" 360 | ) 361 | $Bind += 'ctrl-f:unbind(change,ctrl-f)+change-prompt(fzf> )+enable-search+clear-query+rebind(ctrl-r)' 362 | $Bind += "change:reload:$sleepCmd $RG_PREFIX {q} || $trueCmd" 363 | $fzfArgs += ' --ansi --disabled ' + ($Bind | foreach-object { "--bind=""{0}""" -f $_ }) -join ' ' 364 | 365 | Invoke-Expression -Command $('{0} {1}' -f $script:FzfLocation, $fzfArgs) | ForEach-Object { $results += $_ } 366 | 367 | # we need this here to prevent the editor launch from inherting FZF_DEFAULT_COMMAND from being overwritten (see #267): 368 | if ($script:OverrideFzfDefaultCommand) { 369 | $script:OverrideFzfDefaultCommand.Restore() 370 | $script:OverrideFzfDefaultCommand = $null 371 | } 372 | 373 | if (-not [string]::IsNullOrEmpty($results)) { 374 | $split = $results.Split(':') 375 | $fileList = $split[0] 376 | $lineNum = $split[1] 377 | if ($NoEditor) { 378 | Resolve-Path $fileList 379 | } 380 | else { 381 | $cmd = Get-EditorLaunch -FileList $fileList -LineNum $lineNum 382 | Write-Host "Executing '$cmd'..." 383 | Invoke-Expression -Command $cmd 384 | } 385 | } 386 | } 387 | catch { 388 | Write-Error "Error occurred: $_" 389 | } 390 | finally { 391 | if ($script:OverrideFzfDefaultCommand) { 392 | $script:OverrideFzfDefaultCommand.Restore() 393 | $script:OverrideFzfDefaultCommand = $null 394 | } 395 | } 396 | } 397 | 398 | function Enable-PsFzfAliases() { 399 | # set aliases: 400 | if (-not $DisableAliases) { 401 | SetPsFzfAliasCheck "fe" Invoke-FuzzyEdit 402 | SetPsFzfAliasCheck "fh" Invoke-FuzzyHistory 403 | SetPsFzfAliasCheck "ff" Invoke-FuzzyFasd 404 | SetPsFzfAliasCheck "fkill" Invoke-FuzzyKillProcess 405 | SetPsFzfAliasCheck "fd" Invoke-FuzzySetLocation 406 | if (${function:Set-LocationFuzzyEverything}) { 407 | SetPsFzfAliasCheck "cde" Set-LocationFuzzyEverything 408 | } 409 | SetPsFzfAliasCheck "fz" Invoke-FuzzyZLocation 410 | SetPsFzfAliasCheck "fs" Invoke-FuzzyScoop 411 | SetPsFzfAliasCheck "fgs" Invoke-FuzzyGitStatus 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /PSFzf.Git.ps1: -------------------------------------------------------------------------------- 1 | 2 | $script:GitKeyHandlers = @() 3 | 4 | $script:foundGit = $false 5 | $script:bashPath = $null 6 | $script:grepPath = $null 7 | 8 | if ($PSVersionTable.PSEdition -eq 'Core') { 9 | $script:pwshExec = "pwsh" 10 | } 11 | else { 12 | $script:pwshExec = "powershell" 13 | } 14 | 15 | $script:IsWindowsCheck = ($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows 16 | 17 | if ($RunningInWindowsTerminal -or -not $script:IsWindowsCheck) { 18 | $script:filesString = '📁 Files' 19 | $script:hashesString = '🍡 Hashes' 20 | $script:allBranchesString = '🌳 All branches' 21 | $script:branchesString = '🌲 Branches' 22 | $script:tagsString = '📛 Tags' 23 | $script:stashesString = '🥡 Stashes' 24 | } 25 | else { 26 | $script:filesString = 'Files' 27 | $script:hashesString = 'Hashes' 28 | $script:allBranchesString = 'All branches' 29 | $script:branchesString = 'Branches' 30 | $script:tagsString = 'Tags' 31 | $script:stashesString = 'Stashes' 32 | } 33 | 34 | function Get-GitFzfArguments() { 35 | # take from https://github.com/junegunn/fzf-git.sh/blob/f72ebd823152fa1e9b000b96b71dd28717bc0293/fzf-git.sh#L89 36 | return @{ 37 | Ansi = $true 38 | Layout = "reverse" 39 | Multi = $true 40 | Height = '50%' 41 | MinHeight = 20 42 | Border = $true 43 | Color = 'header:italic:underline' 44 | PreviewWindow = 'right,50%,border-left' 45 | Bind = @('ctrl-/:change-preview-window(down,50%,border-top|hidden|)') 46 | } 47 | } 48 | 49 | function SetupGitPaths() { 50 | if (-not $script:foundGit) { 51 | if ($IsLinux -or $IsMacOS) { 52 | # TODO: not tested on Mac 53 | $script:foundGit = $null -ne $(Get-Command git -ErrorAction Ignore) 54 | $script:bashPath = 'bash' 55 | $script:grepPath = 'grep' 56 | } 57 | else { 58 | $gitInfo = Get-Command git.exe -ErrorAction Ignore 59 | $script:foundGit = $null -ne $gitInfo 60 | if ($script:foundGit) { 61 | # Detect if scoop is installed 62 | $script:scoopInfo = Get-Command scoop -ErrorAction Ignore 63 | if ($null -ne $script:scoopInfo) { 64 | # Detect if git is installed using scoop (using shims) 65 | if ($gitInfo.Source -match 'scoop[\\/]shims') { 66 | # Get the proper git position relative to scoop shims" position 67 | $gitInfo = Get-Command "$($gitInfo.Source)\..\..\apps\git\current\bin\git.exe" 68 | } 69 | } 70 | $gitPathLong = Split-Path (Split-Path $gitInfo.Source -Parent) -Parent 71 | # hack to get short path: 72 | $a = New-Object -ComObject Scripting.FileSystemObject 73 | $f = $a.GetFolder($gitPathLong) 74 | $script:bashPath = Join-Path $f.ShortPath "bin\bash.exe" 75 | $script:bashPath = Resolve-Path $script:bashPath 76 | $script:grepPath = Join-Path ${gitPathLong} "usr\bin\grep.exe" 77 | } 78 | } 79 | } 80 | return $script:foundGit 81 | } 82 | 83 | function SetGitKeyBindings($enable) { 84 | if ($enable) { 85 | if (-not $(SetupGitPaths)) { 86 | Write-Error "Failed to register git key bindings - git executable not found" 87 | return 88 | } 89 | 90 | if (Get-Command Set-PSReadLineKeyHandler -ErrorAction Ignore) { 91 | @( 92 | @{ 93 | Chord = 'ctrl+g,ctrl+b' 94 | BriefDescription = 'Fzf Git Branches Select' 95 | Description = 'Select Git branches via fzf' 96 | ScriptBlock = { Update-CmdLine $(Invoke-PsFzfGitBranches) } 97 | } 98 | @{ 99 | Chord = 'ctrl+g,ctrl+f' 100 | BriefDescription = 'Fzf Git Files Select' 101 | Description = 'Select Git files via fzf' 102 | ScriptBlock = { Update-CmdLine $(Invoke-PsFzfGitFiles) } 103 | } 104 | @{ 105 | Chord = 'ctrl+g,ctrl+h' 106 | BriefDescription = 'Fzf Git Hashes Select' 107 | Description = 'Select Git hashes via fzf' 108 | ScriptBlock = { Update-CmdLine $(Invoke-PsFzfGitHashes) } 109 | } 110 | @{ 111 | Chord = 'ctrl+g,ctrl+p' 112 | BriefDescription = 'Fzf Git Pull Requests Select' 113 | Description = 'Select Git pull requests via fzf' 114 | ScriptBlock = { Update-CmdLine $(Invoke-PsFzfGitPulLRequests) } 115 | } 116 | @{ 117 | Chord = 'ctrl+g,ctrl+s' 118 | BriefDescription = 'Fzf Git Stashes Select' 119 | Description = 'Select Git stashes via fzf' 120 | ScriptBlock = { Update-CmdLine $(Invoke-PsFzfGitStashes) } 121 | } 122 | @{ 123 | Chord = 'ctrl+g,ctrl+t' 124 | BriefDescription = 'Fzf Git Tags Select' 125 | Description = 'Select Git tags via fzf' 126 | ScriptBlock = { Update-CmdLine $(Invoke-PsFzfGitTags) } 127 | } 128 | ) | ForEach-Object { 129 | $script:GitKeyHandlers += $_.Chord 130 | Set-PSReadLineKeyHandler @_ 131 | } 132 | } 133 | else { 134 | Write-Error "Failed to register git key bindings - PSReadLine module not loaded" 135 | return 136 | } 137 | } 138 | } 139 | 140 | function RemoveGitKeyBindings() { 141 | $script:GitKeyHandlers | ForEach-Object { 142 | Remove-PSReadLineKeyHandler -Chord $_ 143 | } 144 | } 145 | 146 | function IsInGitRepo() { 147 | git rev-parse HEAD 2>&1 | Out-Null 148 | return $? 149 | } 150 | 151 | function Get-ColorAlways($setting = ' --color=always') { 152 | if ($RunningInWindowsTerminal -or -not $IsWindowsCheck) { 153 | return $setting 154 | } 155 | else { 156 | return '' 157 | } 158 | } 159 | 160 | function Get-HeaderStrings() { 161 | $header = "CTRL-A (Select all) / CTRL-D (Deselect all) / CTRL-T (Toggle all)" 162 | $keyBinds = 'ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all' 163 | return $Header, $keyBinds 164 | } 165 | 166 | function Update-CmdLine($result) { 167 | InvokePromptHack 168 | if ($result.Length -gt 0) { 169 | $result = $result -join " " 170 | [Microsoft.PowerShell.PSConsoleReadLine]::Insert($result) 171 | } 172 | } 173 | function Invoke-PsFzfGitFiles() { 174 | if (-not (IsInGitRepo)) { 175 | return 176 | } 177 | 178 | if (-not $(SetupGitPaths)) { 179 | Write-Error "git executable could not be found" 180 | return 181 | } 182 | 183 | $previewCmd = "${script:bashPath} \""" + $(Join-Path $PsScriptRoot 'helpers/PsFzfGitFiles-Preview.sh') + "\"" {-1}" + $(Get-ColorAlways) + " \""$($pwd.ProviderPath)\""" 184 | $result = @() 185 | 186 | $headerStrings = Get-HeaderStrings 187 | $gitCmdsHeader = "`nALT-S (Git add) / ALT-R (Git reset)" 188 | $headerStr = $headerStrings[0] + $gitCmdsHeader + "`n`n" 189 | $statusCmd = "git $(Get-ColorAlways '-c color.status=always') status --short" 190 | 191 | $reloadBindCmd = "reload($statusCmd)" 192 | $stageScriptPath = Join-Path $PsScriptRoot 'helpers/PsFzfGitFiles-GitAdd.sh' 193 | $gitStageBind = "alt-s:execute-silent(" + """${script:bashPath}"" '${stageScriptPath}' {+2..})+down+${reloadBindCmd}" 194 | $resetScriptPath = Join-Path $PsScriptRoot 'helpers/PsFzfGitFiles-GitReset.sh' 195 | $gitResetBind = "alt-r:execute-silent(" + """${script:bashPath}"" '${resetScriptPath}' {+2..})+down+${reloadBindCmd}" 196 | 197 | $fzfArguments = Get-GitFzfArguments 198 | $fzfArguments['Bind'] += $headerStrings[1], $gitStageBind, $gitResetBind 199 | Invoke-Expression "& $statusCmd" | ` 200 | Invoke-Fzf @fzfArguments ` 201 | -BorderLabel "$script:filesString" ` 202 | -Preview "$previewCmd" -Header $headerStr | ` 203 | foreach-object { 204 | $result += $_.Substring('?? '.Length) 205 | } 206 | 207 | $result 208 | } 209 | function Invoke-PsFzfGitHashes() { 210 | if (-not (IsInGitRepo)) { 211 | return 212 | } 213 | 214 | if (-not $(SetupGitPaths)) { 215 | Write-Error "git executable could not be found" 216 | return 217 | } 218 | 219 | $previewCmd = "${script:bashPath} \""" + $(Join-Path $PsScriptRoot 'helpers/PsFzfGitHashes-Preview.sh') + "\"" {}" + $(Get-ColorAlways) + " \""$pwd\""" 220 | $result = @() 221 | 222 | $fzfArguments = Get-GitFzfArguments 223 | & git log --date=short --format="%C(green)%C(bold)%cd %C(auto)%h%d %s (%an)" $(Get-ColorAlways).Trim() --graph | ` 224 | Invoke-Fzf @fzfArguments -NoSort ` 225 | -BorderLabel "$script:hashesString" ` 226 | -Preview "$previewCmd" | ForEach-Object { 227 | if ($_ -match '\d\d-\d\d-\d\d\s+([a-f0-9]+)\s+') { 228 | $result += $Matches.1 229 | } 230 | } 231 | 232 | $result 233 | } 234 | 235 | function Invoke-PsFzfGitBranches() { 236 | if (-not (IsInGitRepo)) { 237 | return 238 | } 239 | 240 | if (-not $(SetupGitPaths)) { 241 | Write-Error "git executable could not be found" 242 | return 243 | } 244 | 245 | $fzfArguments = Get-GitFzfArguments 246 | $fzfArguments['PreviewWindow'] = 'down,border-top,40%' 247 | $gitBranchesHelperPath = Join-Path $PsScriptRoot 'helpers/PsFzfGitBranches.sh' 248 | $ShortcutBranchesAll = "ctrl-a:change-prompt" + "($script:allBranchesString> )+reload(" + """${script:bashPath}"" '${gitBranchesHelperPath}' all-branches)" 249 | $fzfArguments['Bind'] += 'ctrl-/:change-preview-window(down,70%|hidden|)', $ShortcutBranchesAll 250 | 251 | $previewCmd = "${script:bashPath} \""" + $(Join-Path $PsScriptRoot 'helpers/PsFzfGitBranches-Preview.sh') + "\"" {}" 252 | $result = @() 253 | # use pwsh to prevent bash from trying to write to host output: 254 | $branches = & $script:pwshExec -NoProfile -NonInteractive -Command "& ${script:bashPath} '$gitBranchesHelperPath' branches" 255 | $branches | 256 | Invoke-Fzf @fzfArguments -Preview "$previewCmd" -BorderLabel "$script:branchesString" -HeaderLines 2 -Tiebreak begin -ReverseInput | ` 257 | ForEach-Object { 258 | $result += $($_ -split ' ')[0] 259 | } 260 | 261 | $result 262 | } 263 | 264 | function Invoke-PsFzfGitTags() { 265 | if (-not (IsInGitRepo)) { 266 | return 267 | } 268 | 269 | if (-not $(SetupGitPaths)) { 270 | Write-Error "git executable could not be found" 271 | return 272 | } 273 | 274 | $fzfArguments = Get-GitFzfArguments 275 | $fzfArguments['PreviewWindow'] = 'right,70%' 276 | $previewCmd = "git show --color=always {}" 277 | $result = @() 278 | git tag --sort -version:refname | 279 | Invoke-Fzf @fzfArguments -Preview "$previewCmd" -BorderLabel "$script:tagsString" | ` 280 | ForEach-Object { 281 | $result += $_ 282 | } 283 | 284 | $result 285 | } 286 | 287 | function Invoke-PsFzfGitStashes() { 288 | if (-not (IsInGitRepo)) { 289 | return 290 | } 291 | 292 | if (-not $(SetupGitPaths)) { 293 | Write-Error "git executable could not be found" 294 | return 295 | } 296 | 297 | $fzfArguments = Get-GitFzfArguments 298 | $fzfArguments['Bind'] += 'ctrl-x:execute-silent(git stash drop {1})+reload(git stash list)' 299 | $header = "CTRL-X (drop stash)`n`n" 300 | $previewCmd = 'git show --color=always {1}' 301 | 302 | $result = @() 303 | git stash list --color=always | 304 | Invoke-Fzf @fzfArguments -Header $header -Delimiter ':' -Preview "$previewCmd" -BorderLabel "$script:stashesString" | ` 305 | ForEach-Object { 306 | $result += $_.Split(':')[0] 307 | } 308 | 309 | $result 310 | } 311 | 312 | function Invoke-PsFzfGitPullRequests() { 313 | if (-not (IsInGitRepo)) { 314 | return 315 | } 316 | 317 | if (-not $(SetupGitPaths)) { 318 | Write-Error "git executable could not be found" 319 | return 320 | } 321 | 322 | $filterCurrentUser = $true 323 | $reloadPrList = $false 324 | 325 | # loop due to requesting possibly selecting current user 326 | do { 327 | # find the repo remote URL 328 | $remoteUrl = git config --get remote.origin.url 329 | 330 | # GitHub 331 | if ($remoteUrl -match 'github.com') { 332 | $script:ghCmdInfo = Get-Command gh -ErrorAction Ignore 333 | if ($null -ne $script:ghCmdInfo) { 334 | if ($filterCurrentUser) { 335 | $currentUser = Invoke-Expression "gh api user --jq '.login'" 336 | $listAllPrsCmdJson = Invoke-Expression "gh pr list --json id,author,title,number --author $currentUser" 337 | } 338 | else { 339 | $currentUser = $null 340 | $listAllPrsCmdJson = Invoke-Expression "gh pr list --json id,author,title,number" 341 | } 342 | 343 | $objs = $listAllPrsCmdJson | ConvertFrom-Json | ForEach-Object { 344 | [PSCustomObject]@{ 345 | PR = "$($PSStyle.Foreground.Green)" + $_.number 346 | Title = "$($PSStyle.Foreground.Magenta)" + $_.title 347 | Creator = "$($PSStyle.Foreground.Yellow)" + $_.author.login 348 | } 349 | } 350 | } 351 | else { 352 | Write-Error "Repo is a GitHub repo and gh command not found" 353 | return 354 | } 355 | $webCmd = 'gh pr view {1} --web' 356 | $previewCmd = 'gh pr view {1} && gh pr diff {1}' 357 | $checkoutCmd = 'gh pr checkout {0}' 358 | } 359 | # Azure DevOps 360 | elseif ($remoteUrl -match 'dev.azure.com|visualstudio.com') { 361 | $script:azCmdInfo = Get-Command az -ErrorAction Ignore 362 | if ($null -ne $script:azCmdInfo) { 363 | if ($filterCurrentUser) { 364 | $currentUser = Invoke-Expression "az account show --query user.name --output tsv" 365 | $listAllPrsCmdJson = Invoke-Expression $('az repos pr list --status "active" --query "[].{title: title, number: pullRequestId, creator: createdBy.uniqueName}"' + "--creator $currentUser") 366 | } 367 | else { 368 | $currentUser = $null 369 | $listAllPrsCmdJson = Invoke-Expression 'az repos pr list --status "active" --query "[].{title: title, number: pullRequestId, creator: createdBy.uniqueName}"' 370 | } 371 | 372 | $objs = $listAllPrsCmdJson | ConvertFrom-Json | ForEach-Object { 373 | [PSCustomObject]@{ 374 | PR = "$($PSStyle.Foreground.Green)" + $_.number 375 | Title = "$($PSStyle.Foreground.Magenta)" + $_.title 376 | Creator = "$($PSStyle.Foreground.Yellow)" + $_.creator 377 | } 378 | } 379 | } 380 | else { 381 | Write-Error "Repo is an Azure DevOps repo and az command not found" 382 | return 383 | } 384 | $webCmd = 'az repos pr show --id {1} --open --output none' 385 | # currently errors on query. Need to fix instead of output everything 386 | #$previewCmd = 'az repos pr show --id {1} --query "{Created:creationDate, Closed:closedDate, Creator:createdBy.displayName, PR:codeReviewId, Title:title, Repo:repository.name, Reviewers:join('', '',reviewers[].displayName), Source:sourceRefName, Target:targetRefName}" --output yamlc' 387 | $previewCmd = 'az repos pr show --id {1} --output yamlc' 388 | $checkoutCmd = 'az repos pr checkout --id {0}' 389 | } 390 | 391 | $fzfArguments = Get-GitFzfArguments 392 | $fzfArguments['Bind'] += 'ctrl-o:execute-silent(' + $webCmd + ')' 393 | $header = "CTRL-O (open in browser) / CTRL-X (checks) / CTRL+U (toggle user filter) / CTRL+P (checkout PR)`n`n" 394 | 395 | $prevCLICOLOR_FORCE = $env:CLICOLOR_FORCE 396 | if ($PSStyle) { 397 | $prevOutputRendering = $PSStyle.OutputRendering 398 | } 399 | 400 | 401 | $env:CLICOLOR_FORCE = 1 # make gh show keep colors 402 | if ($PSStyle) { 403 | $PSStyle.OutputRendering = 'Ansi' 404 | } 405 | 406 | try { 407 | $borderLabel = "Pull Requests" 408 | if ($currentUser) { 409 | $borderLabel += " by $currentUser" 410 | } 411 | $result = $objs | out-string -Stream | ` 412 | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ` 413 | Invoke-Fzf @fzfArguments -Expect "ctrl-x,ctrl-u,ctrl-p" -Header $header -Preview "$previewCmd" -HeaderLines 2 -BorderLabel $borderLabel 414 | 415 | if ($result -is [array]) { 416 | if ($result.Length -ge 2) { 417 | $prId = $result[1].Split(' ')[0] # get the PR ID 418 | } 419 | else { 420 | $prId = $null 421 | } 422 | $reloadPrList = $result[0] -eq 'ctrl-u' # reload if user filter toggled 423 | } 424 | else { 425 | $reloadPrList = $result -eq 'ctrl-u' # reload if user filter toggled 426 | } 427 | $checks = $null 428 | 429 | # reload with user filter toggled: 430 | if ($reloadPrList) { 431 | $filterCurrentUser = -not $filterCurrentUser 432 | } 433 | # checkout PR: 434 | elseif ($result[0] -eq 'ctrl-p') { 435 | Write-Warning "Checking out PR $prId into $($(Get-Location).Path) ..." 436 | Invoke-Expression ($checkoutCmd -f $prId) 437 | } 438 | # open checks for PR: 439 | elseif ($result[0] -eq 'ctrl-x') { 440 | if ($remoteUrl -match 'github.com') { 441 | $env:CLICOLOR_FORCE = $prevCLICOLOR_FORCE 442 | $checksCmd = "gh pr view $prId --json ""statusCheckRollup""" 443 | $checksJsonTxt = Invoke-Expression $checksCmd 444 | $checksJson = $checksJsonTxt | ConvertFrom-Json 445 | $checks = $checksJson.statusCheckRollup | ForEach-Object { 446 | if ($_.status -eq 'COMPLETED') { 447 | if ($_.conclusion -eq 'SUCCESS') { 448 | $status = "$($PSStyle.Foreground.Green)" + '? Success' 449 | } 450 | else { 451 | $status = "$($PSStyle.Foreground.Red)" + '? Failed' 452 | } 453 | } 454 | else { 455 | $status = "$($PSStyle.Foreground.Yellow)" + '?' 456 | } 457 | [PSCustomObject]@{ 458 | Status = $status 459 | Check = "$($PSStyle.Foreground.Magenta)" + $_.name 460 | Link = $_.detailsUrl 461 | } 462 | } 463 | #$runCheckCmd = $null 464 | $runCheckCmd = 'echo running ' 465 | } 466 | elseif ($remoteUrl -match 'dev.azure.com|visualstudio.com') { 467 | $checksCmd = "az repos pr policy list --id $prId --output json" 468 | $checksJsonTxt = Invoke-Expression $checksCmd 469 | $checksJson = $checksJsonTxt | ConvertFrom-Json 470 | 471 | # only worried about blocking checks, for now: 472 | $checks = $checksJson | Where-Object { $_.configuration.isBlocking } | ForEach-Object { 473 | $context = $_.context 474 | $settings = $_.configuration.settings 475 | $type = $_.configuration.type 476 | $link = $remoteUrl, "pullrequest/$($prId)" -join '/' # default to opening PR in browser 477 | 478 | # find check status: 479 | switch ($_.status) { 480 | 'approved' { 481 | $status = "$($PSStyle.Foreground.Green)" + '? Approved' 482 | } 483 | 'rejected' { 484 | $status = "$($PSStyle.Foreground.Red)" + '? Rejected' 485 | } 486 | 'queued' { 487 | if ($context -and $context.IsExpired) { 488 | $status = "$($PSStyle.Foreground.Red)" + '? Expired' 489 | } 490 | else { 491 | $status = "$($PSStyle.Foreground.BrightBlue)" + '?? Queued' 492 | } 493 | } 494 | 'running' { 495 | $status = "$($PSStyle.Foreground.BrightBlue)" + '? Running' 496 | } 497 | default { 498 | $status = $_.status # unknown status 499 | } 500 | } 501 | 502 | # find check name and build link: 503 | switch ($type.displayName) { 504 | 'Build' { 505 | $check = $settings.displayName 506 | if ([string]::IsNullOrWhiteSpace($check)) { 507 | $check = $context.buildDefinitionName 508 | } 509 | if ($context) { 510 | $buildId = $context.buildId 511 | $link = $remoteUrl.split('/_git/')[0], "_build/results?buildId=$buildId" -join '/' 512 | } 513 | } 514 | 'Status' { 515 | $check = $settings.defaultDisplayName 516 | } 517 | default { 518 | $check = $type.displayName 519 | } 520 | } 521 | [PSCustomObject]@{ 522 | EvaluationId = "$($PSStyle.Foreground.Blue)" + $_.evaluationId 523 | Status = $status 524 | Check = "$($PSStyle.Foreground.Magenta)" + $check 525 | Link = $link 526 | } 527 | } 528 | 529 | $runCheckCmd = "az repos pr policy queue --id $prId --output none --evaluation-id " 530 | } 531 | } 532 | 533 | # 2. Run the checks command, if selected in previous command: 534 | if ($null -ne $checks) { 535 | $fzfArguments = Get-GitFzfArguments 536 | #$fzfArguments['Bind'] += 'ctrl-r:execute(' + $runCheckCmd + ')' 537 | if ($runCheckCmd) { 538 | $fzfArguments['Expect'] = "ctrl-r" 539 | $header = "CTRL-R (run selected checks)`n`n" 540 | } 541 | else { 542 | $header = "`n" 543 | } 544 | $env:CLICOLOR_FORCE = 1 # make gh show keep colors 545 | if ($PSStyle) { 546 | $PSStyle.OutputRendering = 'Ansi' 547 | } 548 | 549 | $result = $checks | out-string -Stream | ` 550 | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ` 551 | Invoke-Fzf @fzfArguments -Header $header -HeaderLines 2 -BorderLabel $('? Checks' + " for PR $prId") 552 | 553 | if ($runCheckCmd -and $result[0] -eq 'ctrl-r') { 554 | $result = $result[1..($result.Length - 1)] 555 | $result | ForEach-Object { 556 | $cmd = $($runCheckCmd + $($_ -split ' ')[0]) 557 | Write-Warning "Running check using command '$cmd'..." 558 | Invoke-Expression $cmd 559 | } 560 | } 561 | } 562 | } 563 | finally { 564 | $env:CLICOLOR_FORCE = $prevCLICOLOR_FORCE 565 | if ($PSStyle) { 566 | $PSStyle.OutputRendering = $prevOutputRendering 567 | } 568 | } 569 | } while ($reloadPrList) 570 | 571 | $prId 572 | } 573 | -------------------------------------------------------------------------------- /PSFzf.TabExpansion.ps1: -------------------------------------------------------------------------------- 1 | # borrowed from https://github.com/dahlbyk/posh-git/blob/f69efd9229029519adb32e37a464b7e1533a372c/src/GitTabExpansion.ps1#L81 2 | filter script:quoteStringWithSpecialChars { 3 | if ($_ -and ($_ -match '\s+|#|@|\$|;|,|''|\{|\}|\(|\)')) { 4 | $str = $_ -replace "'", "''" 5 | "'$str'" 6 | } 7 | else { 8 | $_ 9 | } 10 | } 11 | 12 | # taken from https://github.com/dahlbyk/posh-git/blob/2ad946347e7342199fd4bb1b42738833f68721cd/src/GitUtils.ps1#L407 13 | function script:Get-AliasPattern($cmd) { 14 | $aliases = @($cmd) + @(Get-Alias | Where-Object { $_.Definition -eq $cmd } | Select-Object -Exp Name) 15 | "($($aliases -join '|'))" 16 | } 17 | 18 | function Expand-GitWithFzf($lastBlock) { 19 | $gitResults = Expand-GitCommand $lastBlock 20 | # if no results, invoke filesystem completion: 21 | if ($null -eq $gitResults) { 22 | $results = Invoke-Fzf -Multi | script:quoteStringWithSpecialChars 23 | } 24 | else { 25 | $results = $gitResults | Invoke-Fzf -Multi | script:quoteStringWithSpecialChars 26 | } 27 | 28 | if ($results.Count -gt 1) { 29 | $results -join ' ' 30 | } 31 | else { 32 | if (-not $null -eq $results) { 33 | $results 34 | } 35 | else { 36 | '' # output something to prevent default tab expansion 37 | } 38 | } 39 | 40 | InvokePromptHack 41 | } 42 | 43 | function Expand-FileDirectoryPath($lastWord) { 44 | # find dir and file pattern connected to the trigger: 45 | $lastWord = $lastWord.Substring(0, $lastWord.Length - 2) 46 | if ($lastWord.EndsWith('\')) { 47 | $dir = $lastWord.Substring(0, $lastWord.Length - 1) 48 | $file = $null 49 | } 50 | elseif (-not [string]::IsNullOrWhiteSpace($lastWord)) { 51 | $dir = Split-Path $lastWord -Parent 52 | $file = Split-Path $lastWord -Leaf 53 | } 54 | if (-not [System.IO.Path]::IsPathRooted($dir)) { 55 | $dir = Join-Path $PWD.ProviderPath $dir 56 | } 57 | $prevPath = $Pwd.ProviderPath 58 | try { 59 | if (-not [string]::IsNullOrEmpty($dir)) { 60 | Set-Location $dir 61 | } 62 | if (-not [string]::IsNullOrEmpty($file)) { 63 | Invoke-Fzf -Query $file 64 | } 65 | else { 66 | Invoke-Fzf 67 | } 68 | } 69 | finally { 70 | Set-Location $prevPath 71 | } 72 | 73 | InvokePromptHack 74 | } 75 | 76 | $script:TabExpansionEnabled = $false 77 | function SetTabExpansion($enable) { 78 | if ($enable) { 79 | if (-not $script:TabExpansionEnabled) { 80 | $script:TabExpansionEnabled = $true 81 | 82 | RegisterBuiltinCompleters 83 | 84 | # borrowed from https://github.com/dahlbyk/posh-git/blob/70e44dc0c2cdaf10c0cc8eb9ef5a9ca65ab63dcf/src/GitTabExpansion.ps1#L544C58-L544C58 85 | $cmdNames = "git", "tgit", "gitk" 86 | # Create regex pattern from $cmdNames: ^(git|git\.exe|tgit|tgit\.exe|gitk|gitk\.exe)$ 87 | $cmdNamesPattern = "^($($cmdNames -join '|'))(\.exe)?$" 88 | $cmdNames += Get-Alias | Where-Object { $_.Definition -match $cmdNamesPattern } | Foreach-Object Name 89 | 90 | Register-ArgumentCompleter -CommandName $cmdNames -Native -ScriptBlock { 91 | param($wordToComplete, $commandAst, $cursorPosition) 92 | 93 | # The PowerShell completion has a habit of stripping the trailing space when completing: 94 | # git checkout 95 | # The Expand-GitCommand expects this trailing space, so pad with a space if necessary. 96 | $padLength = $cursorPosition - $commandAst.Extent.StartOffset 97 | $textToComplete = $commandAst.ToString().PadRight($padLength, ' ').Substring(0, $padLength) 98 | 99 | Expand-GitCommandPsFzf $textToComplete 100 | } 101 | 102 | } 103 | } 104 | else { 105 | if ($script:TabExpansionEnabled) { 106 | $script:TabExpansionEnabled = $false 107 | } 108 | } 109 | } 110 | 111 | function CheckFzfTrigger { 112 | param($commandName, $parameterName, $wordToComplete, $commandAst, $cursorPosition, $action) 113 | if ([string]::IsNullOrWhiteSpace($env:FZF_COMPLETION_TRIGGER)) { 114 | $completionTrigger = '**' 115 | } 116 | else { 117 | $completionTrigger = $env:FZF_COMPLETION_TRIGGER 118 | } 119 | if ($wordToComplete.EndsWith($completionTrigger)) { 120 | $wordToComplete = $wordToComplete.Substring(0, $wordToComplete.Length - $completionTrigger.Length) 121 | $wordToComplete 122 | } 123 | } 124 | 125 | 126 | function GetServiceSelection() { 127 | param( 128 | [scriptblock] 129 | $ResultAction 130 | ) 131 | $header = [System.Environment]::NewLine + $("{0,-24} NAME" -f "DISPLAYNAME") + [System.Environment]::NewLine 132 | $result = Get-Service | Where-Object { ![string]::IsNullOrEmpty($_.Name) } | ForEach-Object { 133 | "{0,-24} {1}" -f $_.DisplayName.Substring(0, [System.Math]::Min(24, $_.DisplayName.Length)), $_.Name } | Invoke-Fzf -Multi -Header $header 134 | $result | ForEach-Object { 135 | &$ResultAction $_ 136 | } 137 | } 138 | 139 | function RegisterBuiltinCompleters { 140 | $processIdOrNameScriptBlock = { 141 | param($commandName, $parameterName, $wordToComplete, $commandAst, $cursorPosition, $action) 142 | $wordToComplete = CheckFzfTrigger $commandName $parameterName $wordToComplete $commandAst $cursorPosition 143 | if ($null -ne $wordToComplete) { 144 | $selectType = $parameterName 145 | $script:resultArr = @() 146 | GetProcessSelection -ResultAction { 147 | param($result) 148 | $resultSplit = $result.split(' ', [System.StringSplitOptions]::RemoveEmptyEntries) 149 | 150 | if ($selectType -eq 'Name') { 151 | $processNameIdx = 3 152 | $script:resultArr += $resultSplit[$processNameIdx..$resultSplit.Length] -join ' ' 153 | } 154 | elseif ($selectType -eq 'Id') { 155 | $processIdIdx = 2 156 | $script:resultArr += $resultSplit[$processIdIdx] 157 | } 158 | } 159 | 160 | if ($script:resultArr.Length -ge 1) { 161 | $script:resultArr -join ', ' 162 | } 163 | 164 | InvokePromptHack 165 | } 166 | else { 167 | # don't return anything - let normal tab completion work 168 | } 169 | } 170 | 171 | 'Get-Process', 'Stop-Process' | ForEach-Object { 172 | Register-ArgumentCompleter -CommandName $_ -ParameterName "Name" -ScriptBlock $processIdOrNameScriptBlock 173 | Register-ArgumentCompleter -CommandName $_ -ParameterName "Id" -ScriptBlock $processIdOrNameScriptBlock 174 | } 175 | 176 | $serviceNameScriptBlock = { 177 | param($commandName, $parameterName, $wordToComplete, $commandAst, $cursorPosition, $action) 178 | $wordToComplete = CheckFzfTrigger $commandName $parameterName $wordToComplete $commandAst $cursorPosition 179 | if ($null -ne $wordToComplete) { 180 | if ($parameterName -eq 'Name') { 181 | $group = '$2' 182 | } 183 | elseif ($parameterName -eq 'DisplayName') { 184 | $group = '$1' 185 | } 186 | 187 | $script:resultArr = @() 188 | GetServiceSelection -ResultAction { 189 | param($result) 190 | $script:resultArr += $result.Substring(24 + 1) 191 | } 192 | 193 | if ($script:resultArr.Length -ge 1) { 194 | $script:resultArr -join ', ' 195 | } 196 | InvokePromptHack 197 | } 198 | else { 199 | # don't return anything - let normal tab completion work 200 | } 201 | } 202 | 203 | 'Get-Service', 'Start-Service', 'Stop-Service' | ForEach-Object { 204 | Register-ArgumentCompleter -CommandName $_ -ParameterName "Name" -ScriptBlock $serviceNameScriptBlock 205 | Register-ArgumentCompleter -CommandName $_ -ParameterName "DisplayName" -ScriptBlock $serviceNameScriptBlock 206 | } 207 | } 208 | 209 | 210 | function Expand-GitCommandPsFzf($lastWord) { 211 | if ([string]::IsNullOrWhiteSpace($env:FZF_COMPLETION_TRIGGER)) { 212 | $completionTrigger = '**' 213 | } 214 | else { 215 | $completionTrigger = $env:FZF_COMPLETION_TRIGGER 216 | } 217 | if ($lastWord.EndsWith($completionTrigger)) { 218 | $lastWord = $lastWord.Substring(0, $lastWord.Length - $completionTrigger.Length) 219 | Expand-GitWithFzf $lastWord 220 | } 221 | else { 222 | Expand-GitCommand $lastWord 223 | } 224 | } 225 | 226 | 227 | function Invoke-FzfTabCompletion() { 228 | $script:continueCompletion = $true 229 | do { 230 | $script:continueCompletion = script:Invoke-FzfTabCompletionInner 231 | } 232 | while ($script:continueCompletion) 233 | } 234 | 235 | function script:Invoke-FzfTabCompletionInner() { 236 | $script:result = @() 237 | 238 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Management.Automation") 239 | $line = $null 240 | $cursor = $null 241 | [Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor) 242 | 243 | if ($cursor -lt 0 -or [string]::IsNullOrWhiteSpace($line)) { 244 | return $false 245 | } 246 | 247 | try { 248 | $completions = [System.Management.Automation.CommandCompletion]::CompleteInput($line, $cursor, @{}) 249 | } 250 | catch { 251 | # some custom tab completions will cause CompleteInput() to throw, so we gracefully handle those cases. 252 | # For example, see the issue https://github.com/kelleyma49/PSFzf/issues/95. 253 | return $false 254 | } 255 | 256 | 257 | $completionMatches = $completions.CompletionMatches 258 | if ($completionMatches.Count -le 0) { 259 | return $false 260 | } 261 | $script:continueCompletion = $false 262 | 263 | if ($completionMatches.Count -eq 1) { 264 | $script:result = $completionMatches[0].CompletionText 265 | } 266 | elseif ($completionMatches.Count -gt 1) { 267 | $script:result = @() 268 | $script:checkCompletion = $true 269 | $cancelTrigger = 'ESC' 270 | $isTabTrigger = $script:TabContinuousTrigger -eq "`t" 271 | if ($isTabTrigger) { 272 | $expectTriggers = "tab,${cancelTrigger}" 273 | } 274 | else { 275 | $expectTriggers = "${script:TabContinuousTrigger},${cancelTrigger}" 276 | } 277 | 278 | 279 | # normalize so path works correctly for Windows: 280 | $path = $PWD.ProviderPath.Replace('\', '/') 281 | 282 | $arguments = @{ 283 | Layout = 'reverse' 284 | Expect = "$expectTriggers" 285 | PreviewWindow = 'down:30%' 286 | } 287 | if ($isTabTrigger) { 288 | $arguments["Bind"] = @('ctrl-/:change-preview-window(down,right:50%,border-top|hidden|)') 289 | } 290 | else { 291 | $arguments["Bind"] = @('tab:down', 'btab:up', 'ctrl-/:change-preview-window(down,right:50%,border-top|hidden|)') 292 | } 293 | 294 | # need to handle parameters differently so PowerShell doesn't parse completion item as a script parameter: 295 | if ( $completionMatches[0].ResultType -eq 'ParameterName') { 296 | $Command = $Line.Substring(0, $Line.indexof(' ')) 297 | $previewScript = $(Join-Path $PsScriptRoot 'helpers/PsFzfTabExpansion-Parameter.ps1') 298 | $arguments["Preview"] = $("$PowerShellCMD -NoProfile -NonInteractive -File \""$previewScript\"" $Command {}") 299 | } 300 | else { 301 | $previewScript = $(Join-Path $PsScriptRoot 'helpers/PsFzfTabExpansion-Preview.ps1') 302 | $arguments["Preview"] = $($script:PowershellCmd + " -NoProfile -NonInteractive -File \""$previewScript\"" \""" + $path + "\"" {}") 303 | } 304 | 305 | $script:fzfOutput = @() 306 | 307 | $completionMatches | ForEach-Object { $_.CompletionText } | Invoke-Fzf @arguments | ForEach-Object { 308 | $script:fzfOutput += $_ 309 | } 310 | 311 | if ($script:fzfOutput[0] -eq $cancelTrigger) { 312 | InvokePromptHack 313 | return $false 314 | } 315 | elseif ($script:fzfOutput.Length -gt 1) { 316 | $script:result = $script:fzfOutput[1] 317 | } 318 | 319 | # check if we should continue completion: 320 | if ($isTabTrigger) { 321 | $script:continueCompletion = $script:fzfOutput[0] -eq 'tab' 322 | } 323 | else { 324 | $script:continueCompletion = $script:fzfOutput[0] -eq $script:TabContinuousTrigger 325 | } 326 | 327 | 328 | InvokePromptHack 329 | } 330 | 331 | $result = $script:result 332 | if ($null -ne $result) { 333 | # quote strings if we need to: 334 | if ($result -is [system.array]) { 335 | for ($i = 0; $i -lt $result.Length; $i++) { 336 | $result[$i] = FixCompletionResult $result[$i] 337 | } 338 | $str = $result -join ',' 339 | } 340 | else { 341 | $str = FixCompletionResult $result 342 | } 343 | 344 | $isQuoted = $str.EndsWith("'") 345 | $resultTrimmed = $str.Trim(@('''', '"')) 346 | if (Test-Path "$resultTrimmed" -PathType Container) { 347 | if ($isQuoted) { 348 | $str = "'{0}{1}'" -f "$resultTrimmed", [IO.Path]::DirectorySeparatorChar.ToString() 349 | } 350 | else { 351 | $str = "$resultTrimmed" + [IO.Path]::DirectorySeparatorChar.ToString() 352 | } 353 | } 354 | else { 355 | # no more paths to complete, so let's stop completion: 356 | $str += ' ' 357 | $script:continueCompletion = $false 358 | } 359 | 360 | $leftCursor = $completions.ReplacementIndex 361 | $replacementLength = $completions.ReplacementLength 362 | if ($leftCursor -le 0 -and $replacementLength -le 0) { 363 | [Microsoft.PowerShell.PSConsoleReadLine]::Insert($str) 364 | } 365 | else { 366 | [Microsoft.PowerShell.PSConsoleReadLine]::Replace($leftCursor, $replacementLength, $str) 367 | } 368 | 369 | } 370 | 371 | return $script:continueCompletion 372 | } 373 | -------------------------------------------------------------------------------- /PSFzf.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'module' 3 | # 4 | # Generated by: 5 | # 6 | # Generated on: 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'PSFzf.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.0' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = '9454f107-c8bd-4fb9-9098-41a619ad0654' 19 | 20 | # Author of this module 21 | Author = 'Michael Kelley' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'Unknown' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) 2020. All rights reserved.' 28 | 29 | # Description of the functionality provided by this module 30 | Description = 'A thin wrapper around Fzf (https://github.com/junegunn/fzf). If PSReadline is loaded, this wrapper registers Fzf with the keyboard chord Ctrl+t.' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | # PowerShellVersion = '' 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | # RequiredModules = @() 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | RequiredAssemblies = @('PSFzf.dll') 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = @( 70 | 'Enable-PsFzfAliases', 71 | 'Invoke-FuzzyEdit', 72 | 'Invoke-FuzzyFasd', 73 | 'Invoke-FuzzyGitStatus', 74 | 'Invoke-FuzzyHistory', 75 | 'Invoke-FuzzyKillProcess', 76 | 'Invoke-FuzzyScoop', 77 | 'Invoke-FuzzySetLocation', 78 | 'Invoke-FuzzyZLocation', 79 | 'Invoke-Fzf', 80 | 'Invoke-FzfTabCompletion', 81 | 'Invoke-PsFzfGitBranches', 82 | 'Invoke-PsFzfGitFiles', 83 | 'Invoke-PsFzfGitHashes', 84 | 'Invoke-PsFzfGitStashes', 85 | 'Invoke-PsFzfGitTags', 86 | 'Invoke-PsFzfRipgrep', 87 | 'Set-LocationFuzzyEverything', 88 | 'Set-PsFzfOption' 89 | ) 90 | 91 | # Cmdlets to export from this module 92 | CmdletsToExport = '*' 93 | 94 | # Variables to export from this module 95 | VariablesToExport = '*' 96 | 97 | # Aliases to export from this module 98 | AliasesToExport = '*' 99 | 100 | # List of all modules packaged with this module 101 | # ModuleList = @() 102 | 103 | # List of all files packaged with this module 104 | # FileList = @() 105 | 106 | # Private data to pass to the module specified in RootModule/ModuleToProcess 107 | PrivateData = @{ 108 | 109 | PSData = @{ 110 | # Prerelease = "alpha" 111 | } 112 | } 113 | 114 | # HelpInfo URI of this module 115 | # HelpInfoURI = '' 116 | 117 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 118 | # DefaultCommandPrefix = '' 119 | 120 | } 121 | 122 | -------------------------------------------------------------------------------- /PSFzf.pssproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Debug 4 | 2.0 5 | 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 6 | Exe 7 | MyApplication 8 | MyApplication 9 | PowerShellModuleProject1 10 | 11 | 12 | true 13 | full 14 | false 15 | bin\Debug\ 16 | DEBUG;TRACE 17 | prompt 18 | 4 19 | 20 | 21 | pdbonly 22 | true 23 | bin\Release\ 24 | TRACE 25 | prompt 26 | 4 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /PSFzf.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2035 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "PSFzf", "PSFzf.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSFzf-Binary", "PSFzf-Binary\PSFzf-Binary.csproj", "{AC91F17D-066F-4DE0-9DE4-E3B75DE12C8C}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AC91F17D-066F-4DE0-9DE4-E3B75DE12C8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AC91F17D-066F-4DE0-9DE4-E3B75DE12C8C}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AC91F17D-066F-4DE0-9DE4-E3B75DE12C8C}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AC91F17D-066F-4DE0-9DE4-E3B75DE12C8C}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {EA8803B4-EC4D-4B9D-A34A-700E60114CB6} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /PSFzf.tests.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # This is a PowerShell Unit Test file. 3 | # You need a unit test framework such as Pester to run PowerShell Unit tests. 4 | # You can download Pester from http://go.microsoft.com/fwlink/?LinkID=534084 5 | # 6 | Get-Module PsFzf | Remove-Module 7 | 8 | # set env variable so Import-Module doesn't fail: 9 | if ([string]::IsNullOrEmpty($env:GOPATH)) { 10 | $env:GOPATH = "c:\ADirectoryThatShouldNotExist\" 11 | } 12 | Import-Module $(Join-Path $PSScriptRoot PSFzf.psd1) -ErrorAction Stop 13 | 14 | Describe "Find-CurrentPath" { 15 | InModuleScope PsFzf { 16 | Context "Function Exists" { 17 | It "Should Return Nothing" { 18 | $line = "" ; $cursor = 0 19 | $leftCursor = $rightCursor = $null 20 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be $null 21 | $leftCursor | Should -Be 0 22 | $rightCursor | Should -Be 0 23 | } 24 | 25 | It "Should Return Nothing with Spaces Cursor at Beginning" { 26 | $line = " " ; $cursor = 0 27 | $leftCursor = $rightCursor = $null 28 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be " " 29 | $leftCursor | Should -Be 0 30 | $rightCursor | Should -Be 0 31 | } 32 | 33 | It "Should Return Nothing with Spaces Cursor at End" { 34 | $line = " " ; $cursor = 1 35 | $leftCursor = $rightCursor = $null 36 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be " " 37 | $leftCursor | Should -Be 0 38 | $rightCursor | Should -Be 0 39 | } 40 | 41 | It "Should Return Path Cursor at Beginning for Single Char" { 42 | $line = "~" ; $cursor = 0 43 | $leftCursor = $rightCursor = $null 44 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be "~" 45 | $leftCursor | Should -Be 0 46 | $rightCursor | Should -Be ($line.Length - 1) 47 | } 48 | 49 | It "Should Return Path Cursor at Beginning" { 50 | $line = "C:\Windows\" ; $cursor = 0 51 | $leftCursor = $rightCursor = $null 52 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be "c:\Windows\" 53 | $leftCursor | Should -Be 0 54 | $rightCursor | Should -Be ($line.Length - 1) 55 | } 56 | 57 | It "Should Return Path Cursor at End" { 58 | $line = "C:\Windows\" ; $cursor = $line.Length 59 | $leftCursor = $rightCursor = $null 60 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be "c:\Windows\" 61 | $leftCursor | Should -Be 0 62 | $rightCursor | Should -Be ($line.Length - 1) 63 | } 64 | 65 | It "Should Return Command and Path Cursor at Beginning" { 66 | $line = "cd C:\Windows\" ; $cursor = 0 67 | $leftCursor = $rightCursor = $null 68 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be "cd" 69 | $leftCursor | Should -Be 0 70 | $rightCursor | Should -Be ('cd'.Length - 1) 71 | } 72 | 73 | It "Should Return Command and Path Cursor at End" { 74 | $line = "cd C:\Windows\" ; $cursor = $line.Length 75 | $leftCursor = $rightCursor = $null 76 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be "c:\Windows\" 77 | $leftCursor | Should -Be 'cd '.Length 78 | $rightCursor | Should -Be ($line.Length - 1) 79 | } 80 | 81 | It "Should Return Command and Path Cursor at End" { 82 | $line = "cd C:\Windows\" ; $cursor = $line.Length - 1 83 | $leftCursor = $rightCursor = $null 84 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be "c:\Windows\" 85 | $leftCursor | Should -Be 'cd '.Length 86 | $rightCursor | Should -Be ($line.Length - 1) 87 | } 88 | 89 | It "Should Return Path With Quotes Cursor at Beginning" -ForEach @( 90 | @{ Quote = '"' } 91 | @{ Quote = "'" } 92 | ) { 93 | $line = $quote + 'C:\Program Files\' + $quote ; $cursor = 0 94 | $leftCursor = $rightCursor = $null 95 | Find-CurrentPath $line $cursor ([ref]$leftCursor) ([ref]$rightCursor) | Should -Be 'C:\Program Files\' 96 | $leftCursor | Should -Be 0 97 | $rightCursor | Should -Be ($line.Length - 1) 98 | } 99 | } 100 | } 101 | } 102 | 103 | Describe "Add-BinaryModuleTypes" { 104 | InModuleScope PsFzf { 105 | Context "Module Loaded" { 106 | It "Be Able to Create Type" { 107 | $filePath = Join-Path ([system.io.path]::GetTempPath()) 'TestFile.txt' 108 | 1..100 | Add-Content $filePath 109 | $newObject = New-Object PSFzf.IO.ReverseLineReader -ArgumentList $filePath 110 | $newObject | Should -Not -Be $null 111 | } 112 | } 113 | } 114 | } 115 | 116 | Describe "Check FixCompletionResult" { 117 | InModuleScope PsFzf { 118 | Context "Non-quoted Strings Should Not Change" { 119 | It "Check Simple String" { 120 | FixCompletionResult("not_quoted") | Should -Be "not_quoted" 121 | } 122 | It "Check Simple String with quote" { 123 | FixCompletionResult("not_quotedwith'") | Should -Be "not_quotedwith'" 124 | } 125 | } 126 | 127 | Context "Non-quoted Strings With Spaces Should Change" { 128 | It "Check Simple String With Space" { 129 | FixCompletionResult("with space") | Should -Be """with space""" 130 | } 131 | It "Check Simple String with quote" { 132 | FixCompletionResult("with space, ' and with'") | Should -Be """with space, ' and with'""" 133 | } 134 | } 135 | 136 | Context "Quoted Strings Should Not Change" { 137 | It "Check Simple String With Space and Already Double Quoted" { 138 | FixCompletionResult("""with space and already quoted""") | Should -Be """with space and already quoted""" 139 | } 140 | 141 | It "Check Simple String With Space and Already Single Quoted" { 142 | FixCompletionResult("'with space and already quoted'") | Should -Be "'with space and already quoted'" 143 | } 144 | } 145 | } 146 | } 147 | 148 | Describe "Check Parameters" { 149 | InModuleScope PsFzf { 150 | Context "Parameters Should Fail" { 151 | It "Borders Should -Be Mutally Exclusive" { 152 | { $_ = '' | Invoke-Fzf -Border -BorderStyle 'sharp' } | 153 | Should -Throw '*are mutally exclusive' 154 | } 155 | 156 | It "Validate Tiebreak" { 157 | { $_ = '' | Invoke-Fzf -Tiebreak 'Tiebreak' } | 158 | Should -Throw 'Cannot validate argument on parameter ''Tiebreak''*' 159 | } 160 | 161 | It "Validate BorderStyle" { 162 | { $_ = '' | Invoke-Fzf -BorderStyle 'InvalidStyle' } | 163 | Should -Throw 'Cannot validate argument on parameter ''BorderStyle''*' 164 | } 165 | 166 | It "Validate Info" { 167 | { $_ = '' | Invoke-Fzf -Info 'InvalidInfo' } | 168 | Should -Throw 'Cannot validate argument on parameter ''Info''*' 169 | } 170 | 171 | It "Validate Height Pattern Percentage" { 172 | { $_ = '' | Invoke-Fzf -Height '1000%' } | 173 | Should -Throw 'Cannot validate argument on parameter ''Height''*' 174 | } 175 | 176 | It "Validate Height Pattern Non-Number" { 177 | { $_ = '' | Invoke-Fzf -Height 'adf1000' } | 178 | Should -Throw 'Cannot validate argument on parameter ''Height''*' 179 | } 180 | 181 | It "Validate Height Pattern Negative" { 182 | { $_ = '' | Invoke-Fzf -Height '-1' } | 183 | Should -Throw 'Cannot validate argument on parameter ''Height''*' 184 | } 185 | 186 | It "Validate MinHeight Pattern Non-Number" { 187 | { $_ = '' | Invoke-Fzf -MinHeight 'adf1' -Height 10 } | 188 | Should -Throw 'Cannot process argument transformation on parameter ''MinHeight''*' 189 | } 190 | 191 | It "Validate MinHeight Pattern Negative" { 192 | { $_ = '' | Invoke-Fzf -MinHeight '-1' -Height 10 } | 193 | Should -Throw 'Cannot validate argument on parameter ''MinHeight''*' 194 | } 195 | 196 | } 197 | } 198 | } 199 | 200 | Describe "Get-EditorLaunch" { 201 | 202 | 203 | InModuleScope PSFzf { 204 | Context "Vim" { 205 | BeforeEach { 206 | $env:PSFZF_EDITOR_OPTIONS = $null 207 | $env:VSCODE_PID = $null 208 | $env:VISUAL = $null 209 | $env:EDITOR = $null 210 | 211 | $testFile1 = Join-Path $TestDrive 'somefile1.txt' 212 | Set-Content -Path $testFile1 -Value "hello 1" 213 | $testFile2 = Join-Path $TestDrive 'somefile2.txt' 214 | Set-Content -Path $testFile2 -Value "hello 2" 215 | } 216 | 217 | It "Should Return vim Single" { 218 | $env:EDITOR = 'vim' 219 | Get-EditorLaunch $testFile1 | Should -Be "vim ""$testFile1"" +0" 220 | } 221 | 222 | It "Should Return vim Single With Quotes" { 223 | $env:EDITOR = 'vim' 224 | Get-EditorLaunch """$testFile1""" | Should -Be "vim ""$testFile1"" +0" 225 | } 226 | 227 | It "Should Return vim Single With Options" { 228 | $env:EDITOR = 'vim' 229 | $env:PSFZF_EDITOR_OPTIONS = "--clean" 230 | Get-EditorLaunch $testFile1 | Should -Be "vim --clean ""$testFile1"" +0" 231 | } 232 | 233 | It "Should Return vim Single with Line Number" { 234 | $env:EDITOR = 'vim' 235 | Get-EditorLaunch $testFile1 -LineNum 101 | Should -Be "vim ""$testFile1"" +101" 236 | } 237 | 238 | It "Should Return vim Multiple" { 239 | $env:EDITOR = 'vim' 240 | Get-EditorLaunch @($testFile1, $testFile2) | Should -Be "vim ""$testFile1"" ""$testFile2""" 241 | } 242 | 243 | It "Should Return vim Multiple With Quotes" { 244 | $env:EDITOR = 'vim' 245 | Get-EditorLaunch @("""$testFile1""", """$testFile2""") | Should -Be "vim ""$testFile1"" ""$testFile2""" 246 | } 247 | 248 | It "Should Return code Single" { 249 | $env:EDITOR = 'code' 250 | Get-EditorLaunch $testFile1 | Should -Be $('code --goto "{0}:0"' -f $testFile1) 251 | } 252 | 253 | It "Should Return code Single With Quotes" { 254 | $env:EDITOR = 'code' 255 | Get-EditorLaunch """$testFile1""" | Should -Be $('code --goto "{0}:0"' -f $testFile1) 256 | } 257 | 258 | It "Should Return code Single Reuse Window" { 259 | $env:EDITOR = 'code' 260 | $env:VSCODE_PID = 100 261 | Get-EditorLaunch $testFile1 | Should -Be $('code --reuse-window --goto "{0}:0"' -f $testFile1) 262 | } 263 | 264 | It "Should Return code Single with Line Number" { 265 | $env:EDITOR = 'code' 266 | Get-EditorLaunch $testFile1 -LineNum 100 | Should -Be $('code --goto "{0}:100"' -f $testFile1) 267 | } 268 | 269 | It "Should Return code Multiple" { 270 | $env:EDITOR = 'code' 271 | Get-EditorLaunch @($testFile1, $testFile2) | Should -Be "code ""$testFile1"" ""$testFile2""" 272 | } 273 | 274 | It "Should Return code Multiple With Quotes" { 275 | $env:EDITOR = 'code' 276 | Get-EditorLaunch @("""$testFile1""", """$testFile2""") | Should -Be "code ""$testFile1"" ""$testFile2""" 277 | } 278 | } 279 | } 280 | } 281 | # CI seems to have problems on GitHub CI - timing issues? 282 | if ( $false ) { 283 | 284 | Describe "Invoke-Fzf" { 285 | InModuleScope PsFzf { 286 | Context "Function Exists" { 287 | It "Should Return Nothing" { 288 | $result = '' | Invoke-Fzf -Query 'file1.txt' -Select1 -Exit0 -Filter ' ' 289 | $result | Should -Be $null 290 | } 291 | 292 | It "Should Return 1 Item, 1 Element" { 293 | $result = 'file1.txt' | Invoke-Fzf -Select1 -Exit0 -Filter 'file1.txt' 294 | $result | Should -Be 'file1.txt' 295 | } 296 | 297 | It "Should Return 1 Item, Case Insensitive" { 298 | $result = 'file1.txt' | Invoke-Fzf -Select1 -Exit0 -CaseInsensitive -Filter 'FILE1.TXT' 299 | $result | Should -Be 'file1.txt' 300 | } 301 | 302 | It "Should Return Nothing, Case Sensitive" { 303 | $result = 'file1.txt' | Invoke-Fzf -Select1 -Exit0 -CaseSensitive -Filter 'FILE1.TXT' 304 | $result | Should -Be $null 305 | } 306 | 307 | It "Should Return 1 Item, No Multi" { 308 | $result = 'file1.txt', 'file2.txt' | Invoke-Fzf -Multi -Select1 -Exit0 -Filter "file1" 309 | $result | Should -Be 'file1.txt' 310 | } 311 | 312 | It "Should Return 2 Item, Multi" { 313 | $result = 'file1.txt', 'file2.txt' | Invoke-Fzf -Multi -Select1 -Exit0 -Filter "file" 314 | $result.Length | Should -Be 2 315 | $result[0] | Should -Be 'file1.txt' 316 | $result[1] | Should -Be 'file2.txt' 317 | } 318 | } 319 | } 320 | } 321 | } -------------------------------------------------------------------------------- /PSScriptAnalyzerSettings.psd1: -------------------------------------------------------------------------------- 1 | # borrowed from https://devblogs.microsoft.com/powershell/using-psscriptanalyzer-to-check-powershell-version-compatibility/ 2 | @{ 3 | Rules = @{ 4 | PSUseCompatibleSyntax = @{ 5 | Enable = $true 6 | 7 | # List the targeted versions of PowerShell here 8 | TargetVersions = @( 9 | '5.1', 10 | '6.2' 11 | '7.0' 12 | ) 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSFzf 2 | 3 | [![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/PSFzf.svg)](https://www.powershellgallery.com/packages/PSFzf) 4 | [![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/PSFzf?include_prereleases)](https://www.powershellgallery.com/packages/PSFzf) 5 | [![PowerShell Gallery Downloads](https://img.shields.io/powershellgallery/dt/PSFzf)](https://www.powershellgallery.com/packages/PSFzf) 6 | [![Build status](https://github.com/kelleyma49/PSFzf/workflows/CI/badge.svg)](https://github.com/kelleyma49/PSFzf/actions) 7 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kelleyma49/PSFzf/blob/master/LICENSE) 8 | 9 | PSFzf is a PowerShell module that wraps [fzf](https://github.com/junegunn/fzf), a fuzzy file finder for the command line. 10 | 11 | ![keyboard shortcut demonstration](https://raw.github.com/kelleyma49/PSFzf/master/docs/PSFzfExample.gif) 12 | 13 | ## Usage 14 | 15 | To change to a user selected directory: 16 | 17 | ```powershell 18 | Get-ChildItem . -Recurse -Attributes Directory | Invoke-Fzf | Set-Location 19 | ``` 20 | 21 | To edit a file: 22 | 23 | ```powershell 24 | Get-ChildItem . -Recurse -Attributes !Directory | Invoke-Fzf | % { notepad $_ } 25 | ``` 26 | 27 | For day-to-day usage, see the [helper functions included with this module](https://github.com/kelleyma49/PSFzf#helper-functions). 28 | 29 | ## PSReadline Integration 30 | 31 | ### Select Current Provider Path (default chord: Ctrl+t) 32 | 33 | Press Ctrl+t to start PSFzf to select provider paths. PSFzf will parse the current token and use that as the starting path to search from. If current token is empty, or the token isn't a valid path, PSFzf will search below the current working directory. 34 | 35 | Multiple items can be selected. If more than one is selected by the user, the results are returned as a comma separated list. Results are properly quoted if they contain whitespace. 36 | 37 | ### Reverse Search Through PSReadline History (default chord: Ctrl+r) 38 | 39 | Press Ctrl+r to start PSFzf to select a command in the command history saved by PSReadline. PSFzf will insert the command into the current line, but it will not execute the command. 40 | 41 | PSFzf does not override Ctrl+r by default. To confirm that you want to override PSReadline's chord binding, use the [`Set-PsFzfOption`](docs/Set-PsFzfOption.md) command: 42 | 43 | ```powershell 44 | # replace 'Ctrl+t' and 'Ctrl+r' with your preferred bindings: 45 | Set-PsFzfOption -PSReadlineChordProvider 'Ctrl+t' -PSReadlineChordReverseHistory 'Ctrl+r' 46 | ``` 47 | 48 | ### Set-Location Based on Selected Directory (default chord: Alt+c) 49 | 50 | Press Alt+c to start PSFzf to select a directory. By default, `Set-Location` will be called with the selected directory. You can override the default command with the following code in our `$PROFILE`: 51 | 52 | ```powershell 53 | # example command - use $Location with a different command: 54 | $commandOverride = [ScriptBlock]{ param($Location) Write-Host $Location } 55 | # pass your override to PSFzf: 56 | Set-PsFzfOption -AltCCommand $commandOverride 57 | ``` 58 | 59 | ### Search Through Command Line Arguments in PSReadline History (default chord: Alt+a) 60 | 61 | Press Alt+a to start PSFzf to select command line arguments used in PSReadline history. The picked argument will be inserted in the current line. The line that would result from the selection is shown in the preview window. 62 | 63 | ## Tab Expansion 64 | 65 | PSFzf can replace the standard tab completion: 66 | 67 | ```powershell 68 | Set-PSReadLineKeyHandler -Key Tab -ScriptBlock { Invoke-FzfTabCompletion } 69 | ``` 70 | 71 | To activate continuous completion, press the directory separator character to complete the current selection and start tab completion for the next part of the container path. 72 | 73 | PSFzf supports specialized tab expansion with a small set of commands. After typing the default trigger command, which defaults to "`**`", and press Tab, PsFzf tab expansion will provide selectable list of options. 74 | 75 | The following commands are supported: 76 | 77 | | Command | Notes | 78 | |---------|-------| 79 | | `git` | Uses [`posh-git`](https://github.com/dahlbyk/posh-git) for providing tab completion options. Requires at least version 1.0.0 Beta 4. 80 | | `Get-Service`, `Start-Service`, `Stop-Service` | Allows the user to select between the installed services. 81 | | `Get-Process`, `Start-Process` | Allows the user to select between running processes. 82 | 83 | To override the trigger command, set `FZF_COMPLETION_TRIGGER` to your preferred trigger sequence. 84 | 85 | Use the following command to enable tab expansion: 86 | 87 | ```powershell 88 | Set-PsFzfOption -TabExpansion 89 | ``` 90 | 91 | ## Using within a Pipeline 92 | 93 | `Invoke-Fzf` works with input from a pipeline. You can use it in the middle of a pipeline, or as part of an expression. 94 | 95 | ```powershell 96 | Set-Location (Get-ChildItem . -Recurse | ? { $_.PSIsContainer } | Invoke-Fzf) # This works as of version 2.2.8 97 | Get-ChildItem . -Recurse | ? { $_.PSIsContainer } | Invoke-Fzf | Set-Location 98 | ``` 99 | 100 | ## Overriding Behavior 101 | 102 | PsFzf supports overriding behavior by setting these fzf environment variables: 103 | | Env Var Name | Description | 104 | |------------------------------|------------------------------------------------------------------------------------------------------------------------------| 105 | | `_PSFZF_FZF_DEFAULT_OPTS` | If this environment variable is set, then `FZF_DEFAULT_OPTS` is temporarily set with the contents. This allows the user to have different default options for PSFZF and fzf. 106 | | `FZF_DEFAULT_COMMAND` | The command specified in this environment variable will override the default command when PSFZF detects that the current location is a file system provider. | 107 | | `FZF_CTRL_T_COMMAND` | The command specified in this environment variable will be used when Ctrl+t is pressed by the user. | 108 | | `FZF_ALT_C_COMMAND` | The command specified in this environment variable will be used when Alt+c is pressed by the user. | 109 | | `PSFZF_EDITOR_OPTIONS` | Contains options passed to the editor application used in the `Invoke-FuzzyEdit()` function | 110 | 111 | ## Helper Functions 112 | 113 | In addition to its core function [Invoke-Fzf](docs/Invoke-Fzf.md), PSFzf includes a set of useful functions and aliases. The aliases are not installed by default. To enable aliases, use [`Set-PSFzfOption`](docs/Set-PsFzfOption.md)'s `-EnableAlias`* options. 114 | 115 | | Function | Alias | Description 116 | | ---------------------------------------------------------------------| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 117 | | [`Invoke-FuzzyEdit`](docs/Invoke-FuzzyEdit.md) | `fe` | Starts an editor for the selected files in the fuzzy finder. 118 | | [`Invoke-FuzzyFasd`](docs/Invoke-FuzzyFasd.md) | `ff` | Starts fzf with input from the files saved in [fasd](https://github.com/clvv/fasd)(non-Windows) or [fasdr](https://github.com/kelleyma49/fasdr) (Windows) and sets the current location. 119 | | [`Invoke-FuzzyZLocation`](docs/Invoke-FuzzyZLocation.md) | `fz` | Starts fzf with input from the history of [ZLocation](https://github.com/vors/ZLocation) and sets the current location. 120 | | [`Invoke-FuzzyGitStatus`](docs/Invoke-FuzzyGitStatus.md) | `fgs` | Starts fzf with input from output of the `git status` function. 121 | | [`Invoke-FuzzyHistory`](docs/Invoke-FuzzyHistory.md) | `fh` | Rerun a previous command from history based on the user's selection in fzf. 122 | | [`Invoke-FuzzyKillProcess`](docs/Invoke-FuzzyKillProcess.md) | `fkill` | Runs `Stop-Process` on processes selected by the user in fzf. 123 | | [`Invoke-FuzzySetLocation`](docs/Invoke-FuzzySetLocation.md) | `fd` | Sets the current location from the user's selection in fzf. 124 | | [`Invoke-FuzzyScoop`](docs/Invoke-FuzzyScoop.md) | `fs` | Starts fzf on [Scoop](https://scoop.sh) applications list. 125 | | [`Invoke-PsFzfRipgrep`](docs/Invoke-PsFzfRipgrep.md) | N/A | Uses Ripgrep and Fzf to interactively search files. 126 | | [`Set-LocationFuzzyEverything`](docs/Set-LocationFuzzyEverything.md) | `cde` | Sets the current location based on the [Everything](https://www.voidtools.com/) database. 127 | 128 | ## Prerequisites 129 | 130 | Follow the [installation instructions for fzf](https://github.com/junegunn/fzf#installation) before installing PSFzf. PSFzf will run `Get-Command` to find `fzf` in your path. 131 | 132 | ### Windows 133 | 134 | The latest version of `fzf` is available via [Chocolatey](https://chocolatey.org/packages/fzf), or you can download the `fzf` binary and place it in your path. Run `Get-Command fzf*.exe` to verify that PowerShell can find the executable. 135 | 136 | PSFzf has been tested under PowerShell 5.0, 6.0, and 7.0. 137 | 138 | ### MacOS 139 | 140 | Use Homebrew or download the binary and place it in your path. Run `Get-Command fzf*` to verify that PowerShell can find the executable. 141 | 142 | PSFzf has been tested under PowerShell 6.0 and 7.0. 143 | 144 | ### Linux 145 | 146 | PSFzf has been tested under PowerShell 6.0 and 7.0 in the Windows Subsystem for Linux. 147 | 148 | ## Installation 149 | 150 | PSFzf is available on the [PowerShell Gallery](https://www.powershellgallery.com/packages/PSFzf) and [Scoop](https://github.com/ScoopInstaller/Extras/blob/master/bucket/psfzf.json). PSReadline should be imported before PSFzf as PSFzf registers PSReadline key handlers listed in the [PSReadline integration section](https://github.com/kelleyma49/PSFzf#psreadline-integration). 151 | 152 | ## Helper Function Requirements 153 | 154 | * [`Invoke-FuzzyFasd`](docs/Invoke-FuzzyFasd.md) requires [Fasdr](https://github.com/kelleyma49/fasdr) to be previously installed under Windows. Other platforms require [Fasd](https://github.com/clvv/fasd) to be installed. 155 | * [`Invoke-FuzzyZLocation`](docs/Invoke-FuzzyZLocation.md) requires [ZLocation](https://github.com/vors/ZLocation) and works only under Windows. 156 | * [`Set-LocationFuzzyEverything`](docs/Set-LocationFuzzyEverything.md) works only under Windows and requires [PSEverything](https://github.com/powercode/PSEverything) to be previously installed. 157 | * [`Invoke-FuzzyScoop`](docs/Invoke-FuzzyScoop.md) works only under Windows and requires [Scoop](https://scoop.sh) to be previously installed. 158 | * [`Invoke-FuzzyGitStatus`](docs/Invoke-FuzzyGitStatus.md) requires [git](https://git-scm.com/) to be installed. 159 | * [`Invoke-PsFzfRipgrep`](docs/Invoke-PsFzfRipgrep.md) requires [ripgrep](https://github.com/BurntSushi/ripgrep) and [bat](https://github.com/sharkdp/bat) to be installed. 160 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzyEdit.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzyEdit 7 | ## SYNOPSIS 8 | Starts an editor for the selected files in the fuzzy finder. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzyEdit [-Directory ] 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Allows the user to pick multiple files from fzf that will be launched in the editor defined in the environment variable EDITOR. Under Windows, Visual Studio Code is launched if EDITOR is not defined. If not running under Windows, vim is launched. 17 | If Invoke-FuzzyEdit is executed in the Visual Studio Code integrated terminal, the --reuse-window option is used which should cause the file to open in the currently active Visual Studio Code window. 18 | ## EXAMPLES 19 | 20 | ### Launch Invoke-FuzzyEdit 21 | 22 | Launches fzf in the current directory and loads the selected files in the default editor. 23 | 24 | 25 | ```PowerShell 26 | Invoke-FuzzyEdit 27 | ``` 28 | 29 | ## PARAMETERS 30 | ### -Directory 31 | Path to directory to start fzf in. If not passed defaults to current directory. 32 | 33 | ```yaml 34 | Type: String 35 | Parameter Sets: (All) 36 | Aliases: 37 | 38 | Required: False 39 | Position: 0 40 | Default value: None 41 | Accept pipeline input: False 42 | Accept wildcard characters: False 43 | ``` 44 | 45 | ### CommonParameters 46 | This cmdlet does not support common parameters. 47 | ## INPUTS 48 | 49 | ### None 50 | This cmdlet does not accept any input. 51 | ## OUTPUTS 52 | 53 | ### None 54 | This cmdlet does not generate any output. 55 | ## NOTES 56 | 57 | ## RELATED LINKS 58 | 59 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzyFasd.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzyFasd 7 | ## SYNOPSIS 8 | Starts fzf with input from the files saved in fasd (non-Windows) or fasdr (Windows) and sets the current location. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzyFasd 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Allows the user to select a file from fasd's database and set the current location. 17 | ## EXAMPLES 18 | 19 | ### Launch Invoke-FuzzyFasd 20 | 21 | Launches fzf with input from fasd's database and set the location based on user selection. 22 | 23 | 24 | ```PowerShell 25 | Invoke-FuzzyFasd 26 | ``` 27 | 28 | ## PARAMETERS 29 | 30 | ### CommonParameters 31 | This cmdlet does not support common parameters. 32 | ## INPUTS 33 | 34 | ### None 35 | This cmdlet does not accept any input. 36 | ## OUTPUTS 37 | 38 | ### None 39 | This cmdlet does not generate any output. 40 | ## NOTES 41 | This function will be created if [fasdr](https://github.com/kelleyma49/fasdr) can be found by PowerShell. 42 | ## RELATED LINKS 43 | 44 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzyGitStatus.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzyGitStatus 7 | ## SYNOPSIS 8 | Starts fzf with input from output of the `git status` function. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzyGitStatus 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Allows the user to select files listed from the output of the `git status` function executed in the current directory. 17 | 18 | These keyboard shortcuts are supported: 19 | 20 | - CTRL+A selects all items 21 | - CTRL+D deselects all items 22 | - CTRL+T toggles the selection state of all items 23 | 24 | ## EXAMPLES 25 | 26 | ### Launch Invoke-FuzzyGitStatus 27 | 28 | Launches fzf with input from a `git status` command executed in the current directory. 29 | 30 | 31 | ```PowerShell 32 | Invoke-FuzzyGitStatus 33 | ``` 34 | 35 | ## PARAMETERS 36 | 37 | ### CommonParameters 38 | This cmdlet does not support common parameters. 39 | ## INPUTS 40 | 41 | ### None 42 | This cmdlet does not accept any input. 43 | ## OUTPUTS 44 | 45 | ### None 46 | This cmdlet does not generate any output. 47 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzyHistory.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzyHistory 7 | ## SYNOPSIS 8 | Rerun a previous command from history based on the user's selection in fzf. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzyHistory 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Executes a command selected by the user in fzf. 17 | ## EXAMPLES 18 | 19 | ### Launch Invoke-FuzzyHistory 20 | 21 | Launches fzf and executes the command selected by the user. 22 | 23 | ```PowerShell 24 | Invoke-FuzzyHistory 25 | ``` 26 | 27 | ## PARAMETERS 28 | 29 | ### CommonParameters 30 | This cmdlet does not support common parameters. 31 | ## INPUTS 32 | 33 | ### None 34 | This cmdlet does not accept any input. 35 | ## OUTPUTS 36 | 37 | ### None 38 | This cmdlet does not generate any output. 39 | ## NOTES 40 | 41 | ## RELATED LINKS 42 | 43 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzyKillProcess.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzyKillProcess 7 | ## SYNOPSIS 8 | Runs `Stop-Process` on processes selected by the user in fzf. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzyKillProcess 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Runs Stop-Process on all processes selected by the the user in fzf. 17 | ## EXAMPLES 18 | 19 | ### Launch Invoke-FuzzyKillProcess 20 | 21 | Launches fzf and stops the selected processes. 22 | 23 | 24 | ```PowerShell 25 | Invoke-FuzzyKillProcess 26 | ``` 27 | 28 | ## PARAMETERS 29 | 30 | ### CommonParameters 31 | This cmdlet does not support common parameters. 32 | ## INPUTS 33 | 34 | ### None 35 | This cmdlet does not accept any input. 36 | ## OUTPUTS 37 | 38 | ### None 39 | This cmdlet does not generate any output. 40 | ## NOTES 41 | 42 | ## RELATED LINKS 43 | 44 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzyScoop.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzyScoop 7 | ## SYNOPSIS 8 | Starts fzf on Scoop applications list. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzyScoop [-subcommand ] [-subcommandflags ] 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Allows the user to select (multiple) applications from locally stored Scoop buckets and runs `subcommand` on them. Default value of `subcommand` is `install`. 17 | ## EXAMPLES 18 | 19 | ### Launch Invoke-FuzzyScoop 20 | 21 | Launches fzf and selects some applications to install. 22 | 23 | ```PowerShell 24 | Invoke-FuzzyScoop 25 | ``` 26 | 27 | ### Launch Invoke-FuzzyScoop using `uninstall` subcommand 28 | 29 | Launches fzf for selects some applications to uninstall. 30 | 31 | 32 | ```PowerShell 33 | Invoke-FuzzyScoop -subcommand uninstall 34 | ``` 35 | 36 | ## PARAMETERS 37 | ### -subcommand 38 | The Scoop command that will be run on the selected applications. 39 | 40 | ```yaml 41 | Type: String 42 | Parameter Sets: (All) 43 | Aliases: 44 | 45 | Required: False 46 | Position: 0 47 | Default value: install 48 | Accept pipeline input: False 49 | Accept wildcard characters: False 50 | ``` 51 | 52 | ### -subcommandflags 53 | A set of flags that will be additionally passed to the Scoop subcommand. 54 | 55 | ```yaml 56 | Type: String 57 | Parameter Sets: (All) 58 | Aliases: 59 | 60 | Required: False 61 | Position: 0 62 | Default value: 63 | Accept pipeline input: False 64 | Accept wildcard characters: False 65 | ``` 66 | 67 | ### CommonParameters 68 | This cmdlet does not support common parameters. 69 | ## INPUTS 70 | 71 | ### None 72 | This cmdlet does not accept any input. 73 | ## OUTPUTS 74 | 75 | ### None 76 | This cmdlet does not generate any output. 77 | ## NOTES 78 | This function will be effective if [Scoop](https://scoop.sh) can be found in PATH. 79 | 80 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzySetLocation.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzySetLocation 7 | ## SYNOPSIS 8 | Sets the current location from the user's selection in fzf. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzySetLocation [-Directory ] 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Set the current location of a subdirectory based on the user's selection in fzf. 17 | ## EXAMPLES 18 | 19 | ### Set-Location to a subdirectory located in the Windows directory 20 | 21 | Launches fzf and allows the user to select a subdirectory from the Windows directory. 22 | 23 | ```PowerShell 24 | Invoke-FuzzySetLocation c:\Windows 25 | ``` 26 | 27 | ## PARAMETERS 28 | ### -Directory 29 | The path to a directory that contains the subdirectories that the user will choose from. 30 | 31 | ```yaml 32 | Type: String 33 | Parameter Sets: (All) 34 | Aliases: 35 | 36 | Required: False 37 | Position: 0 38 | Default value: None 39 | Accept pipeline input: False 40 | Accept wildcard characters: False 41 | ``` 42 | 43 | ### CommonParameters 44 | This cmdlet does not support common parameters. 45 | ## INPUTS 46 | 47 | ### None 48 | This cmdlet does not accept any input. 49 | ## OUTPUTS 50 | 51 | ### None 52 | This cmdlet does not generate any output. 53 | ## NOTES 54 | 55 | ## RELATED LINKS 56 | 57 | -------------------------------------------------------------------------------- /docs/Invoke-FuzzyZLocation.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-FuzzyZLocation 7 | ## SYNOPSIS 8 | Starts fzf with input from the history saved by ZLocation and sets the current location. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-FuzzyZLocation 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Allows the user to select a file from ZLocation's history database and set the current location. 17 | ## EXAMPLES 18 | 19 | ### Launch Invoke-FuzzyZLocation 20 | 21 | Launches fzf with input from ZLocation's history database and set the location based on user selection. 22 | 23 | 24 | ```PowerShell 25 | Invoke-FuzzyZLocation 26 | ``` 27 | 28 | ## PARAMETERS 29 | 30 | ### CommonParameters 31 | This cmdlet does not support common parameters. 32 | ## INPUTS 33 | 34 | ### None 35 | This cmdlet does not accept any input. 36 | ## OUTPUTS 37 | 38 | ### None 39 | This cmdlet does not generate any output. 40 | ## NOTES 41 | This function will be created if [ZLocation](https://github.com/vors/ZLocation) can be found by PowerShell. 42 | ## RELATED LINKS 43 | 44 | -------------------------------------------------------------------------------- /docs/Invoke-Fzf.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PsFzf.psm1-help.xml 3 | online version: 4 | schema: 2.0.0 5 | --- 6 | 7 | # Invoke-Fzf 8 | 9 | ## SYNOPSIS 10 | Starts the fuzzy file finder based on input from the pipeline. 11 | ## SYNTAX 12 | 13 | ```PowerShell 14 | Invoke-Fzf [-Extended] [-ExtendedExact] [-CaseInsensitive] [-CaseSensitive] [[-Delimiter] ] [-NoSort] 15 | [-ReverseInput] [[-Tiebreak] ] [-Multi] [-NoMouse] [-Cycle] [-NoHScroll] [-Reverse] [-InlineInfo] 16 | [[-Prompt] ] [[-Header] ] [[-HeaderLines] ] [[-History] ] 17 | [[-HistorySize] ] [[-Preview] ] [[-PreviewWindow] ] [[-Query] ] [-Select1] 18 | [-Exit0] [[-Filter] ] [[-Input] ] 19 | ``` 20 | 21 | ## DESCRIPTION 22 | The Add-Frecent function adds a path to the Fasdr database for the passed in provider. 23 | 24 | ## EXAMPLES 25 | 26 | ### Example 1 27 | ```PowerShell 28 | PS C:\> Set-Location (Invoke-Fzf) 29 | ``` 30 | 31 | Sets the current location based on the user selection in fzf. 32 | 33 | ## PARAMETERS 34 | 35 | ### -CaseInsensitive 36 | Case-insensitive match (default: smart-case match) 37 | 38 | ```yaml 39 | Type: SwitchParameter 40 | Parameter Sets: (All) 41 | Aliases: i 42 | 43 | Required: False 44 | Position: Named 45 | Default value: None 46 | Accept pipeline input: False 47 | Accept wildcard characters: False 48 | ``` 49 | 50 | ### -CaseSensitive 51 | Case-sensitive match 52 | 53 | ```yaml 54 | Type: SwitchParameter 55 | Parameter Sets: (All) 56 | Aliases: 57 | 58 | Required: False 59 | Position: Named 60 | Default value: None 61 | Accept pipeline input: False 62 | Accept wildcard characters: False 63 | ``` 64 | 65 | ### -Cycle 66 | Enable cyclic scroll 67 | 68 | ```yaml 69 | Type: SwitchParameter 70 | Parameter Sets: (All) 71 | Aliases: 72 | 73 | Required: False 74 | Position: Named 75 | Default value: None 76 | Accept pipeline input: False 77 | Accept wildcard characters: False 78 | ``` 79 | 80 | ### -Delimiter 81 | Field delimiter regex (default: AWK-style) 82 | 83 | ```yaml 84 | Type: String 85 | Parameter Sets: (All) 86 | Aliases: d 87 | 88 | Required: False 89 | Position: 0 90 | Default value: None 91 | Accept pipeline input: False 92 | Accept wildcard characters: False 93 | ``` 94 | 95 | ### -Exit0 96 | Exit immediately when there's no match 97 | 98 | ```yaml 99 | Type: SwitchParameter 100 | Parameter Sets: (All) 101 | Aliases: e0 102 | 103 | Required: False 104 | Position: Named 105 | Default value: None 106 | Accept pipeline input: False 107 | Accept wildcard characters: False 108 | ``` 109 | 110 | ### -Extended 111 | Extended-search mode (enabled by default; +x or --no-extended to disable) 112 | 113 | ```yaml 114 | Type: SwitchParameter 115 | Parameter Sets: (All) 116 | Aliases: x 117 | 118 | Required: False 119 | Position: Named 120 | Default value: None 121 | Accept pipeline input: False 122 | Accept wildcard characters: False 123 | ``` 124 | 125 | ### -Exact 126 | Enable Exact-match 127 | 128 | ```yaml 129 | Type: SwitchParameter 130 | Parameter Sets: (All) 131 | Aliases: e 132 | 133 | Required: False 134 | Position: Named 135 | Default value: None 136 | Accept pipeline input: False 137 | Accept wildcard characters: False 138 | ``` 139 | 140 | ### -Filter 141 | Filter mode. Do not start interactive finder. 142 | 143 | ```yaml 144 | Type: String 145 | Parameter Sets: (All) 146 | Aliases: f 147 | 148 | Required: False 149 | Position: 10 150 | Default value: None 151 | Accept pipeline input: False 152 | Accept wildcard characters: False 153 | ``` 154 | 155 | ### -Header 156 | String to print as header 157 | 158 | ```yaml 159 | Type: String 160 | Parameter Sets: (All) 161 | Aliases: 162 | 163 | Required: False 164 | Position: 3 165 | Default value: None 166 | Accept pipeline input: False 167 | Accept wildcard characters: False 168 | ``` 169 | 170 | ### -HeaderLines 171 | The first N lines of the input are treated as header 172 | 173 | ```yaml 174 | Type: Int32 175 | Parameter Sets: (All) 176 | Aliases: 177 | 178 | Required: False 179 | Position: 4 180 | Default value: None 181 | Accept pipeline input: False 182 | Accept wildcard characters: False 183 | ``` 184 | 185 | ### -History 186 | History file 187 | 188 | ```yaml 189 | Type: String 190 | Parameter Sets: (All) 191 | Aliases: 192 | 193 | Required: False 194 | Position: 5 195 | Default value: None 196 | Accept pipeline input: False 197 | Accept wildcard characters: False 198 | ``` 199 | 200 | ### -HistorySize 201 | Maximum number of history entries (default: 1000) 202 | 203 | ```yaml 204 | Type: Int32 205 | Parameter Sets: (All) 206 | Aliases: 207 | 208 | Required: False 209 | Position: 6 210 | Default value: None 211 | Accept pipeline input: False 212 | Accept wildcard characters: False 213 | ``` 214 | 215 | ### -InlineInfo 216 | Display finder info inline with the query 217 | 218 | ```yaml 219 | Type: SwitchParameter 220 | Parameter Sets: (All) 221 | Aliases: 222 | 223 | Required: False 224 | Position: Named 225 | Default value: None 226 | Accept pipeline input: False 227 | Accept wildcard characters: False 228 | ``` 229 | 230 | ### -Input 231 | The input to display in the interactive finder 232 | 233 | ```yaml 234 | Type: Object[] 235 | Parameter Sets: (All) 236 | Aliases: 237 | 238 | Required: False 239 | Position: 11 240 | Default value: None 241 | Accept pipeline input: True (ByValue) 242 | Accept wildcard characters: False 243 | ``` 244 | 245 | ### -Multi 246 | Enable multi-select with tab/shift-tab 247 | 248 | ```yaml 249 | Type: SwitchParameter 250 | Parameter Sets: (All) 251 | Aliases: m 252 | 253 | Required: False 254 | Position: Named 255 | Default value: None 256 | Accept pipeline input: False 257 | Accept wildcard characters: False 258 | ``` 259 | 260 | ### -NoHScroll 261 | Disable horizontal scroll 262 | 263 | ```yaml 264 | Type: SwitchParameter 265 | Parameter Sets: (All) 266 | Aliases: 267 | 268 | Required: False 269 | Position: Named 270 | Default value: None 271 | Accept pipeline input: False 272 | Accept wildcard characters: False 273 | ``` 274 | 275 | ### -NoMouse 276 | Disable mouse 277 | 278 | ```yaml 279 | Type: SwitchParameter 280 | Parameter Sets: (All) 281 | Aliases: 282 | 283 | Required: False 284 | Position: Named 285 | Default value: None 286 | Accept pipeline input: False 287 | Accept wildcard characters: False 288 | ``` 289 | 290 | ### -NoSort 291 | Do not sort the result 292 | 293 | ```yaml 294 | Type: SwitchParameter 295 | Parameter Sets: (All) 296 | Aliases: 297 | 298 | Required: False 299 | Position: Named 300 | Default value: None 301 | Accept pipeline input: False 302 | Accept wildcard characters: False 303 | ``` 304 | 305 | ### -Preview 306 | Command to preview highlighted line ({}) 307 | 308 | ```yaml 309 | Type: String 310 | Parameter Sets: (All) 311 | Aliases: 312 | 313 | Required: False 314 | Position: 7 315 | Default value: None 316 | Accept pipeline input: False 317 | Accept wildcard characters: False 318 | ``` 319 | 320 | ### -PreviewWindow 321 | Preview window layout (default: right:50%) [up|down|left|right][:SIZE[%]][:hidden] 322 | 323 | ```yaml 324 | Type: String 325 | Parameter Sets: (All) 326 | Aliases: 327 | 328 | Required: False 329 | Position: 8 330 | Default value: None 331 | Accept pipeline input: False 332 | Accept wildcard characters: False 333 | ``` 334 | 335 | ### -Prompt 336 | Input prompt (default: '> ') 337 | 338 | ```yaml 339 | Type: String 340 | Parameter Sets: (All) 341 | Aliases: 342 | 343 | Required: False 344 | Position: 2 345 | Default value: None 346 | Accept pipeline input: False 347 | Accept wildcard characters: False 348 | ``` 349 | 350 | ### -Query 351 | Start the finder with the given query 352 | 353 | ```yaml 354 | Type: String 355 | Parameter Sets: (All) 356 | Aliases: q 357 | 358 | Required: False 359 | Position: 9 360 | Default value: None 361 | Accept pipeline input: False 362 | Accept wildcard characters: False 363 | ``` 364 | 365 | ### -Reverse 366 | Reverse orientation 367 | 368 | ```yaml 369 | Type: SwitchParameter 370 | Parameter Sets: (All) 371 | Aliases: 372 | 373 | Required: False 374 | Position: Named 375 | Default value: None 376 | Accept pipeline input: False 377 | Accept wildcard characters: False 378 | ``` 379 | 380 | ### -ReverseInput 381 | Reverse the order of the input 382 | 383 | ```yaml 384 | Type: SwitchParameter 385 | Parameter Sets: (All) 386 | Aliases: tac 387 | 388 | Required: False 389 | Position: Named 390 | Default value: None 391 | Accept pipeline input: False 392 | Accept wildcard characters: False 393 | ``` 394 | 395 | ### -Select1 396 | Automatically select the only match 397 | 398 | ```yaml 399 | Type: SwitchParameter 400 | Parameter Sets: (All) 401 | Aliases: s1 402 | 403 | Required: False 404 | Position: Named 405 | Default value: None 406 | Accept pipeline input: False 407 | Accept wildcard characters: False 408 | ``` 409 | 410 | ### -Tiebreak 411 | Comma-separated list of sort criteria to apply when the scores are tied [length|begin|end|index] (default: length) 412 | 413 | ```yaml 414 | Type: String 415 | Parameter Sets: (All) 416 | Aliases: 417 | Accepted values: length, begin, end, index 418 | 419 | Required: False 420 | Position: 1 421 | Default value: None 422 | Accept pipeline input: False 423 | Accept wildcard characters: False 424 | ``` 425 | 426 | ## INPUTS 427 | 428 | ### System.Object[] 429 | 430 | Objects to display in the interactive finder 431 | 432 | 433 | ## OUTPUTS 434 | 435 | ### System.Object[] 436 | 437 | Objects selected by the user 438 | 439 | ## NOTES 440 | 441 | ## RELATED LINKS 442 | 443 | -------------------------------------------------------------------------------- /docs/Invoke-PsFzfRipgrep.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Invoke-PsFzfRipgrep 7 | ## SYNOPSIS 8 | Uses fzf as an interactive Ripgrep launcher 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Invoke-PsFzfRipgrep -SearchString [-NoEditor] 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Uses Ripgrep and Fzf to interactively search files. 17 | ## EXAMPLES 18 | 19 | ### Launch Invoke-PsFzfRipgrep with search string 20 | 21 | ```PowerShell 22 | Invoke-PsFzfRipgrep -SearchString 'Key' # Starts search with initial ripgrep query of the string 'Key' 23 | ``` 24 | 25 | ## PARAMETERS 26 | ### -SearchString 27 | Initial string to start ripgrep query 28 | 29 | ```yaml 30 | Type: String 31 | Parameter Sets: (All) 32 | Aliases: 33 | 34 | Required: True 35 | Position: 0 36 | Default value: None 37 | Accept pipeline input: False 38 | Accept wildcard characters: False 39 | ``` 40 | ### -NoEditor 41 | Returns result instead of launching editor 42 | 43 | ```yaml 44 | Type: Switch 45 | Parameter Sets: (All) 46 | Aliases: 47 | 48 | Required: False 49 | Position: 1 50 | Default value: False 51 | Accept pipeline input: False 52 | Accept wildcard characters: False 53 | ``` 54 | 55 | ### CommonParameters 56 | This cmdlet does not support common parameters. 57 | ## INPUTS 58 | 59 | ### None 60 | This cmdlet does not accept any input. 61 | ## OUTPUTS 62 | 63 | ### None 64 | This cmdlet does not generate any output. 65 | ## NOTES 66 | This function is adapted from [Fzf's advanced document](https://github.com/junegunn/fzf/blob/master/ADVANCED.md#switching-between-ripgrep-mode-and-fzf-mode). 67 | This function requires the installation of [ripgrep](https://github.com/BurntSushi/ripgrep) and [bat](https://github.com/sharkdp/bat). 68 | ## RELATED LINKS 69 | 70 | -------------------------------------------------------------------------------- /docs/PSFzfExample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelleyma49/PSFzf/9e8596ff047e32c43c1fa2d79ab7a5a9d8c350bb/docs/PSFzfExample.gif -------------------------------------------------------------------------------- /docs/Set-LocationFuzzyEverything.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Set-LocationFuzzyEverything 7 | ## SYNOPSIS 8 | Sets the current location based on the Everything database. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Set-LocationFuzzyEverything [-Directory ] 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Allows the user to select a directory from the Everything database and sets the current location. 17 | ## EXAMPLES 18 | 19 | ### Launch Set-LocationFuzzyEverything 20 | 21 | Launches fzf and sets the current location based on the user selection. 22 | 23 | 24 | ```PowerShell 25 | Set-LocationFuzzyEverything 26 | ``` 27 | 28 | ### Launch Set-LocationFuzzyEverything specifying a path filter 29 | 30 | Launches fzf for all subdirectories of c:\Windows and sets the current location based on the user selection. 31 | 32 | 33 | ```PowerShell 34 | Set-LocationFuzzyEverything c:\Windows 35 | ``` 36 | 37 | ## PARAMETERS 38 | ### -Directory 39 | The path to a directory that contains the subdirectories that the user will choose from. 40 | 41 | ```yaml 42 | Type: String 43 | Parameter Sets: (All) 44 | Aliases: 45 | 46 | Required: False 47 | Position: 0 48 | Default value: None 49 | Accept pipeline input: False 50 | Accept wildcard characters: False 51 | ``` 52 | 53 | ### CommonParameters 54 | This cmdlet does not support common parameters. 55 | ## INPUTS 56 | 57 | ### None 58 | This cmdlet does not accept any input. 59 | ## OUTPUTS 60 | 61 | ### None 62 | This cmdlet does not generate any output. 63 | ## NOTES 64 | This function will be created if [PSEverything](https://github.com/powercode/PSEverything) can be found by PowerShell. 65 | ## RELATED LINKS 66 | 67 | -------------------------------------------------------------------------------- /docs/Set-PsFzfOption.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSFzf.psm1-help.xml 3 | schema: 2.0.0 4 | --- 5 | 6 | # Set-PsFzfOption 7 | ## SYNOPSIS 8 | Sets the available PSFzf options. 9 | ## SYNTAX 10 | 11 | ```PowerShell 12 | Set-PsFzfOption 13 | ``` 14 | 15 | ## DESCRIPTION 16 | Allows the user to set various PSFzf options, such as PSReadline chords and tab expansion. 17 | ## EXAMPLES 18 | 19 | ### Set PSReadline Options 20 | 21 | Set PsFzf's history and file finder keyboard shortcuts. 22 | 23 | 24 | ```PowerShell 25 | Set-PsFzfOption -PSReadlineChordProvider 'Ctrl+t' -PSReadlineChordReverseHistory 'Ctrl+r' 26 | ``` 27 | 28 | ## PARAMETERS 29 | 30 | ### CommonParameters 31 | This cmdlet does not support common parameters. 32 | 33 | ### -PSReadlineChordProvider 34 | PSReadline keyboard chord shortcut to trigger file and directory selection 35 | 36 | ```yaml 37 | Type: String 38 | Parameter Sets: (All) 39 | Aliases: 40 | 41 | Required: False 42 | Position: Named 43 | Default value: None 44 | Accept pipeline input: False 45 | Accept wildcard characters: False 46 | ``` 47 | ### -PSReadlineChordReverseHistory 48 | PSReadline keyboard chord shortcut to trigger history selection 49 | 50 | ```yaml 51 | Type: String 52 | Parameter Sets: (All) 53 | Aliases: 54 | 55 | Required: False 56 | Position: Named 57 | Default value: None 58 | Accept pipeline input: False 59 | Accept wildcard characters: False 60 | ``` 61 | ### -GitKeyBindings 62 | Enables key bindings for git commands. 63 | 64 | ```yaml 65 | Type: SwitchParameter 66 | Parameter Sets: (All) 67 | Aliases: 68 | 69 | Required: False 70 | Position: Named 71 | Default value: None 72 | Accept pipeline input: False 73 | Accept wildcard characters: False 74 | ``` 75 | 76 | ### -TabExpansion 77 | Enables tab expansion support 78 | 79 | ```yaml 80 | Type: SwitchParameter 81 | Parameter Sets: (All) 82 | Aliases: 83 | 84 | Required: False 85 | Position: Named 86 | Default value: None 87 | Accept pipeline input: False 88 | Accept wildcard characters: False 89 | ``` 90 | 91 | ### -EnableAliasFuzzyEdit 92 | Enables the `fe` aliases for the `Invoke-FuzzyEdit` function 93 | 94 | ```yaml 95 | Type: SwitchParameter 96 | Parameter Sets: (All) 97 | Aliases: 98 | 99 | Required: False 100 | Position: Named 101 | Default value: None 102 | Accept pipeline input: False 103 | Accept wildcard characters: False 104 | ``` 105 | 106 | ### -EnableAliasFuzzyFasd 107 | Enables the `ff` aliases for the `Invoke-FuzzyFasd` function 108 | ```yaml 109 | Type: SwitchParameter 110 | Parameter Sets: (All) 111 | Aliases: 112 | 113 | Required: False 114 | Position: Named 115 | Default value: None 116 | Accept pipeline input: False 117 | Accept wildcard characters: False 118 | ``` 119 | 120 | ### -EnableAliasFuzzyHistory 121 | Enables the `fh` aliases for the `Invoke-FuzzyHistory` function 122 | ```yaml 123 | Type: SwitchParameter 124 | Parameter Sets: (All) 125 | Aliases: 126 | 127 | Required: False 128 | Position: Named 129 | Default value: None 130 | Accept pipeline input: False 131 | Accept wildcard characters: False 132 | ``` 133 | 134 | ### -EnableAliasFuzzyKillProcess 135 | Enables the `fkill` aliases for the `Invoke-FuzzyKillProcess` function 136 | ```yaml 137 | Type: SwitchParameter 138 | Parameter Sets: (All) 139 | Aliases: 140 | 141 | Required: False 142 | Position: Named 143 | Default value: None 144 | Accept pipeline input: False 145 | Accept wildcard characters: False 146 | ``` 147 | 148 | ### -EnableAliasFuzzySetLocation 149 | Enables the `fd` aliases for the `Invoke-FuzzySetLocation` function 150 | ```yaml 151 | Type: SwitchParameter 152 | Parameter Sets: (All) 153 | Aliases: 154 | 155 | Required: False 156 | Position: Named 157 | Default value: None 158 | Accept pipeline input: False 159 | Accept wildcard characters: False 160 | ``` 161 | 162 | ### -EnableAliasFuzzySetEverything 163 | Enables the `cde` aliases for the `Set-LocationFuzzyEverything` function 164 | ```yaml 165 | Type: SwitchParameter 166 | Parameter Sets: (All) 167 | Aliases: 168 | 169 | Required: False 170 | Position: Named 171 | Default value: None 172 | Accept pipeline input: False 173 | Accept wildcard characters: False 174 | ``` 175 | 176 | ### -EnableAliasFuzzyScoop 177 | Enables the `fs` aliases for the `Invoke-FuzzyScoop` function 178 | ```yaml 179 | Type: SwitchParameter 180 | Parameter Sets: (All) 181 | Aliases: 182 | 183 | Required: False 184 | Position: Named 185 | Default value: None 186 | Accept pipeline input: False 187 | Accept wildcard characters: False 188 | ``` 189 | 190 | ### -EnableAliasFuzzyZLocation 191 | Enables the `fz` aliases for the `Invoke-FuzzySetZLocation` function 192 | ```yaml 193 | Type: SwitchParameter 194 | Parameter Sets: (All) 195 | Aliases: 196 | 197 | Required: False 198 | Position: Named 199 | Default value: None 200 | Accept pipeline input: False 201 | Accept wildcard characters: False 202 | ``` 203 | 204 | ### -EnableAliasFuzzyGitStatus 205 | Enables the `fgs` aliases for the `Invoke-FuzzyGitStatus` function 206 | ```yaml 207 | Type: SwitchParameter 208 | Parameter Sets: (All) 209 | Aliases: 210 | 211 | Required: False 212 | Position: Named 213 | Default value: None 214 | Accept pipeline input: False 215 | Accept wildcard characters: False 216 | ``` 217 | 218 | ### -EnableFd 219 | uses the `fd` command instead of the OS specific file and directory commands 220 | ```yaml 221 | Type: SwitchParameter 222 | Parameter Sets: (All) 223 | Aliases: 224 | 225 | Required: False 226 | Position: Named 227 | Default value: None 228 | Accept pipeline input: False 229 | Accept wildcard characters: False 230 | ``` 231 | 232 | ### -AltCCommand 233 | Specifies a user supplied command that will be used in the command that is bound to the Alt-C command 234 | 235 | ```powershell 236 | # example command - use $Location with a different command: 237 | $commandOverride = [ScriptBlock]{ param($Location) Write-Host $Location } 238 | # pass your override to PSFzf: 239 | Set-PsFzfOption -AltCCommand $commandOverride 240 | ``` 241 | ```yaml 242 | Type: String 243 | Parameter Sets: (All) 244 | Aliases: 245 | 246 | Required: False 247 | Position: Named 248 | Default value: None 249 | Accept pipeline input: False 250 | Accept wildcard characters: False 251 | ``` 252 | ## INPUTS 253 | ### None 254 | 255 | ## OUTPUTS 256 | 257 | ### None 258 | This cmdlet does not generate any output. 259 | -------------------------------------------------------------------------------- /en-US/about_PSFzf.help.txt: -------------------------------------------------------------------------------- 1 | TOPIC 2 | about_PSFzf 3 | 4 | SHORT DESCRIPTION 5 | 6 | PSFzf is a wrapper around the fuzzy finder fzf. 7 | 8 | LONG DESCRIPTION 9 | 10 | PSFzf is a wrapper around the fuzzy finder fzf. It provides: 11 | 12 | * Integration with PSReadline - press Ctrl+t to start fzf 13 | * Convenience function for selecting a file to edit (Invoke-FuzzyEdit, or fe) 14 | * Convenience function for selecting a frecent file use Fasdr (Invoke-FuzzyFasd, or ff) 15 | * Convenience function for selecting a recent directory using ZLocation (Invoke-FuzzyZLocation, or fz) 16 | * Convenience function for selecting files from git status (Invoke-FuzzyGitStatus, or fgs) 17 | * Convenience function for invoking previous history (Invoke-FuzzyHistory, or fh) 18 | * Convenience function for killing processes (Invoke-FuzzyKillProcess, or fkill) 19 | * Convenience function for setting the current location (Invoke-FuzzySetLocation, or fd) 20 | * Convenience function for selecting Scoop applications (Invoke-FuzzyScoop, or fs) 21 | * Convenience function for selecting a file or folder from Everything (Set-LocationFuzzyEverything, or cde) 22 | 23 | Basic Usage 24 | ----------- 25 | Invoke-PSFzf reads input from the pipeline. For example, the following command allows the user to 26 | to select a file from the interface and set the current location to that folder: 27 | 28 | cd (gci -Recurse | where {$_.PSIsContainer} | Invoke-Fzf) 29 | 30 | If Invoke-PSFzf is passed an object, it attempts to find a common property to display, like Name or FullName. 31 | For example, these will display just the name rather than the string representation of the object: 32 | 33 | Get-Process | Invoke-Fzf 34 | gci alias: | Invoke-Fzf 35 | 36 | POWERSHELL COMPATIBILITY 37 | 38 | Compatibility information can be found here: https://github.com/kelleyma49/PSFzf/blob/master/README.md#prerequisites. 39 | 40 | FEEDBACK 41 | 42 | https://github.com/kelleyma49/PSFzf 43 | 44 | CONTRIBUTING TO PSFZF 45 | 46 | Feel free to submit a pull request or submit feedback on the github page. 47 | -------------------------------------------------------------------------------- /helpers/GetProcessesList.ps1: -------------------------------------------------------------------------------- 1 | function GetProcessesList() { 2 | Get-Process | ` 3 | Where-Object { ![string]::IsNullOrEmpty($_.ProcessName) } | ` 4 | ForEach-Object { 5 | $pmSize = $_.PM / 1MB 6 | $cpu = $_.CPU 7 | # make sure we display a value so we can correctly parse selections: 8 | if ($null -eq $cpu) { 9 | $cpu = 0.0 10 | } 11 | "{0,-8:n2} {1,-8:n2} {2,-8} {3}" -f $pmSize, $cpu, $_.Id, $_.ProcessName } 12 | } 13 | 14 | GetProcessesList -------------------------------------------------------------------------------- /helpers/Join-ModuleFiles.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | $scriptPath = Join-Path $PSScriptRoot '../PSFzf.psm1' 4 | Set-Content -Path $scriptPath -Value ('# AUTOGENERATED - DO NOT EDIT' + "`n") 5 | Get-ChildItem (Join-Path $PSScriptRoot '../PSFzf.*.ps1') | ForEach-Object { 6 | $name = $_.Name 7 | if ($name -ne 'PSFzf.tests.ps1') { 8 | Write-Host "Adding $name" 9 | Add-Content -Path $scriptPath -Value "# $name" 10 | Get-Content -Path $_ | Add-Content -Path $scriptPath 11 | } 12 | } 13 | $scriptPath = Resolve-Path $scriptPath 14 | Write-Host "Created $scriptPath" -------------------------------------------------------------------------------- /helpers/PSFzfGitBranches.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [switch]$AllBranches, 3 | [switch]$Branches 4 | ) 5 | function branches() { 6 | param($All = "") 7 | $all = git branch --sort=committerdate --sort=HEAD --format='%(HEAD) %(color:yellow)%(refname:short) %(color:green)(%(committerdate:relative))\t%(color:blue)%(subject)%(color:reset)' --color=always | ` 8 | ForEach-Object { 9 | $split = $_.Split("\t"); 10 | [PSCustomObject]@{ 11 | branch = $split[0] 12 | info = $split[1] 13 | } 14 | } 15 | $PSStyle.OutputRendering = "ANSI" 16 | $all | format-table -HideTableHeaders | Out-String 17 | $all | Out-File ~/crap-ps.txt -Append 18 | } 19 | 20 | if ($AllBranches) { 21 | "CTRL-A (show all branches)`n" 22 | branches -All "-a" 23 | } 24 | elseif ($Branches) { 25 | "`n" 26 | branches 27 | } -------------------------------------------------------------------------------- /helpers/PsFzfGitBranches-Preview.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | git log --oneline --graph --date=short --color=always --pretty='format:%C(auto)%cd %h%d %s' $(cut -c1- <<< $1 | cut -d' ' -f1) -- 3 | -------------------------------------------------------------------------------- /helpers/PsFzfGitBranches.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | branches() { 4 | # TODO: the branch name is intentionally not colored as we can't figure out how to remove VT codes from the output 5 | git branch "$@" --sort=committerdate --sort=HEAD --format=$'%(color:yellow)%(refname:short) %(color:green)(%(committerdate:relative))\t%(color:blue)%(subject)%(color:reset)' --color=always | column -ts$'\t' 6 | } 7 | case "$1" in 8 | branches) 9 | echo $'CTRL-A (show all branches)\n' 10 | branches 11 | ;; 12 | all-branches) 13 | echo $'\n' 14 | branches -a 15 | ;; 16 | *) exit 1 ;; 17 | esac 18 | -------------------------------------------------------------------------------- /helpers/PsFzfGitFiles-GitAdd.sh: -------------------------------------------------------------------------------- 1 | git add -- "$@" 2 | -------------------------------------------------------------------------------- /helpers/PsFzfGitFiles-GitReset.sh: -------------------------------------------------------------------------------- 1 | git reset -- "$@" 2 | -------------------------------------------------------------------------------- /helpers/PsFzfGitFiles-Preview.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | args=$2 3 | dir="${3//\\//}" 4 | file=$1 5 | 6 | pushd . > /dev/null 7 | cd "$dir" 8 | if [ ! -e "$file" ]; then 9 | echo "$file deleted" 10 | elif git ls-files --error-unmatch "$1" > /dev/null 2>&1; then 11 | git diff --no-ext-diff $args HEAD -- $file | head -500 12 | else 13 | echo "$file added" 14 | fi 15 | popd > /dev/null 16 | -------------------------------------------------------------------------------- /helpers/PsFzfGitHashes-Preview.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | args=$2 3 | dir="${3//\\//}" 4 | hash=$1 5 | 6 | pushd . > /dev/null 7 | cd "$dir" 8 | echo $hash | grep -o "[a-f0-9]\{7,\}" -m 1 | head -1 | xargs git show $args 9 | popd > /dev/null 10 | -------------------------------------------------------------------------------- /helpers/PsFzfTabExpansion-Parameter.ps1: -------------------------------------------------------------------------------- 1 | # Can't use named parameters 2 | $command = $args[0] 3 | $parameter = $args[1] 4 | $parameter = $parameter.replace('-', '') 5 | 6 | $RunningInWindowsTerminal = [bool]($env:WT_Session) 7 | $IsWindowsCheck = ($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows 8 | $ansiCompatible = $script:RunningInWindowsTerminal -or (-not $script:IsWindowsCheck) 9 | 10 | if ([System.Management.Automation.Cmdlet]::CommonParameters.Contains($parameter)) { 11 | $tempFile = New-TemporaryFile 12 | Get-Help about_CommonParameters | out-file $tempFile 13 | $found = Get-Content $tempFile | select-string ('^' + $parameter + '$') -Context 0, 20 -AllMatches:$false 14 | if ($null -ne $found) { 15 | Write-Output $found[0] | ForEach-Object { $_ -replace ("^> $parameter", "`n-$parameter (common parameter)") } 16 | } 17 | Remove-Item $tempFile -ErrorAction SilentlyContinue 18 | } 19 | else { 20 | if ($ansiCompatible -and $(Get-Command bat -ErrorAction Ignore)) { 21 | Get-Help -Name $Command -Parameter $parameter | bat --no-config --language=man --color always --style=plain 22 | } 23 | else { 24 | Get-Help -Name $Command -Parameter $parameter 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /helpers/PsFzfTabExpansion-Preview.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ($DirName, $Item) 3 | 4 | # trim quote strings: 5 | $DirName = $DirName.Trim("'").Trim('"') 6 | $Item = $Item.Trim("'").Trim('"') 7 | 8 | $RunningInWindowsTerminal = [bool]($env:WT_Session) 9 | $IsWindowsCheck = ($PSVersionTable.PSVersion.Major -le 5) -or $IsWindows 10 | $ansiCompatible = $script:RunningInWindowsTerminal -or (-not $script:IsWindowsCheck) 11 | 12 | if ([System.IO.Path]::IsPathRooted($Item)) { 13 | $path = $Item 14 | } 15 | else { 16 | $path = Join-Path $DirName $Item 17 | } 18 | # is directory? 19 | if (Test-Path $path -PathType Container) { 20 | Get-ChildItem $path 21 | 22 | if (Get-Command git -ErrorAction Ignore) { 23 | Write-Output "" # extra separator before git status 24 | Push-Location $path 25 | if ($ansiCompatible -and $(Get-Command bat -ErrorAction Ignore)) { 26 | git log -1 2> $null | bat --no-config "--style=changes" --color always 27 | } 28 | else { 29 | git log -1 2> $null 30 | } 31 | Pop-Location 32 | } 33 | } 34 | # is file? 35 | elseif (Test-Path $path -PathType leaf) { 36 | # use bat (https://github.com/sharkdp/bat) if it's available: 37 | if ($ansiCompatible -and $(Get-Command bat -ErrorAction Ignore)) { 38 | bat --no-config "--style=numbers,changes" --color always $path 39 | } 40 | else { 41 | Get-Content $path 42 | } 43 | } 44 | # PowerShell command? 45 | elseif (($cmdResults = Get-Command $Item -ErrorAction Ignore)) { 46 | if ($cmdResults) { 47 | if ($cmdResults.CommandType -ne 'Application') { 48 | if ($ansiCompatible -and $(Get-Command bat -ErrorAction Ignore)) { 49 | Get-Help $Item | bat --no-config --language=man --color always --style=plain 50 | } 51 | else { 52 | 53 | } 54 | 55 | } 56 | else { 57 | # just output application location: 58 | $cmdResults.Source 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------