feat(file-icons): FileIconDescriptor 支持 names / folderNames 与 open 参数
图标主题(VSCode 的 fileNames / folderNames 半边)需要按**文件名**和**目录名** 着色,扩展名规则表达不了 `package.json` / `Dockerfile` / `node_modules`。 - `names?: readonly string[]`:精确 basename(大小写不敏感),命中优先于扩展名。 - `folderNames?: readonly string[]`:精确目录 basename,命中优先于保留扩展名; 只认领列出的目录,未列出的目录仍走 `'folder'`/`'folder-open'` 或内置 glyph。 - `exts` 变为可选:省略 = 没有扩展名规则(`names`-only 注册不再是 catch-all); `exts: []` 仍是全局默认 catch-all,语义不变。 - `icon(path, size, open?)`:目录行的展开态传入工厂,一条注册即可渲染开/合两态。 - `matchFileIcon` 先 names 后 exts;`matchFolderIcon(open, name?)` 先 folderNames 后保留扩展名(新参数可选,旧调用不变)。 - 指南 §7 同步:字段、匹配顺序、四例注册(names / folderNames / 全目录 / catch-all)。 测试:`tests/file-icons.spec.tsx` 19 → 23(names 大小写、name 优先于 ext、 folderNames 只认领列出目录、open 传入工厂);`tests/consumer-types.ts` 补新字段。
This commit is contained in:
@@ -132,9 +132,11 @@ service.openFile({ sessionId: 's1', cwd: '/p' }, '/p/a.csv', 'Data')
|
||||
const icon: FileIconDescriptor = {
|
||||
id: 'my-plugin:icons',
|
||||
exts: ['csv', FOLDER_EXT, FOLDER_OPEN_EXT],
|
||||
names: ['package.json'],
|
||||
folderNames: ['node_modules'],
|
||||
priority: 5,
|
||||
icon: (path: string, size: number) => {
|
||||
void path; void size
|
||||
icon: (path: string, size: number, open?: boolean) => {
|
||||
void path; void size; void open
|
||||
return null
|
||||
},
|
||||
}
|
||||
@@ -142,6 +144,7 @@ service.registerFileIcon(icon)
|
||||
service.getFileIcons()
|
||||
service.matchFileIcon('/p/a.csv')
|
||||
service.matchFolderIcon(true)
|
||||
service.matchFolderIcon(true, 'node_modules')
|
||||
service.fileIcon('/p/a.csv', 14)
|
||||
service.folderIcon('/p', true, 14)
|
||||
|
||||
|
||||
@@ -172,6 +172,57 @@ describe('fileIcon resolver chain (specific → builtin → catch-all → VscFil
|
||||
})
|
||||
})
|
||||
|
||||
describe('name matching (names / folderNames — the icon-theme half)', () => {
|
||||
it('a names rule claims the exact basename, case-insensitively', () => {
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
service.registerFileIcon({ id: 'npm', names: ['package.json'], icon: marker })
|
||||
expect(service.matchFileIcon('/w/package.json')?.id).toBe('npm')
|
||||
expect(service.matchFileIcon('/w/Package.JSON')?.id).toBe('npm')
|
||||
expect(service.matchFileIcon('/w/pkg.json')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('a name rule outranks an extension rule and the built-in glyph', () => {
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
service.registerFileIcon({ id: 'by-ext', exts: ['json'], icon: markerB })
|
||||
service.registerFileIcon({ id: 'by-name', names: ['package.json'], icon: marker })
|
||||
expect(service.matchFileIcon('/w/package.json')?.id).toBe('by-name')
|
||||
expect(glyphOf(service.fileIcon('/w/package.json', 14))).toBe('span')
|
||||
// Other json files still take the extension rule.
|
||||
expect(glyphOf(service.fileIcon('/w/tsconfig.json', 14))).toBe('b')
|
||||
})
|
||||
|
||||
it('a folderNames rule claims only the directories it names, and outranks the reserved exts', () => {
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
service.registerFileIcon({ id: 'all-dirs', exts: ['folder', 'folder-open'], icon: markerB })
|
||||
service.registerFileIcon({ id: 'named', folderNames: ['node_modules'], icon: marker })
|
||||
expect(service.matchFolderIcon(false, 'node_modules')?.id).toBe('named')
|
||||
expect(service.matchFolderIcon(false, 'NODE_MODULES')?.id).toBe('named')
|
||||
// An unnamed directory still takes the reserved-ext registration.
|
||||
expect(service.matchFolderIcon(false, 'src')?.id).toBe('all-dirs')
|
||||
// A descriptor with folderNames only never claims unnamed directories.
|
||||
const solo = createBetterSidebarService(createSidebarStore())
|
||||
solo.registerFileIcon({ id: 'named', folderNames: ['src'], icon: marker })
|
||||
expect(solo.matchFolderIcon(false, 'lib')).toBeUndefined()
|
||||
expect(glyphOf(solo.folderIcon('/w/lib', false, 14))).toBe(VscFolder)
|
||||
})
|
||||
|
||||
it('the folder factory receives the open flag so one descriptor renders both states', () => {
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
const seen: (boolean | undefined)[] = []
|
||||
service.registerFileIcon({
|
||||
id: 'stateful',
|
||||
folderNames: ['src'],
|
||||
icon: (_path, _size, open) => {
|
||||
seen.push(open)
|
||||
return open === true ? marker() : markerB()
|
||||
},
|
||||
})
|
||||
expect(glyphOf(service.folderIcon('/w/src', false, 14))).toBe('b')
|
||||
expect(glyphOf(service.folderIcon('/w/src', true, 14))).toBe('span')
|
||||
expect(seen).toEqual([false, true])
|
||||
})
|
||||
})
|
||||
|
||||
describe('folderIcon resolver (registered folder/folder-open → builtin glyphs)', () => {
|
||||
it('unregistered directories show the builtin glyphs', () => {
|
||||
const service = createBetterSidebarService(createSidebarStore())
|
||||
|
||||
Reference in New Issue
Block a user