97 lines
3.7 KiB
TypeScript
97 lines
3.7 KiB
TypeScript
import type { Disposable, Event, Memento } from 'vscode'
|
||
import type { CodeReaderApi, LanguageAdapter, LibraryRule, Translator } from './contract'
|
||
|
||
/** globalState 里按 languageId 记选中的翻译插件 id:Record<languageId, translatorId>。 */
|
||
export const TRANSLATOR_CHOICE_KEY = 'superpowers.codeReader.translatorByLanguage'
|
||
|
||
interface Entry<T> { value: T }
|
||
|
||
/**
|
||
* 语言插件、翻译插件、库规则的注册表。内置实现和其他扩展都通过它注册(它本身就是对外的 CodeReaderApi)。
|
||
* 不依赖 vscode 运行时:Disposable 和事件都是结构兼容的普通对象,便于单元测试。
|
||
*/
|
||
export class CodeReaderRegistry implements CodeReaderApi {
|
||
private readonly adapters: Entry<LanguageAdapter>[] = []
|
||
private readonly translators: Entry<Translator>[] = []
|
||
private readonly rules: Entry<LibraryRule>[] = []
|
||
private readonly listeners = new Set<() => void>()
|
||
|
||
constructor(private readonly state: Pick<Memento, 'get' | 'update'>) {}
|
||
|
||
/** 注册 / 注销任何插件后触发。 */
|
||
readonly onDidChange: Event<void> = (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<Record<string, string>>(TRANSLATOR_CHOICE_KEY)?.[languageId]
|
||
return candidates.find(translator => translator.id === chosen) ?? candidates[0]
|
||
}
|
||
|
||
/** 按 languageId 记住选中的翻译。 */
|
||
async selectTranslator(languageId: string, translatorId: string): Promise<void> {
|
||
const choices = this.state.get<Record<string, string>>(TRANSLATOR_CHOICE_KEY) ?? {}
|
||
await this.state.update(TRANSLATOR_CHOICE_KEY, { ...choices, [languageId]: translatorId })
|
||
}
|
||
|
||
private add<T>(list: Entry<T>[], 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()
|
||
}
|
||
}
|