mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(web): display Windows file search paths correctly (#1311)
* fix(web): display Windows file search paths correctly * fix(hub): scope path normalization to Windows
This commit is contained in:
@@ -90,4 +90,65 @@ describe('file search route', () => {
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes ripgrep path separators before deriving file names and directories', async () => {
|
||||
const session = {
|
||||
id: 'session-1',
|
||||
namespace: 'default',
|
||||
active: true,
|
||||
metadata: { path: 'C:\\project' }
|
||||
} as unknown as Session
|
||||
const engine = {
|
||||
resolveSessionAccess: () => ({ ok: true as const, sessionId: 'session-1', session }),
|
||||
runRipgrep: async () => ({
|
||||
success: true,
|
||||
stdout: 'src\\nested\\file.ts\nroot.ts\n'
|
||||
}),
|
||||
statFiles: async (_sessionId: string, paths: string[]) => ({
|
||||
success: true,
|
||||
entries: paths.map((path) => ({ path, size: 10, modified: 100 }))
|
||||
})
|
||||
} as unknown as Partial<SyncEngine>
|
||||
|
||||
const response = await buildApp(engine).request('/api/sessions/session-1/files?query=.ts')
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toEqual({
|
||||
success: true,
|
||||
files: [
|
||||
{ fileName: 'file.ts', filePath: 'src/nested', fullPath: 'src/nested/file.ts', fileType: 'file', size: 10, modified: 100 },
|
||||
{ fileName: 'root.ts', filePath: '', fullPath: 'root.ts', fileType: 'file', size: 10, modified: 100 },
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves backslashes in file names for non-Windows sessions', async () => {
|
||||
const session = {
|
||||
id: 'session-1',
|
||||
namespace: 'default',
|
||||
active: true,
|
||||
metadata: { path: '/project' }
|
||||
} as unknown as Session
|
||||
const engine = {
|
||||
resolveSessionAccess: () => ({ ok: true as const, sessionId: 'session-1', session }),
|
||||
runRipgrep: async () => ({
|
||||
success: true,
|
||||
stdout: 'src/file\\name.ts\n'
|
||||
}),
|
||||
statFiles: async (_sessionId: string, paths: string[]) => ({
|
||||
success: true,
|
||||
entries: paths.map((path) => ({ path, size: 10, modified: 100 }))
|
||||
})
|
||||
} as unknown as Partial<SyncEngine>
|
||||
|
||||
const response = await buildApp(engine).request('/api/sessions/session-1/files?query=.ts')
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toEqual({
|
||||
success: true,
|
||||
files: [
|
||||
{ fileName: 'file\\name.ts', filePath: 'src', fullPath: 'src/file\\name.ts', fileType: 'file', size: 10, modified: 100 },
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,6 +21,14 @@ const generatedImageSchema = z.object({
|
||||
imageId: z.string().min(1)
|
||||
})
|
||||
|
||||
function normalizeFileSearchPath(path: string): string {
|
||||
return path.replaceAll('\\', '/')
|
||||
}
|
||||
|
||||
function isWindowsSessionPath(path: string): boolean {
|
||||
return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith('\\\\')
|
||||
}
|
||||
|
||||
function parseBooleanParam(value: string | undefined): boolean | undefined {
|
||||
if (value === 'true') return true
|
||||
if (value === 'false') return false
|
||||
@@ -227,10 +235,14 @@ export function createGitRoutes(getSyncEngine: () => SyncEngine | null): Hono<We
|
||||
}
|
||||
|
||||
const stdout = result.stdout ?? ''
|
||||
const normalizePath = isWindowsSessionPath(sessionPath)
|
||||
? normalizeFileSearchPath
|
||||
: (path: string) => path
|
||||
const paths = stdout
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
.map(normalizePath)
|
||||
.slice(0, limit)
|
||||
|
||||
const metadataResult = await runRpc(() => engine.statFiles(sessionResult.sessionId, paths))
|
||||
|
||||
@@ -260,8 +260,7 @@ function SearchResultRow(props: {
|
||||
onOpen: () => void
|
||||
showDivider: boolean
|
||||
}) {
|
||||
const { t, locale } = useTranslation()
|
||||
const subtitle = getProjectRootLabel(props.file.filePath, t)
|
||||
const { locale } = useTranslation()
|
||||
const metadata = formatFileMetadata(props.file.size, props.file.modified, locale)
|
||||
const icon = props.file.fileType === 'file'
|
||||
? <FileIcon fileName={props.file.fileName} size={22} />
|
||||
@@ -275,11 +274,8 @@ function SearchResultRow(props: {
|
||||
>
|
||||
{icon}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate font-medium">{props.file.fileName}</div>
|
||||
<div className="flex min-w-0 items-center gap-2 text-xs text-[var(--app-hint)]">
|
||||
<span className="truncate">{subtitle}</span>
|
||||
{metadata ? <span className="shrink-0">{metadata}</span> : null}
|
||||
</div>
|
||||
<div className="truncate font-medium">{props.file.fullPath}</div>
|
||||
{metadata ? <div className="text-xs text-[var(--app-hint)]">{metadata}</div> : null}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user