14
14
* limitations under the License.
15
15
*/
16
16
17
- import { lstat , realpath , readdir } from 'fs'
18
- import { basename , join , dirname as pathDirname } from 'path'
17
+ import { basename , dirname as pathDirname } from 'path'
19
18
20
19
import {
21
20
Tab ,
@@ -28,40 +27,45 @@ import {
28
27
CompletionResponse
29
28
} from '@kui-shell/core'
30
29
31
- /**
32
- * Is the given filepath a directory?
33
- *
34
- */
35
- const isDirectory = ( filepath : string ) : Promise < boolean > =>
36
- new Promise < boolean > ( ( resolve , reject ) => {
37
- lstat ( filepath , ( err , stats ) => {
38
- if ( err ) {
39
- reject ( err )
40
- } else {
41
- if ( stats . isSymbolicLink ( ) ) {
42
- // debug('following symlink')
43
- // TODO: consider turning these into the better async calls?
44
- return realpath ( filepath , ( err , realpath ) => {
45
- if ( err ) {
46
- reject ( err )
47
- } else {
48
- isDirectory ( realpath )
49
- . then ( resolve )
50
- . catch ( reject )
51
- }
52
- } )
53
- }
30
+ import { ls } from '../vfs/delegates'
31
+ import { GlobStats } from '../lib/glob'
54
32
55
- resolve ( stats . isDirectory ( ) )
33
+ function findMatchingFilesFrom ( files : GlobStats [ ] , dirToScan : string , last : string , lastIsDir : boolean ) {
34
+ const partial = basename ( last ) + ( lastIsDir ? '/' : '' )
35
+ const partialHasADot = partial . startsWith ( '.' )
36
+
37
+ const matches = files . filter ( _f => {
38
+ const f = _f . name
39
+
40
+ // exclude dot files from tab completion, also emacs ~ temp files just for fun
41
+ return (
42
+ ( lastIsDir || f . indexOf ( partial ) === 0 ) &&
43
+ ! f . endsWith ( '~' ) &&
44
+ f !== '.' &&
45
+ f !== '..' &&
46
+ ( partialHasADot || ! f . startsWith ( '.' ) )
47
+ )
48
+ } )
49
+
50
+ // add a trailing slash to any matched directory names
51
+ const lastHasPath = / \/ / . test ( last )
52
+ return {
53
+ mode : 'raw' ,
54
+ content : matches . map ( matchStats => {
55
+ const match = matchStats . nameForDisplay
56
+ const completion = lastIsDir ? match : match . substring ( partial . length )
57
+
58
+ // show a special label only if we have a dirname prefix
59
+ const label = lastHasPath ? basename ( match ) : undefined
60
+
61
+ if ( matchStats . dirent . isDirectory ) {
62
+ return { completion : `${ completion } /` , label : label ? `${ label } /` : undefined }
63
+ } else {
64
+ return { completion, addSpace : true , label }
56
65
}
57
66
} )
58
- } ) . catch ( err => {
59
- if ( err . code === 'ENOENT' ) {
60
- return false
61
- } else {
62
- throw err
63
- }
64
- } )
67
+ }
68
+ }
65
69
66
70
/**
67
71
* Tab completion handler for local files
@@ -75,7 +79,7 @@ async function completeLocalFiles(
75
79
return ( await tab . REPL . rexec < CompletionResponse [ ] > ( `fscomplete -- "${ toBeCompleted } "` ) ) . content
76
80
}
77
81
78
- function doComplete ( args : Arguments ) {
82
+ async function doComplete ( args : Arguments ) {
79
83
const last = args . command . substring ( args . command . indexOf ( '-- ' ) + '-- ' . length ) . replace ( / ^ " ( .* ) " $ / , '$1' )
80
84
81
85
// dirname will "foo" in the above example; it
@@ -84,55 +88,16 @@ function doComplete(args: Arguments) {
84
88
const lastIsDir = last . charAt ( last . length - 1 ) === '/'
85
89
const dirname = lastIsDir ? last : pathDirname ( last )
86
90
87
- // debug('suggest local file', dirname, last)
88
-
89
91
if ( dirname ) {
90
- // then dirname exists! now scan the directory so we can find matches
91
- return new Promise ( ( resolve , reject ) => {
92
+ try {
93
+ // Note: by passing a: true, we effect an `ls -a`, which will give us dot files
92
94
const dirToScan = expandHomeDir ( dirname )
93
- readdir ( dirToScan , async ( err , files ) => {
94
- if ( err ) {
95
- console . error ( 'fs.readdir error' , err )
96
- reject ( err )
97
- } else {
98
- const partial = basename ( last ) + ( lastIsDir ? '/' : '' )
99
- const partialHasADot = partial . startsWith ( '.' )
100
-
101
- const matches : string [ ] = files . filter ( _f => {
102
- const f = _f
103
-
104
- // exclude dot files from tab completion, also emacs ~ temp files just for fun
105
- return (
106
- ( lastIsDir || f . indexOf ( partial ) === 0 ) &&
107
- ! f . endsWith ( '~' ) &&
108
- f !== '.' &&
109
- f !== '..' &&
110
- ( partialHasADot || ! f . startsWith ( '.' ) )
111
- )
112
- } )
113
-
114
- // add a trailing slash to any matched directory names
115
- const lastHasPath = / \/ / . test ( last )
116
- resolve ( {
117
- mode : 'raw' ,
118
- content : await Promise . all (
119
- matches . map ( async match => {
120
- const completion = lastIsDir ? match : match . substring ( partial . length )
121
-
122
- // show a special label only if we have a dirname prefix
123
- const label = lastHasPath ? basename ( match ) : undefined
124
-
125
- if ( await isDirectory ( join ( dirToScan , match ) ) ) {
126
- return { completion : `${ completion } /` , label : label ? `${ label } /` : undefined }
127
- } else {
128
- return { completion, addSpace : true , label }
129
- }
130
- } )
131
- )
132
- } )
133
- }
134
- } )
135
- } )
95
+ const fileList = await ls ( { tab : args . tab , REPL : args . REPL , parsedOptions : { a : true } } , [ dirToScan ] )
96
+ return findMatchingFilesFrom ( fileList , dirToScan , last , lastIsDir )
97
+ } catch ( err ) {
98
+ console . error ( 'tab completion vfs.ls error' , err )
99
+ throw err
100
+ }
136
101
}
137
102
}
138
103
0 commit comments