import type { Disposable, Event, Memento } from 'vscode' import type { CodeReaderApi, LanguageAdapter, LibraryRule, Translator } from './contract' /** globalState 里按 languageId 记选中的翻译插件 id:Record。 */ export const TRANSLATOR_CHOICE_KEY = 'superpowers.codeReader.translatorByLanguage' interface Entry { value: T } /** * 语言插件、翻译插件、库规则的注册表。内置实现和其他扩展都通过它注册(它本身就是对外的 CodeReaderApi)。 * 不依赖 vscode 运行时:Disposable 和事件都是结构兼容的普通对象,便于单元测试。 */ export class CodeReaderRegistry implements CodeReaderApi { private readonly adapters: Entry[] = [] private readonly translators: Entry[] = [] private readonly rules: Entry[] = [] private readonly listeners = new Set<() => void>() constructor(private readonly state: Pick) {} /** 注册 / 注销任何插件后触发。 */ readonly onDidChange: Event = (listener, thisArgs?, disposables?) => { const bound = (): void => { listener.call(thisArgs, undefined) } this.listeners.add(bound) const disposable = { dispose: () => this.listeners.delete(bound) } disposables?.push(disposable) return disposable } registerLanguage(adapter: LanguageAdapter): Disposable { return this.add(this.adapters, adapter) } registerTranslator(translator: Translator): Disposable { return this.add(this.translators, translator) } registerLibraryRule(rule: LibraryRule): Disposable { return this.add(this.rules, rule) } /** 当前语言插件覆盖的全部 languageId(去重、排序),给 when 子句用。 */ languageIds(): string[] { return [...new Set(this.adapters.flatMap(entry => entry.value.languageIds))].sort() } /** 某 languageId 的语言插件;多个插件覆盖同一语言时后注册的生效。 */ languageFor(languageId: string): LanguageAdapter | undefined { return [...this.adapters].reverse().find(entry => entry.value.languageIds.includes(languageId))?.value } /** 某 languageId 可选的翻译插件,按注册顺序。 */ translatorsFor(languageId: string): Translator[] { return this.translators.map(entry => entry.value).filter(translator => translator.languageIds.includes(languageId)) } /** 某 languageId 的库规则,按注册顺序:几个规则都认同一块时先注册的算。 */ rulesFor(languageId: string): LibraryRule[] { return this.rules.map(entry => entry.value).filter(rule => rule.languageIds.includes(languageId)) } /** 选中的翻译:globalState 里记的那个还注册着就用它,否则用第一个。 */ selectedTranslator(languageId: string): Translator | undefined { const candidates = this.translatorsFor(languageId) const chosen = this.state.get>(TRANSLATOR_CHOICE_KEY)?.[languageId] return candidates.find(translator => translator.id === chosen) ?? candidates[0] } /** 按 languageId 记住选中的翻译。 */ async selectTranslator(languageId: string, translatorId: string): Promise { const choices = this.state.get>(TRANSLATOR_CHOICE_KEY) ?? {} await this.state.update(TRANSLATOR_CHOICE_KEY, { ...choices, [languageId]: translatorId }) } private add(list: Entry[], value: T): Disposable { const entry = { value } list.push(entry) this.fire() return { dispose: () => { const index = list.indexOf(entry) if (index < 0) return list.splice(index, 1) this.fire() }, } } private fire(): void { for (const listener of [...this.listeners]) listener() } }