Skip to content

Commit 78f858d

Browse files
committed
Eliminate statSync calls and filter prefixes first in path completion
1 parent 59cca53 commit 78f858d

2 files changed

Lines changed: 36 additions & 11 deletions

File tree

cli/src/__tests__/path-completion.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'fs'
1+
import {
2+
mkdirSync,
3+
mkdtempSync,
4+
rmSync,
5+
symlinkSync,
6+
writeFileSync,
7+
} from 'fs'
28
import os from 'os'
39
import path from 'path'
410

@@ -239,5 +245,17 @@ describe('getPathCompletion', () => {
239245
// Files should be ignored, common prefix from directories only
240246
expect(result).toBe(path.join(tempDir, 'project-'))
241247
})
248+
249+
test('completes symlinks pointing to directories', () => {
250+
const targetDir = path.join(tempDir, 'actual-dir')
251+
mkdirSync(targetDir)
252+
try {
253+
symlinkSync(targetDir, path.join(tempDir, 'symlinked-dir'))
254+
const result = getPathCompletion(path.join(tempDir, 'sym'))
255+
expect(result).toBe(path.join(tempDir, 'symlinked-dir') + path.sep)
256+
} catch {
257+
// Skip on systems where symlink creation is unprivileged/unsupported
258+
}
259+
})
242260
})
243261
})

cli/src/utils/path-completion.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,29 @@ export function getPathCompletion(inputPath: string): string | null {
4848

4949
// List directories in parent that match the partial
5050
try {
51-
const items = readdirSync(parentDir)
51+
const entries = readdirSync(parentDir, { withFileTypes: true })
5252
const matches: string[] = []
5353

54-
for (const item of items) {
54+
for (const entry of entries) {
55+
const name = entry.name
5556
// Skip hidden files unless user typed a dot
56-
if (item.startsWith('.') && !partial.startsWith('.')) continue
57+
if (name.startsWith('.') && !partial.startsWith('.')) continue
5758

58-
const fullPath = path.join(parentDir, item)
59-
try {
60-
if (!statSync(fullPath).isDirectory()) continue
61-
} catch {
62-
continue
59+
// Filter by prefix first before doing any directory resolution
60+
if (!name.toLowerCase().startsWith(partial)) continue
61+
62+
let isDirectory = entry.isDirectory()
63+
if (!isDirectory && entry.isSymbolicLink()) {
64+
try {
65+
const fullPath = path.join(parentDir, name)
66+
isDirectory = statSync(fullPath).isDirectory()
67+
} catch {
68+
isDirectory = false
69+
}
6370
}
6471

65-
if (item.toLowerCase().startsWith(partial)) {
66-
matches.push(item)
72+
if (isDirectory) {
73+
matches.push(name)
6774
}
6875
}
6976

0 commit comments

Comments
 (0)