@@ -9,7 +9,7 @@ import type { CommandCompletion } from '@commas/types/terminal'
99import * as commas from '../../api/core-main'
1010import { resolveHome } from '../../shared/terminal'
1111import { execa , memoizeAsync } from './helper'
12- import { loginExecute } from './shell'
12+ import { BIN_PATH , loginExecute } from './shell'
1313
1414function isCommandLineArgument ( query : string ) {
1515 return query . startsWith ( '-' )
@@ -321,6 +321,29 @@ async function getHistoryCompletions(query: string, command: string) {
321321 } ) )
322322}
323323
324+ async function getZshCaptureCompletions ( input : string , query : string , cwd : string ) {
325+ try {
326+ const { stdout } = await loginExecute ( quote ( [ path . join ( BIN_PATH , 'zsh-capture-completion.sh' ) , input ] ) , {
327+ shell : '/bin/zsh' ,
328+ cwd,
329+ } )
330+ const choices = uniq ( stdout . trim ( ) . split ( / [ \r \n ] + / ) )
331+ return choices . map < CommandCompletion > ( choice => {
332+ const matches = choice . match ( / ^ ( .+ ?) \s + - - \s + ( .+ ) $ / )
333+ const value = matches ? matches [ 1 ] : choice
334+ const description = matches ? matches [ 2 ] : undefined
335+ return {
336+ type : 'command' ,
337+ query,
338+ value,
339+ description,
340+ }
341+ } )
342+ } catch {
343+ return [ ]
344+ }
345+ }
346+
324347async function getAllCommands ( ) {
325348 if ( process . platform === 'win32' ) return [ ]
326349 try {
@@ -381,13 +404,14 @@ function isCommandEntry(entry: ParseEntry): entry is string {
381404 return typeof entry === 'string' && / ^ \w / . test ( entry )
382405}
383406
384- async function getCompletions ( input : string , cwd : string ) {
407+ async function getCompletions ( input : string , cwd : string , capture ?: boolean ) {
385408 const entries = parse ( input ) . filter ( item => {
386409 return ! ( typeof item === 'object' && 'comment' in item )
387410 } )
388411 if ( ! entries . length ) return [ ]
389412 const lastToken = entries [ entries . length - 1 ]
390413 const isWordStart = / \s $ / . test ( input ) || typeof lastToken !== 'string'
414+ const currentWord = isWordStart ? '' : lastToken
391415 const tokenIndex = entries . findLastIndex ( item => {
392416 return isControlOperatorEntry ( item ) && item . op !== '>'
393417 } )
@@ -398,8 +422,6 @@ async function getCompletions(input: string, cwd: string) {
398422 const subcommandIndex = args . findIndex ( isCommandEntry )
399423 const undeterminedSubcommand = subcommandIndex !== - 1 ? args [ subcommandIndex ] as string : undefined
400424 const subcommandArgs = subcommandIndex !== - 1 ? args . slice ( subcommandIndex + 1 ) : [ ]
401- const currentWord = isWordStart ? '' : lastToken
402- const isInputingArgs = isCommandLineArgument ( currentWord )
403425 const command = undeterminedCommand && ( isWordStart || args . length > 0 )
404426 ? undeterminedCommand . toLowerCase ( )
405427 : ''
@@ -417,40 +439,48 @@ async function getCompletions(input: string, cwd: string) {
417439 subcommand,
418440 } ) ) ,
419441 )
420- // History
421- if ( currentWord ) {
422- asyncCompletionLists . push (
423- getHistoryCompletions ( currentWord , command ) ,
424- )
425- }
426- // Commands
427- if ( ! command && ! / ^ ( .+ | ~ ) ? [ \\ / ] / . test ( currentWord ) ) {
442+ if ( capture ) {
443+ // Zsh capture
428444 asyncCompletionLists . push (
429- getCommandCompletions ( currentWord ) ,
430- )
431- }
432- // Files
433- const frequentlyUsedFileCommands = [ '.' , 'cat' , 'cd' , 'cp' , 'diff' , 'more' , 'mv' , 'rm' , 'source' , 'vi' ]
434- if ( ! isInputingArgs && (
435- isControlOperatorEntry ( lastToken ) && lastToken . op === '>'
436- || command && ( currentWord || frequentlyUsedFileCommands . includes ( command ) )
437- || ! command && / ^ ( .+ | ~ ) ? [ \\ / ] / . test ( currentWord )
438- ) ) {
439- const directoryCommands = [ 'cd' , 'dir' , 'ls' ]
440- const directoryOnly = directoryCommands . includes ( command )
441- asyncCompletionLists . push (
442- getFileCompletions ( currentWord , cwd , directoryOnly ) ,
443- )
444- }
445- if ( command ) {
446- asyncCompletionLists . push (
447- getManPageCompletions ( currentWord , command , subcommand ) ,
448- )
449- }
450- if ( command === 'npm' && subcommand === 'run' ) {
451- asyncCompletionLists . push (
452- getFrequentlyUsedProgramCompletions ( currentWord , cwd , command , subcommand ) ,
445+ getZshCaptureCompletions ( input , currentWord , cwd ) ,
453446 )
447+ } else {
448+ // History
449+ if ( currentWord ) {
450+ asyncCompletionLists . push (
451+ getHistoryCompletions ( currentWord , command ) ,
452+ )
453+ }
454+ // Commands
455+ if ( ! command && ! / ^ ( .+ | ~ ) ? [ \\ / ] / . test ( currentWord ) ) {
456+ asyncCompletionLists . push (
457+ getCommandCompletions ( currentWord ) ,
458+ )
459+ }
460+ // Files
461+ const isInputingArgs = isCommandLineArgument ( currentWord )
462+ const frequentlyUsedFileCommands = [ '.' , 'cat' , 'cd' , 'cp' , 'diff' , 'more' , 'mv' , 'rm' , 'source' , 'vi' ]
463+ if ( ! isInputingArgs && (
464+ isControlOperatorEntry ( lastToken ) && lastToken . op === '>'
465+ || command && ( currentWord || frequentlyUsedFileCommands . includes ( command ) )
466+ || ! command && / ^ ( .+ | ~ ) ? [ \\ / ] / . test ( currentWord )
467+ ) ) {
468+ const directoryCommands = [ 'cd' , 'dir' , 'ls' ]
469+ const directoryOnly = directoryCommands . includes ( command )
470+ asyncCompletionLists . push (
471+ getFileCompletions ( currentWord , cwd , directoryOnly ) ,
472+ )
473+ }
474+ if ( command ) {
475+ asyncCompletionLists . push (
476+ getManPageCompletions ( currentWord , command , subcommand ) ,
477+ )
478+ }
479+ if ( command === 'npm' && subcommand === 'run' ) {
480+ asyncCompletionLists . push (
481+ getFrequentlyUsedProgramCompletions ( currentWord , cwd , command , subcommand ) ,
482+ )
483+ }
454484 }
455485 const lists = await Promise . all ( asyncCompletionLists )
456486 const completions = lists . flat ( )
0 commit comments