Files
superwork/vscode/src/codeReader/registry.ts
T

97 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}
}