@@ -76,7 +76,7 @@ export class PathExecutableCache implements vscode.Disposable {
76
76
const promises : Promise < Set < ICompletionResource > | undefined > [ ] = [ ] ;
77
77
const labels : Set < string > = new Set < string > ( ) ;
78
78
for ( const path of paths ) {
79
- promises . push ( this . _getFilesInPath ( path , pathSeparator , labels ) ) ;
79
+ promises . push ( this . _getExecutablesInPath ( path , pathSeparator , labels ) ) ;
80
80
}
81
81
82
82
// Merge all results
@@ -96,7 +96,7 @@ export class PathExecutableCache implements vscode.Disposable {
96
96
return this . _cachedExes ;
97
97
}
98
98
99
- private async _getFilesInPath ( path : string , pathSeparator : string , labels : Set < string > ) : Promise < Set < ICompletionResource > | undefined > {
99
+ private async _getExecutablesInPath ( path : string , pathSeparator : string , labels : Set < string > ) : Promise < Set < ICompletionResource > | undefined > {
100
100
try {
101
101
const dirExists = await fs . stat ( path ) . then ( stat => stat . isDirectory ( ) ) . catch ( ( ) => false ) ;
102
102
if ( ! dirExists ) {
@@ -106,11 +106,53 @@ export class PathExecutableCache implements vscode.Disposable {
106
106
const fileResource = vscode . Uri . file ( path ) ;
107
107
const files = await vscode . workspace . fs . readDirectory ( fileResource ) ;
108
108
for ( const [ file , fileType ] of files ) {
109
- const formattedPath = getFriendlyResourcePath ( vscode . Uri . joinPath ( fileResource , file ) , pathSeparator ) ;
110
- if ( ! labels . has ( file ) && fileType !== vscode . FileType . Unknown && fileType !== vscode . FileType . Directory && await isExecutable ( formattedPath , this . _cachedWindowsExeExtensions ) ) {
111
- result . add ( { label : file , documentation : formattedPath , kind : vscode . TerminalCompletionItemKind . Method } ) ;
112
- labels . add ( file ) ;
109
+ let kind : vscode . TerminalCompletionItemKind | undefined ;
110
+ let formattedPath : string | undefined ;
111
+ const resource = vscode . Uri . joinPath ( fileResource , file ) ;
112
+
113
+ // Skip unknown or directory file types early
114
+ if ( fileType === vscode . FileType . Unknown || fileType === vscode . FileType . Directory ) {
115
+ continue ;
116
+ }
117
+
118
+ try {
119
+ const lstat = await fs . lstat ( resource . fsPath ) ;
120
+ if ( lstat . isSymbolicLink ( ) ) {
121
+ try {
122
+ const symlinkRealPath = await fs . realpath ( resource . fsPath ) ;
123
+ const isExec = await isExecutable ( symlinkRealPath , this . _cachedWindowsExeExtensions ) ;
124
+ if ( ! isExec ) {
125
+ continue ;
126
+ }
127
+ kind = vscode . TerminalCompletionItemKind . Method ;
128
+ formattedPath = `${ resource . fsPath } -> ${ symlinkRealPath } ` ;
129
+ } catch {
130
+ continue ;
131
+ }
132
+ }
133
+ } catch {
134
+ // Ignore errors for unreadable files
135
+ continue ;
136
+ }
137
+
138
+ formattedPath = formattedPath ?? getFriendlyResourcePath ( resource , pathSeparator ) ;
139
+
140
+ // Check if already added or not executable
141
+ if ( labels . has ( file ) ) {
142
+ continue ;
113
143
}
144
+
145
+ const isExec = kind === vscode . TerminalCompletionItemKind . Method || await isExecutable ( formattedPath , this . _cachedWindowsExeExtensions ) ;
146
+ if ( ! isExec ) {
147
+ continue ;
148
+ }
149
+
150
+ result . add ( {
151
+ label : file ,
152
+ documentation : formattedPath ,
153
+ kind : kind ?? vscode . TerminalCompletionItemKind . Method
154
+ } ) ;
155
+ labels . add ( file ) ;
114
156
}
115
157
return result ;
116
158
} catch ( e ) {
0 commit comments