├── .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 | [](https://www.powershellgallery.com/packages/PSFzf)
4 | [](https://www.powershellgallery.com/packages/PSFzf)
5 | [](https://www.powershellgallery.com/packages/PSFzf)
6 | [](https://github.com/kelleyma49/PSFzf/actions)
7 | [](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 | 
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]