Skip to content

Instantly share code, notes, and snippets.

@charlesroper
Last active July 20, 2025 09:57
Show Gist options
  • Save charlesroper/90b7b3f76951a7ac24e624378700679d to your computer and use it in GitHub Desktop.
Save charlesroper/90b7b3f76951a7ac24e624378700679d to your computer and use it in GitHub Desktop.
Native tab-completion for eza on PowerShell 7+
<#
eza-completion.ps1
──────────────────
Native tab-completion for eza on PowerShell 7+
Drop this file in your $PROFILE directory and source it:
. (Join-Path (Split-Path $PROFILE -Parent) "eza-completion.ps1")
Make sure the following PSReadLine options are set:
Set-PSReadLineOption -ShowToolTips
Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete
#>
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
# Register the completer for the eza command and its aliases. Note that you have
# to include aliases because PowerShell does not automatically register them.
Register-ArgumentCompleter -Native `
-CommandName 'eza', 'eza.exe', 'ls', 'll' `
-ScriptBlock {
param(
$wordToComplete,
$commandAst,
$cursorPosition
)
# ─── one-line descriptions taken from `eza --help` ────────────────────────
$helpText = New-Object 'System.Collections.Hashtable' ([System.StringComparer]::Ordinal)
# Meta options
$helpText['-?'] = '-?, --help show list of command-line options'
$helpText['--help'] = '-?, --help show list of command-line options'
$helpText['-v'] = '-v, --version show version of eza'
$helpText['--version'] = '-v, --version show version of eza'
# Display options
$helpText['-1'] = '-1, --oneline display one entry per line'
$helpText['--oneline'] = '-1, --oneline display one entry per line'
$helpText['-l'] = '-l, --long display extended file metadata as a table'
$helpText['--long'] = '-l, --long display extended file metadata as a table'
$helpText['-G'] = '-G, --grid display entries as a grid (default)'
$helpText['--grid'] = '-G, --grid display entries as a grid (default)'
$helpText['-x'] = '-x, --across sort the grid across, rather than downwards'
$helpText['--across'] = '-x, --across sort the grid across, rather than downwards'
$helpText['-R'] = '-R, --recurse recurse into directories'
$helpText['--recurse'] = '-R, --recurse recurse into directories'
$helpText['-T'] = '-T, --tree recurse into directories as a tree'
$helpText['--tree'] = '-T, --tree recurse into directories as a tree'
$helpText['-X'] = '-X, --dereference dereference symbolic links when displaying information'
$helpText['--dereference'] = '-X, --dereference dereference symbolic links when displaying information'
$helpText['-F'] = '-F, --classify display type indicator by file names'
$helpText['--classify'] = '-F, --classify display type indicator by file names'
$helpText['--color'] = '--colo[u]r when to use terminal colours (always, auto, never)'
$helpText['--colour'] = '--colo[u]r when to use terminal colours (always, auto, never)'
$helpText['--color-scale'] = '--colo[u]r-scale highlight levels of ''field'' distinctly (all, age, size)'
$helpText['--colour-scale'] = '--colo[u]r-scale highlight levels of ''field'' distinctly (all, age, size)'
$helpText['--color-scale-mode'] = '--colo[u]r-scale-mode use gradient or fixed colors in --color-scale (fixed, gradient)'
$helpText['--colour-scale-mode'] = '--colo[u]r-scale-mode use gradient or fixed colors in --color-scale (fixed, gradient)'
$helpText['--icons'] = '--icons when to display icons (always, auto, never)'
$helpText['--no-quotes'] = '--no-quotes don''t quote file names with spaces'
$helpText['--hyperlink'] = '--hyperlink display entries as hyperlinks'
$helpText['--absolute'] = '--absolute display entries with their absolute path (on, follow, off)'
$helpText['--follow-symlinks'] = '--follow-symlinks drill down into symbolic links that point to directories'
$helpText['-w'] = '-w, --width set screen width in columns'
$helpText['--width'] = '-w, --width set screen width in columns'
# Filtering and sorting options
$helpText['-a'] = '-a, --all show hidden and ''dot'' files'
$helpText['--all'] = '-a, --all show hidden and ''dot'' files'
$helpText['-A'] = '-A, --almost-all equivalent to --all; included for compatibility with `ls -A`'
$helpText['--almost-all'] = '-A, --almost-all equivalent to --all; included for compatibility with `ls -A`'
$helpText['-d'] = '-d, --treat-dirs-as-files list directories as files; don''t list their contents'
$helpText['--treat-dirs-as-files'] = '-d, --treat-dirs-as-files list directories as files; don''t list their contents'
$helpText['-D'] = '-D, --only-dirs list only directories'
$helpText['--only-dirs'] = '-D, --only-dirs list only directories'
$helpText['-f'] = '-f, --only-files list only files'
$helpText['--only-files'] = '-f, --only-files list only files'
$helpText['--show-symlinks'] = '--show-symlinks explicitly show symbolic links'
$helpText['--no-symlinks'] = '--no-symlinks do not show symbolic links'
$helpText['-L'] = '-L, --level limit the depth of recursion'
$helpText['--level'] = '-L, --level limit the depth of recursion'
$helpText['-r'] = '-r, --reverse reverse the sort order'
$helpText['--reverse'] = '-r, --reverse reverse the sort order'
$helpText['-s'] = '-s, --sort which field to sort by'
$helpText['--sort'] = '-s, --sort which field to sort by'
$helpText['--group-directories-first'] = '--group-directories-first list directories before other files'
$helpText['--group-directories-last'] = '--group-directories-last list directories after other files'
$helpText['-I'] = '-I, --ignore-glob glob patterns (pipe-separated) of files to ignore'
$helpText['--ignore-glob'] = '-I, --ignore-glob glob patterns (pipe-separated) of files to ignore'
$helpText['--git-ignore'] = '--git-ignore ignore files mentioned in ''.gitignore'''
# Long view options
$helpText['-b'] = '-b, --binary list file sizes with binary prefixes'
$helpText['--binary'] = '-b, --binary list file sizes with binary prefixes'
$helpText['-B'] = '-B, --bytes list file sizes in bytes, without any prefixes'
$helpText['--bytes'] = '-B, --bytes list file sizes in bytes, without any prefixes'
$helpText['-g'] = '-g, --group list each file''s group'
$helpText['--group'] = '-g, --group list each file''s group'
$helpText['--smart-group'] = '--smart-group only show group if it has a different name from owner'
$helpText['-h'] = '-h, --header add a header row to each column'
$helpText['--header'] = '-h, --header add a header row to each column'
$helpText['-H'] = '-H, --links list each file''s number of hard links'
$helpText['--links'] = '-H, --links list each file''s number of hard links'
$helpText['-i'] = '-i, --inode list each file''s inode number'
$helpText['--inode'] = '-i, --inode list each file''s inode number'
$helpText['-M'] = '-M, --mounts show mount details (Linux and Mac only)'
$helpText['--mounts'] = '-M, --mounts show mount details (Linux and Mac only)'
$helpText['-n'] = '-n, --numeric list numeric user and group IDs'
$helpText['--numeric'] = '-n, --numeric list numeric user and group IDs'
$helpText['-O'] = '-O, --flags list file flags (Mac, BSD, and Windows only)'
$helpText['--flags'] = '-O, --flags list file flags (Mac, BSD, and Windows only)'
$helpText['-S'] = '-S, --blocksize show size of allocated file system blocks'
$helpText['--blocksize'] = '-S, --blocksize show size of allocated file system blocks'
$helpText['-t'] = '-t, --time which timestamp field to list (modified, accessed, created)'
$helpText['--time'] = '-t, --time which timestamp field to list (modified, accessed, created)'
$helpText['--time-style'] = '--time-style how to format timestamps (default, iso, long-iso, full-iso, relative, or custom)'
$helpText['-m'] = '-m, --modified use the modified timestamp field'
$helpText['--modified'] = '-m, --modified use the modified timestamp field'
$helpText['-u'] = '-u, --accessed use the accessed timestamp field'
$helpText['--accessed'] = '-u, --accessed use the accessed timestamp field'
$helpText['-U'] = '-U, --created use the created timestamp field'
$helpText['--created'] = '-U, --created use the created timestamp field'
$helpText['--changed'] = '--changed use the changed timestamp field'
$helpText['--total-size'] = '--total-size show the size of a directory as the size of all files and directories inside'
$helpText['--no-total-size'] = '--no-total-size do not show the total size of directories'
$helpText['--no-permissions'] = '--no-permissions suppress the permissions field'
$helpText['--no-filesize'] = '--no-filesize suppress the filesize field'
$helpText['--no-user'] = '--no-user suppress the user field'
$helpText['--no-time'] = '--no-time suppress the time field'
$helpText['--stdin'] = '--stdin read file names from stdin, one per line'
$helpText['--git'] = '--git list each file''s Git status, if tracked or ignored'
$helpText['--no-git'] = '--no-git suppress Git status (always overrides --git)'
$helpText['--git-repos'] = '--git-repos list root of git-tree status'
$helpText['--git-repos-no-status'] = '--git-repos-no-status list each git-repos branch name (much faster)'
# ─── Context-aware options ────────────────────────────────────────────────
# 'Name' and 'Extension' are for case-sensitive sorting.
$fileSortOptions = @(
'name', 'Name', 'extension', 'Extension', 'size', 'type',
'created', 'modified', 'accessed', 'changed', 'inode', 'none',
'date', 'time', 'old', 'new'
)
$timeSortOptions = @(
'modified', 'accessed', 'created', 'changed')
$timeStyles = @(
'default', 'iso', 'long-iso', 'full-iso', 'relative', 'custom'
)
$colorAndIconOptions = @(
'always', 'auto', 'never'
)
$colorScaleOptions = @(
'all', 'age', 'size'
)
$colorScaleModeOptions = @(
'fixed', 'gradient'
)
# ─── Context-aware values ─────────────────────────────────────────────────
$valueTable = New-Object 'System.Collections.Hashtable' ([System.StringComparer]::Ordinal)
$valueTable['--absolute'] = @('on', 'follow', 'off')
$valueTable['--color'] = $colorAndIconOptions
$valueTable['--colour'] = $colorAndIconOptions
$valueTable['--icons'] = $colorAndIconOptions
$valueTable['--color-scale'] = $colorScaleOptions
$valueTable['--colour-scale'] = $colorScaleOptions
$valueTable['--color-scale-mode'] = $colorScaleModeOptions
$valueTable['--colour-scale-mode'] = $colorScaleModeOptions
$valueTable['--level'] = (1..9 | ForEach-Object { "$_" })
$valueTable['--sort'] = $fileSortOptions
$valueTable['--time'] = $timeSortOptions
$valueTable['--time-style'] = $timeStyles
$valueTable['--width'] = @() # No specific values, but expects one.
$valueTable['--ignore-glob'] = @() # Expects a value.
$valueTable['-s'] = $fileSortOptions
$valueTable['-t'] = $timeSortOptions
$valueTable['-L'] = (1..9 | ForEach-Object { "$_" })
$valueTable['-w'] = @()
$valueTable['-I'] = @()
# ─── every recognised switch ──────────────────────────────────────────────
$switches = @(
# Meta options
'-?', '--help',
'-v', '--version',
# Display options
'-1', '--oneline',
'-l', '--long',
'-G', '--grid',
'-x', '--across',
'-R', '--recurse',
'-T', '--tree',
'-X', '--dereference',
'-F', '--classify',
'--no-quotes',
'--hyperlink',
'--follow-symlinks',
# Filtering and sorting options
'-a', '--all',
'-A', '--almost-all',
'-d', '--treat-dirs-as-files',
'-D', '--only-dirs',
'-f', '--only-files',
'--show-symlinks',
'--no-symlinks',
'-r', '--reverse',
'--group-directories-first',
'--group-directories-last',
'--git-ignore',
# Long view options
'-b', '--binary',
'-B', '--bytes',
'-g', '--group',
'--smart-group',
'-h', '--header',
'-H', '--links',
'-i', '--inode',
'-M', '--mounts',
'-n', '--numeric',
'-O', '--flags',
'-S', '--blocksize',
'-m', '--modified',
'-u', '--accessed',
'-U', '--created',
'--changed',
'--total-size',
'--no-total-size',
'--no-permissions',
'--no-filesize',
'--no-user',
'--no-time',
'--stdin',
'--git',
'--no-git',
'--git-repos',
'--git-repos-no-status'
)
# ──── figure out what the previous token was ──────────────────────────────
$elements = $commandAst.CommandElements
$prevToken = $null
if ($elements.Count -gt 1) {
# Get the last two elements.
$lastElement = $elements[-1]
$penultimateElement = if ($elements.Count -gt 1) { $elements[-2] } else { $null }
function Get-ElementValue($element) {
if ($null -eq $element) { return $null }
switch ($element) {
{ $_ -is [StringConstantExpressionAst] } { return $_.Value }
{ $_ -is [CommandParameterAst] } { return "-" + $_.ParameterName }
default { return $_.ToString() }
}
}
# If the cursor is at the end of the line after one or more spaces when
# <tab> is hit, then PowerShell treats this trailing whitespace as a
# StringConstantExpressionAst which has a Value property of "",
# therefore $lastElement and $wordToComplete (the token currently under
# the cursor) represent empty strings. That means we need to grab the
# penultimate element instead of the last element when hitting <tab>
# after a space.
#
# E.g., "eza -s<space><tab>" -> $lastElement.Value = ""
# $wordToComplete = ""
# $penultimateElement = "-s"
#
# Otherwise, the last element would be "-s" and so we select that.
if ($lastElement -is [StringConstantExpressionAst] -and $lastElement.Value -eq $wordToComplete) {
$prevToken = Get-ElementValue $penultimateElement
} else {
# This handles completing a value when there's no space after the parameter, e.g., "eza -s<tab>"
$prevToken = Get-ElementValue $lastElement
}
}
# ──── value completion ────────────────────────────────────────────────────
if ($prevToken -and $valueTable.ContainsKey($prevToken)) {
$valueMatches = $valueTable[$prevToken] |
Where-Object { $_ -like "$wordToComplete*" }
if ($valueMatches) {
$valueMatches |
ForEach-Object { [CompletionResult]::new($_, $_, 'ParameterValue', $_) }
return # only return when we have value completions
}
}
# ──── switch completion (plus fallback to files) ──────────────────────────
$switchMatches = $switches + $valueTable.Keys |
Where-Object { $_ -like "$wordToComplete*" } |
Sort-Object {
# Sort short options first (starting with single dash), then long options
if ($_ -match '^-[^-]') { "0$_" } else { "1$_" }
}
foreach ($s in $switchMatches) {
# Use help text as the tooltip if available
$tooltip = if ($helpText.ContainsKey($s)) { $helpText[$s] } else { $s }
[CompletionResult]::new($s, $s, 'ParameterName', $tooltip)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment