✨ feat(vscode): 代码阅读的解析与模板直译核心

This commit is contained in:
cruldra committed 2026-09-24 21:16:21 +08:00
1 parent af56985d9a
commit 4d34b8c715
24 files changed
+2294

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
import type { Disposable, Event, Memento } from 'vscode'
import type { CodeReaderApi, LanguageAdapter, 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 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)
}
/** 当前语言插件覆盖的全部 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))
}
/** 选中的翻译: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()
}
}