✨ feat(vscode): 代码阅读的解析与模板直译核心
This commit is contained in:
1 parent
af56985d9a
commit
4d34b8c715
24 files changed
+2294
No files matched your search
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user