completers/markitdown_completer/markitdown_completer.ps1
|
Set-StrictMode -Version 2.0 if (-not (Get-Variable -Name MarkItDownCompletionCache -Scope Script -ErrorAction Ignore)) { $script:MarkItDownCompletionCache = @{ OptionSpecs = $null OptionMap = $null } } function New-MarkItDownCompletionResult { param( [string]$CompletionText, [string]$ResultType = 'ParameterValue', [string]$ToolTip, [string]$ListItemText ) if ([string]::IsNullOrWhiteSpace($ListItemText)) { $ListItemText = $CompletionText } if ([string]::IsNullOrWhiteSpace($ToolTip)) { $ToolTip = $ListItemText } [System.Management.Automation.CompletionResult]::new( $CompletionText, $ListItemText, $ResultType, $ToolTip ) } function Get-MarkItDownOptionSpecs { $cache = $script:MarkItDownCompletionCache if ($null -eq $cache.OptionSpecs) { $cache.OptionSpecs = @( [pscustomobject]@{ Tokens = @('-h', '--help'); ValueKind = $null; Description = 'Show help message and exit.' } [pscustomobject]@{ Tokens = @('-v', '--version'); ValueKind = $null; Description = 'Show version number and exit.' } [pscustomobject]@{ Tokens = @('-o', '--output'); ValueKind = 'Output'; Description = 'Write converted markdown to a file.' } [pscustomobject]@{ Tokens = @('-x', '--extension'); ValueKind = 'Extension'; Description = 'Hint the input extension when reading from stdin.' } [pscustomobject]@{ Tokens = @('-m', '--mime-type'); ValueKind = 'MimeType'; Description = 'Hint the input MIME type.' } [pscustomobject]@{ Tokens = @('-c', '--charset'); ValueKind = 'Charset'; Description = 'Hint the input charset.' } [pscustomobject]@{ Tokens = @('-d', '--use-docintel'); ValueKind = $null; Description = 'Use Azure Document Intelligence extraction (requires --endpoint).' } [pscustomobject]@{ Tokens = @('--use-cu', '--use-content-understanding'); ValueKind = $null; Description = 'Use Azure Content Understanding extraction (requires --cu-endpoint).' } [pscustomobject]@{ Tokens = @('-e', '--endpoint'); ValueKind = 'Endpoint'; Description = 'Document Intelligence endpoint URL.' } [pscustomobject]@{ Tokens = @('--cu-endpoint'); ValueKind = 'Endpoint'; Description = 'Content Understanding endpoint URL.' } [pscustomobject]@{ Tokens = @('--cu-analyzer'); ValueKind = 'Analyzer'; Description = 'Content Understanding analyzer ID (auto-selected by file type when omitted).' } [pscustomobject]@{ Tokens = @('--cu-file-types'); ValueKind = 'FileTypes'; Description = 'Comma-separated file types routed to Content Understanding (e.g. pdf,jpeg,mp4).' } [pscustomobject]@{ Tokens = @('-p', '--use-plugins'); ValueKind = $null; Description = 'Enable installed third-party plugins.' } [pscustomobject]@{ Tokens = @('--list-plugins'); ValueKind = $null; Description = 'List installed third-party plugins.' } [pscustomobject]@{ Tokens = @('--keep-data-uris'); ValueKind = $null; Description = 'Preserve data URIs in the output.' } ) } $cache.OptionSpecs } function Get-MarkItDownOptionMap { $cache = $script:MarkItDownCompletionCache if ($null -eq $cache.OptionMap) { $map = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::Ordinal) foreach ($spec in Get-MarkItDownOptionSpecs) { foreach ($token in $spec.Tokens) { $map[$token] = $spec } } $cache.OptionMap = $map } $cache.OptionMap } function Get-MarkItDownExtensionSuggestions { # Every ACCEPTED_FILE_EXTENSIONS entry across markitdown 0.1.7's converters. @( 'pdf', 'docx', 'pptx', 'xlsx', 'xls', 'csv', 'json', 'jsonl', 'ipynb', 'html', 'htm', 'xhtml', 'xml', 'rss', 'atom', 'txt', 'text', 'md', 'markdown', 'rtf', 'epub', 'zip', 'eml', 'msg', 'jpg', 'jpeg', 'jpe', 'png', 'bmp', 'tiff', 'heic', 'heif', 'mp3', 'wav', 'flac', 'ogg', 'aac', 'm4a', 'wma', 'mp4', 'm4v', 'mov', 'mkv', 'avi', 'webm', 'flv', 'wmv' ) } function Get-MarkItDownCuFileTypeTable { # ContentUnderstandingFileType values in markitdown 0.1.8; --cu-file-types # rejects anything else with 'Unknown file type'. @( 'pdf', 'docx', 'pptx', 'xlsx', 'html', 'txt', 'md', 'rtf', 'xml', 'eml', 'msg', 'jpeg', 'png', 'bmp', 'tiff', 'heif', 'mp4', 'm4v', 'mov', 'avi', 'mkv', 'webm', 'flv', 'wmv', 'wav', 'mp3', 'm4a', 'flac', 'ogg', 'aac', 'wma' ) } function Get-MarkItDownMimeTypeSuggestions { # Every ACCEPTED_MIME_TYPE_PREFIXES entry across markitdown 0.1.7's converters. @( 'application/pdf', 'application/x-pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.presentationml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel', 'application/excel', 'application/vnd.ms-outlook', 'application/json', 'application/xml', 'application/xhtml', 'application/xhtml+xml', 'application/atom', 'application/atom+xml', 'application/rss', 'application/rss+xml', 'application/csv', 'application/markdown', 'application/rtf', 'application/epub', 'application/epub+zip', 'application/x-epub+zip', 'application/zip', 'text/plain', 'text/html', 'text/csv', 'text/xml', 'text/markdown', 'text/rtf', 'message/rfc822', 'image/jpeg', 'image/png', 'image/bmp', 'image/tiff', 'image/heic', 'image/heif', 'image/svg+xml', 'audio/mpeg', 'audio/mp3', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'audio/flac', 'audio/x-flac', 'audio/ogg', 'audio/aac', 'audio/x-ms-wma', 'video/mp4', 'video/x-m4v', 'video/quicktime', 'video/x-matroska', 'video/x-msvideo', 'video/webm', 'video/x-flv', 'video/x-ms-wmv' ) } function Get-MarkItDownCharsetSuggestions { @( 'utf-8', 'utf-16', 'utf-16le', 'utf-16be', 'utf-32', 'ascii', 'latin1', 'windows-1252' ) } function Get-MarkItDownUriSchemeTable { @('https://', 'http://', 'file:///', 'data:') } function ConvertFrom-MarkItDownTypedWord { # Splits the text typed so far into its value and the opening quote the # user typed ('' when bare, otherwise ' or "), undoing that quote style's # escapes. PowerShell treats U+2018-U+201B as single quotes and # U+201C-U+201E as double quotes; a doubled quote keeps its second # character, as the parser does. param([string]$Text) $single = '[''\u2018-\u201B]' $double = '["\u201C-\u201E]' $quoteClass = if ($Text -match "^$single") { $single } elseif ($Text -match "^$double") { $double } if (-not $quoteClass) { return [pscustomobject]@{ Value = $Text; Quote = '' } } $value = [System.Text.StringBuilder]::new() for ($i = 1; $i -lt $Text.Length; $i++) { $char = [string]$Text[$i] $next = if ($i + 1 -lt $Text.Length) { [string]$Text[$i + 1] } else { '' } if ($quoteClass -eq $double -and $char -eq '`' -and $next) { $i++ [void]$value.Append($next) } elseif ($char -match $quoteClass) { if ($next -notmatch $quoteClass) { break } $i++ [void]$value.Append($next) } else { [void]$value.Append($char) } } $quote = if ($quoteClass -eq $single) { "'" } else { '"' } [pscustomobject]@{ Value = $value.ToString(); Quote = $quote } } function ConvertTo-MarkItDownArgument { # Renders a value as one PowerShell argument: bare when safe and no quote # was typed, otherwise in the typed quote style (single by default). # Whitespace and argument-mode metacharacters (including the typographic # quotes) end or split a bare word, and a leading '@' or '#' would start a # splat or a comment. param( [string]$Value, [string]$Quote ) if (-not $Quote) { if ($Value -notmatch '[\s{}();,|&<>''"`$\u2018-\u201E]' -and $Value -notmatch '^[@#]') { return $Value } $Quote = "'" } if ($Quote -eq "'") { return "'" + ($Value -replace '([''\u2018-\u201B])', '$1$1') + "'" } '"' + ($Value -replace '([`"$\u201C-\u201E])', '`$1') + '"' } function Get-MarkItDownTokenText { param([System.Management.Automation.Language.Ast]$Element) if ($Element -is [System.Management.Automation.Language.StringConstantExpressionAst]) { return $Element.Value } if ($Element -is [System.Management.Automation.Language.CommandParameterAst]) { return $Element.Extent.Text } $Element.Extent.Text } function Get-MarkItDownArgumentTokens { param( [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) $tokens = @() foreach ($element in $CommandAst.CommandElements | Select-Object -Skip 1) { if ($element.Extent.EndOffset -lt $CursorPosition) { $tokens += Get-MarkItDownTokenText -Element $element } } $tokens } function Split-MarkItDownAttachedOption { param([string]$Token) # Returns the option spec and attached value for '--opt=value' tokens, # or $null when the token is not an attached, value-bearing option. if ([string]::IsNullOrEmpty($Token) -or -not $Token.StartsWith('-') -or -not $Token.Contains('=')) { return $null } $separator = $Token.IndexOf('=') $name = $Token.Substring(0, $separator) $optionMap = Get-MarkItDownOptionMap if (-not $optionMap.ContainsKey($name) -or -not $optionMap[$name].ValueKind) { return $null } [pscustomobject]@{ Name = $name Spec = $optionMap[$name] Value = $Token.Substring($separator + 1) } } function Get-MarkItDownExpectedValueSpec { param([string[]]$TokensBeforeCurrent) if (-not $TokensBeforeCurrent -or $TokensBeforeCurrent.Count -eq 0) { return $null } $optionMap = Get-MarkItDownOptionMap $lastToken = $TokensBeforeCurrent[-1] if ($optionMap.ContainsKey($lastToken) -and $optionMap[$lastToken].ValueKind) { return $optionMap[$lastToken] } $null } function Get-MarkItDownPathCompletions { param( [string]$InputPath, [string]$ToolTipPrefix = 'Path', [switch]$Attached ) $typed = ConvertFrom-MarkItDownTypedWord -Text $InputPath $typedValue = $typed.Value # An empty $parent means the current directory with no typed folder part, # so candidates stay bare names; a typed folder part (even '.\') is kept # exactly as typed, separators included. if ([string]::IsNullOrWhiteSpace($typedValue)) { $parent = '' $leaf = '' } elseif ($typedValue.EndsWith('\') -or $typedValue.EndsWith('/')) { $parent = $typedValue $leaf = '' } else { $parent = Split-Path -Path $typedValue -Parent $leaf = if ($parent) { $typedValue -replace '^.*[\\/]' } else { $typedValue } } $typedFolder = if ($parent) { $typedValue.Substring(0, $typedValue.Length - $leaf.Length) } else { '' } $separator = if ($typedFolder -match '[\\/]$') { $Matches[0] } else { [string][System.IO.Path]::DirectorySeparatorChar } # Enumerate lazily through .NET so a 30k-entry directory is not # materialized: directories first, then files markitdown can convert, # then everything else, capped at 200 entries in total. $limit = 200 $basePath = if (-not $parent) { $PWD.ProviderPath } elseif ([System.IO.Path]::IsPathRooted($parent)) { $parent } else { Join-Path -Path $PWD.ProviderPath -ChildPath $parent } try { $directory = [System.IO.DirectoryInfo]::new($basePath) if (-not $directory.Exists) { return } $pattern = $leaf + '*' $hidden = [System.IO.FileAttributes]::Hidden $convertible = [System.Collections.Generic.HashSet[string]]::new([string[]](Get-MarkItDownExtensionSuggestions), [System.StringComparer]::OrdinalIgnoreCase) $directories = New-Object System.Collections.Generic.List[object] foreach ($entry in $directory.EnumerateDirectories($pattern)) { if (($entry.Attributes -band $hidden) -eq $hidden) { continue } [void]$directories.Add($entry) if ($directories.Count -ge $limit) { break } } $preferred = New-Object System.Collections.Generic.List[object] $others = New-Object System.Collections.Generic.List[object] foreach ($entry in $directory.EnumerateFiles($pattern)) { if (($entry.Attributes -band $hidden) -eq $hidden) { continue } if ($convertible.Contains($entry.Extension.TrimStart('.'))) { [void]$preferred.Add($entry) if ($preferred.Count -ge $limit) { break } } elseif ($others.Count -lt $limit) { [void]$others.Add($entry) } } } catch { return } $items = @( @($directories | Sort-Object -Property Name) + @($preferred | Sort-Object -Property Name) + @($others | Sort-Object -Property Name) ) | Select-Object -First $limit foreach ($item in $items) { $isContainer = $item -is [System.IO.DirectoryInfo] # A whole-word name starting with a dash would parse as a parameter, so # it gets the current-directory prefix, as PowerShell's own completion # does; after an attached '--opt=' the word starts with the option. $pathText = if ($typedFolder) { $typedFolder + $item.Name } elseif (-not $Attached -and $item.Name -match '^[-\u2013-\u2015]') { '.' + $separator + $item.Name } else { $item.Name } if ($isContainer) { $pathText += $separator } $completionText = ConvertTo-MarkItDownArgument -Value $pathText -Quote $typed.Quote $resultType = if ($isContainer) { 'ProviderContainer' } else { 'ProviderItem' } New-MarkItDownCompletionResult -CompletionText $completionText -ResultType $resultType -ListItemText $pathText -ToolTip "${ToolTipPrefix}: $($item.FullName)" } } function Complete-MarkItDownCommaList { param( [string[]]$Candidates, [string]$CurrentWord, [string]$ToolTip ) $prefix = '' $segment = $CurrentWord $selected = @() if ($CurrentWord -like '*,*') { $lastComma = $CurrentWord.LastIndexOf(',') $prefix = $CurrentWord.Substring(0, $lastComma + 1) $segment = $CurrentWord.Substring($lastComma + 1) $selected = @($prefix.TrimEnd(',').Split(',') | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) } foreach ($candidate in $Candidates) { if ($selected -contains $candidate) { continue } if ($candidate -like ([System.Management.Automation.WildcardPattern]::Escape($segment) + '*')) { New-MarkItDownCompletionResult -CompletionText ($prefix + $candidate) -ListItemText $candidate -ToolTip $ToolTip } } } function Get-MarkItDownValueCompletions { param( [object]$Spec, [string]$CurrentWord, [switch]$Attached ) if ($Spec.ValueKind -eq 'Output') { $results = @(Get-MarkItDownPathCompletions -InputPath $CurrentWord -ToolTipPrefix 'Output' -Attached:$Attached) if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $results += New-MarkItDownCompletionResult -CompletionText 'output.md' -ToolTip 'Write markdown output to output.md.' } return $results } # Match on the unquoted value; a quote the user typed is kept below. $typed = ConvertFrom-MarkItDownTypedWord -Text $CurrentWord $pattern = [System.Management.Automation.WildcardPattern]::Escape($typed.Value) + '*' $results = @( switch ([string]$Spec.ValueKind) { 'Extension' { Get-MarkItDownExtensionSuggestions | Where-Object { $_ -like $pattern } | ForEach-Object { New-MarkItDownCompletionResult -CompletionText $_ -ToolTip 'Input extension hint.' } } 'MimeType' { Get-MarkItDownMimeTypeSuggestions | Where-Object { $_ -like $pattern } | ForEach-Object { New-MarkItDownCompletionResult -CompletionText $_ -ToolTip 'Input MIME type hint.' } } 'Charset' { Get-MarkItDownCharsetSuggestions | Where-Object { $_ -like $pattern } | ForEach-Object { New-MarkItDownCompletionResult -CompletionText $_ -ToolTip 'Input charset hint.' } } 'Endpoint' { @('https://<resource>.cognitiveservices.azure.com/') | Where-Object { $_ -like $pattern } | ForEach-Object { New-MarkItDownCompletionResult -CompletionText $_ -ToolTip 'Azure endpoint URL.' } } 'Analyzer' { if ([string]::IsNullOrWhiteSpace($typed.Value)) { New-MarkItDownCompletionResult -CompletionText '<analyzer-id>' -ToolTip 'Content Understanding analyzer ID.' } } 'FileTypes' { Complete-MarkItDownCommaList -Candidates (Get-MarkItDownCuFileTypeTable) -CurrentWord $typed.Value -ToolTip 'File type routed to Content Understanding.' } } ) if (-not $typed.Quote) { return $results } foreach ($item in $results) { $completionText = ConvertTo-MarkItDownArgument -Value $item.CompletionText -Quote $typed.Quote New-MarkItDownCompletionResult -CompletionText $completionText -ResultType $item.ResultType -ToolTip $item.ToolTip -ListItemText $item.ListItemText } } function Get-MarkItDownOptionCompletions { param([string]$CurrentWord) foreach ($spec in Get-MarkItDownOptionSpecs) { foreach ($token in $spec.Tokens) { if ($token -like ([System.Management.Automation.WildcardPattern]::Escape($CurrentWord) + '*')) { New-MarkItDownCompletionResult -CompletionText $token -ResultType 'ParameterName' -ToolTip $spec.Description } } } } function Complete-MarkItDownInput { param([string]$CurrentWord) $typedValue = (ConvertFrom-MarkItDownTypedWord -Text $CurrentWord).Value # A URI operand (http:, https:, file:, data:) is never a local listing. if ($typedValue -match '^[A-Za-z][A-Za-z0-9+.-]*:(//|$)' -and $typedValue -notmatch '^[A-Za-z]:[\\/]?$') { foreach ($scheme in Get-MarkItDownUriSchemeTable) { if ($scheme -like ([System.Management.Automation.WildcardPattern]::Escape($typedValue) + '*')) { New-MarkItDownCompletionResult -CompletionText $scheme -ToolTip 'Convert a remote or data URI.' } } return } Get-MarkItDownPathCompletions -InputPath $CurrentWord -ToolTipPrefix 'Input' if ($typedValue -match '^[a-z]*$') { foreach ($scheme in Get-MarkItDownUriSchemeTable) { if ($scheme -like ([System.Management.Automation.WildcardPattern]::Escape($typedValue) + '*')) { New-MarkItDownCompletionResult -CompletionText $scheme -ToolTip 'Convert a remote or data URI.' } } } if ([string]::IsNullOrWhiteSpace($typedValue)) { New-MarkItDownCompletionResult -CompletionText '<filename>' -ToolTip 'Input file to convert. Omit to read from stdin.' } } function Get-MarkItDownArgumentValue { # The value a native command receives for $Text written as one argument: # a string's value, or an unquoted comma list joined with commas. Anything # else (a quoted list element, an expression) yields $null. param([string]$Text) $ast = [System.Management.Automation.Language.Parser]::ParseInput("x $Text", [ref]$null, [ref]$null) $command = $ast.Find({ param($node) $node -is [System.Management.Automation.Language.CommandAst] }, $true) $elements = @($command.CommandElements | Select-Object -Skip 1) if ($elements.Count -ne 1) { return $null } $element = $elements[0] if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst]) { return $element.Value } if ($element -is [System.Management.Automation.Language.ArrayLiteralAst]) { $parts = @($element.Elements) if (@($parts | Where-Object { $_ -isnot [System.Management.Automation.Language.StringConstantExpressionAst] -or $_.StringConstantType -ne 'BareWord' }).Count -eq 0) { return (($parts | ForEach-Object Value) -join ',') } } $null } function Select-MarkItDownSegmentCompletion { # Inside an unquoted comma list PowerShell replaces only the segment after # the last comma and hands a native command the list joined with commas. # A candidate is kept when its value extends the typed list and the rest # stays bare, so the accepted line is still one argument with that value. param( [object[]]$Results, [string]$SegmentPrefix ) foreach ($item in $Results) { $value = Get-MarkItDownArgumentValue -Text $item.CompletionText if ($null -eq $value -or -not $value.StartsWith($SegmentPrefix, [System.StringComparison]::Ordinal)) { continue } $rest = $value.Substring($SegmentPrefix.Length) $unsafe = @($rest.Split(',') | Where-Object { -not $_ -or (ConvertTo-MarkItDownArgument -Value $_) -cne $_ }) if ($unsafe.Count -gt 0) { continue } New-MarkItDownCompletionResult -CompletionText $rest -ResultType $item.ResultType -ToolTip $item.ToolTip -ListItemText $item.ListItemText } } function Get-MarkItDownWordCompletion { param( [string]$CurrentWord, [string[]]$TokensBeforeCurrent ) $expectedValue = Get-MarkItDownExpectedValueSpec -TokensBeforeCurrent $TokensBeforeCurrent if ($expectedValue) { return @(Get-MarkItDownValueCompletions -Spec $expectedValue -CurrentWord $CurrentWord) } $attached = Split-MarkItDownAttachedOption -Token $CurrentWord if ($attached) { $prefix = $attached.Name + '=' return @( foreach ($item in @(Get-MarkItDownValueCompletions -Spec $attached.Spec -CurrentWord $attached.Value -Attached)) { New-MarkItDownCompletionResult -CompletionText ($prefix + $item.CompletionText) -ResultType $item.ResultType -ToolTip $item.ToolTip -ListItemText $item.ListItemText } ) } if (-not [string]::IsNullOrEmpty($CurrentWord) -and $CurrentWord.StartsWith('-')) { return @(Get-MarkItDownOptionCompletions -CurrentWord $CurrentWord) } $positionals = @() $optionMap = Get-MarkItDownOptionMap $skipNext = $false foreach ($token in $TokensBeforeCurrent) { if ($skipNext) { $skipNext = $false continue } if ($optionMap.ContainsKey($token)) { if ($optionMap[$token].ValueKind) { $skipNext = $true } continue } if (Split-MarkItDownAttachedOption -Token $token) { continue } $positionals += $token } if ($positionals.Count -eq 0) { return @( Complete-MarkItDownInput -CurrentWord $CurrentWord Get-MarkItDownOptionCompletions -CurrentWord $CurrentWord ) } if ([string]::IsNullOrWhiteSpace($CurrentWord)) { return @(Get-MarkItDownOptionCompletions -CurrentWord $CurrentWord) } @() } function Complete-MarkItDown { param( [string]$WordToComplete, [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) # PowerShell hands a quoted word over re-quoted ("'it" arrives as "'it'") # and drops the quote inside an attached "--opt='it", while it replaces # the whole word as typed; so the word is read from the element under the # cursor. An unquoted comma list ('--opt=a,b', or 'a,' which parses as an # error expression) is the exception: PowerShell replaces only the segment # after the last comma, so the whole element is completed and the # candidates are cut back to that segment. $currentWord = $WordToComplete $segmentPrefix = '' $cursorElement = $CommandAst.CommandElements | Select-Object -Skip 1 | Where-Object { $_.Extent.StartOffset -lt $CursorPosition -and $_.Extent.EndOffset -ge $CursorPosition } | Select-Object -First 1 if ($cursorElement) { $elementText = $cursorElement.Extent.Text.Substring(0, $CursorPosition - $cursorElement.Extent.StartOffset) $isList = $cursorElement -is [System.Management.Automation.Language.ArrayLiteralAst] -or $cursorElement -is [System.Management.Automation.Language.ErrorExpressionAst] if (-not $isList) { $currentWord = $elementText } elseif ($elementText.EndsWith(',' + $WordToComplete, [System.StringComparison]::Ordinal)) { $currentWord = $elementText $segmentPrefix = $elementText.Substring(0, $elementText.Length - $WordToComplete.Length) } } $tokensBeforeCurrent = @(Get-MarkItDownArgumentTokens -CommandAst $CommandAst -CursorPosition $CursorPosition) $results = @(Get-MarkItDownWordCompletion -CurrentWord $currentWord -TokensBeforeCurrent $tokensBeforeCurrent) if (-not $segmentPrefix) { return $results } $segmentResults = @(Select-MarkItDownSegmentCompletion -Results $results -SegmentPrefix $segmentPrefix) if ($segmentResults.Count -eq 0 -and $WordToComplete) { # Echo the typed segment so PowerShell's filename fallback adds no # candidate that would break the list; an empty segment cannot be # echoed (PowerShell rejects an empty completion text). return @(New-MarkItDownCompletionResult -CompletionText $WordToComplete -ToolTip 'Nothing continues this comma list; quote the whole value to complete a name that contains a comma.') } $segmentResults } Register-ArgumentCompleter -Native -CommandName @('markitdown', 'markitdown.exe') -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) Complete-MarkItDown -WordToComplete $wordToComplete -CommandAst $commandAst -CursorPosition $cursorPosition } |