PowerShell からも peco で日本語を扱えるようになった ので、コマンドラインから使える簡単なランチャを作ってみようと思います。
環境は以下の通り。
Name Value
---- -----
PSVersion 7.0.2
PSEdition Core
GitCommitId 7.0.2
OS Microsoft Windows 10.0.18362
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
このような動作をイメージしています。
(1) よくアクセスするフォルダ/ファイルのパスをテキストファイルに列挙しておく
(2) 読み込んだファイルを peco でフィルタリング
・[ファイルを選んだ場合]→そのまま開く
・[フォルダを選んだ場合]→サブフォルダを再度 peco でフィルタリングして開く
実際にコードを書いていきます。まずは peco のラッパーから。
上記の記事から PowerShell 7 にバージョンを上げたことで $OutputEncoding
のデフォルト値が utf-8 となり少し記述が短くなっています。
function psPeco {
$origin = [System.Console]::OutputEncoding
$utf8 = [System.Text.Encoding]::UTF8
[System.Console]::OutputEncoding = $utf8
$out = $input | Out-String -Stream | peco.exe "--initial-filter=Fuzzy"
[System.Console]::OutputEncoding = $origin
return $out
}
次はテキストファイルの読み込み部分。C:\Personal
に launch.txt
を作って改行区切りでパスを書いていきます(文字コードは BOM なし utf-8 )。
パスをそのままフィルタリングするとフルパスにもマッチしてしまうので少し工夫しました。
- テキストファイルの各行からフィルタリング用の「表示名」を生成する
- 表示名はパスの後ろに
|
で区切って指定 - 指定しなかった場合はパス最下層を表示名とする
- 表示名はパスの後ろに
- 表示名をキーとしたハッシュテーブルを操作するようにする
- キーは文字数順でソートしておく(マッチを楽にするため)
- 存在しないパスは除外しておく
function New-LaunchHashTable {
$objArray = New-Object System.Collections.ArrayList
Get-Content "C:\Personal\launch.txt" -Encoding utf8 | ForEach-Object {
if ($_ -match "\|") {
$pair = @($_ -split "\|")
$fullPath = $pair[0]
$displayName = $pair[1]
}
else {
$fullPath = $_
$displayName = $fullPath | Split-Path -Leaf
}
$objArray.Add([PSCustomObject]@{
Name = $displayName
Fullname = $fullPath
}) > $null
}
$hashTable = [ordered]@{}
$objArray | Where-Object {Test-Path $_.Fullname} | Sort-Object {($_.Name).Length} | ForEach-Object {
$hashTable[$_.Name] = $_.Fullname
}
return $hashTable
}
launch.txt
を下記のように指定すると……
C:\Personal\tools
C:\Personal\tools\mytool
C:\Personal\tools\memo.txt
C:\Personal\work\memo.txt|work_memo
Name
と表示されているのに .Keys
でキーを取得できるのはツッコミを受けそうですが仕様です。
最後に、実際のフィルタリング処理です。
サブフォルダの検索で *
を候補として表示させているのは、テキストファイルの候補から絞り込んだフォルダそのものを開けるようにするためです。サブフォルダが多すぎる場合は Get-ChildItem
の -Depth
で検索の深さを指定してやると速くなります。
function Invoke-FuzzyLauncher {
$launchHashTable = New-LaunchHashTable
$selected = $launchHashTable.Keys | psPeco -fuzzy | Select-Object -First 1
if (-not $selected) {
return
}
$rootDir = $launchHashTable[$selected]
if (Test-Path $rootDir -PathType Leaf) {
$targetPath = $rootDir
}
else {
$subDir = @(Get-ChildItem $rootDir -Directory -Name)
if (-not $subDir) {
$targetPath = $rootDir
}
else {
$sorted = @("*") + @($subDir | Sort-Object Length)
$leafPath = $sorted | psPeco -fuzzy
if (-not $leafPath) {
return
}
elseif ($leafPath -eq "*") {
$targetPath = $rootDir
}
else {
$targetPath = $rootDir | Join-Path -ChildPath $leafPath
}
}
}
Invoke-Item $targetPath
}
以上の内容を $profile
に書いておくとターミナル起動時に自動で読み込まれて使用できるようなります(z
など短いエイリアスを当てておくとさらに便利)。