mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
import {
|
|
pluginCommandKeybindingActionId,
|
|
type PluginCommandAliasActionId
|
|
} from '../../shared/plugins/plugin-command-actions'
|
|
import { getKeybindingConflictIdentity, type KeybindingOverrides } from '../../shared/keybindings'
|
|
import type {
|
|
PluginCommandContribution,
|
|
PluginManifest
|
|
} from '../../shared/plugins/plugin-manifest'
|
|
import type { PluginKeybindingContribution } from '../../shared/plugins/plugin-content-pack-contributions'
|
|
import {
|
|
isInvalidDiscoveredPlugin,
|
|
type DiscoveredPlugin,
|
|
type ValidDiscoveredPlugin
|
|
} from './plugin-discovery'
|
|
|
|
export type PluginCommandKeybinding = {
|
|
key: string
|
|
when: 'global' | 'worktree'
|
|
}
|
|
|
|
export type PluginCommandRegistration = {
|
|
pluginKey: string
|
|
id: string
|
|
title: string
|
|
context: 'global' | 'worktree'
|
|
handler: { type: 'built-in'; action: PluginCommandAliasActionId } | { type: 'worker' }
|
|
keybindings: PluginCommandKeybinding[]
|
|
}
|
|
|
|
type CommandOwner = {
|
|
pluginKey: string
|
|
key: string
|
|
}
|
|
|
|
export class PluginCommandRegistry {
|
|
private active: PluginCommandRegistration[] = []
|
|
private readonly previews = new Map<string, PluginCommandRegistration[]>()
|
|
private readonly errors = new Map<string, string>()
|
|
|
|
list(): readonly PluginCommandRegistration[] {
|
|
return this.active
|
|
}
|
|
|
|
preview(pluginKey: string): readonly PluginCommandRegistration[] {
|
|
return this.previews.get(pluginKey) ?? []
|
|
}
|
|
|
|
error(pluginKey: string): string | null {
|
|
return this.errors.get(pluginKey) ?? null
|
|
}
|
|
|
|
reconcile(
|
|
discovered: readonly DiscoveredPlugin[],
|
|
isApproved: (plugin: ValidDiscoveredPlugin) => boolean,
|
|
overrides: KeybindingOverrides = {},
|
|
platform: NodeJS.Platform = process.platform
|
|
): void {
|
|
const candidates = discovered.filter(
|
|
(plugin): plugin is ValidDiscoveredPlugin =>
|
|
!isInvalidDiscoveredPlugin(plugin) && plugin.manifest.contributes.commands.length > 0
|
|
)
|
|
const registrations = candidates.map((plugin) => ({
|
|
pluginKey: plugin.pluginKey,
|
|
approved: isApproved(plugin),
|
|
commands: registrationsForManifest(plugin.pluginKey, plugin.manifest)
|
|
}))
|
|
|
|
this.previews.clear()
|
|
this.errors.clear()
|
|
for (const plugin of registrations) {
|
|
this.previews.set(plugin.pluginKey, plugin.commands)
|
|
}
|
|
|
|
const approved = registrations.filter((plugin) => plugin.approved)
|
|
const chordOwners = new Map<string, CommandOwner[]>()
|
|
for (const plugin of approved) {
|
|
for (const command of plugin.commands) {
|
|
for (const keybinding of effectiveCommandKeybindings(command, overrides)) {
|
|
const identity = getKeybindingConflictIdentity(keybinding.key, platform)
|
|
const owners = chordOwners.get(identity) ?? []
|
|
owners.push({
|
|
pluginKey: plugin.pluginKey,
|
|
key: keybinding.key
|
|
})
|
|
chordOwners.set(identity, owners)
|
|
}
|
|
}
|
|
}
|
|
|
|
const conflicted = new Set<string>()
|
|
for (const owners of chordOwners.values()) {
|
|
// Global/worktree are the only contexts, so all owners of the same chord overlap.
|
|
if (owners.length < 2) {
|
|
continue
|
|
}
|
|
for (const owner of owners) {
|
|
conflicted.add(owner.pluginKey)
|
|
this.errors.set(
|
|
owner.pluginKey,
|
|
`plugin keybinding ${owner.key} conflicts with another plugin`
|
|
)
|
|
}
|
|
}
|
|
|
|
this.active = approved
|
|
.filter((plugin) => !conflicted.has(plugin.pluginKey))
|
|
.flatMap((plugin) => plugin.commands)
|
|
}
|
|
}
|
|
|
|
function effectiveCommandKeybindings(
|
|
command: PluginCommandRegistration,
|
|
overrides: KeybindingOverrides
|
|
): PluginCommandKeybinding[] {
|
|
const override = overrides[pluginCommandKeybindingActionId(command.pluginKey, command.id)]
|
|
if (!Array.isArray(override)) {
|
|
return command.keybindings
|
|
}
|
|
return override.map((key) => ({ key, when: command.context }))
|
|
}
|
|
|
|
function registrationsForManifest(
|
|
pluginKey: string,
|
|
manifest: PluginManifest
|
|
): PluginCommandRegistration[] {
|
|
const bindingsByCommand = new Map<string, PluginKeybindingContribution[]>()
|
|
for (const binding of manifest.contributes.keybindings) {
|
|
const commandId = binding.command
|
|
const bindings = bindingsByCommand.get(commandId)
|
|
if (bindings) {
|
|
bindings.push(binding)
|
|
} else {
|
|
bindingsByCommand.set(commandId, [binding])
|
|
}
|
|
}
|
|
return manifest.contributes.commands.map((command) => ({
|
|
pluginKey,
|
|
id: command.id,
|
|
title: command.title,
|
|
context: command.context ?? 'global',
|
|
handler:
|
|
command.action === undefined
|
|
? { type: 'worker' as const }
|
|
: { type: 'built-in' as const, action: command.action as PluginCommandAliasActionId },
|
|
keybindings: keybindingsForCommand(command, bindingsByCommand.get(command.id) ?? [])
|
|
}))
|
|
}
|
|
|
|
function keybindingsForCommand(
|
|
command: PluginCommandContribution,
|
|
keybindings: readonly PluginKeybindingContribution[]
|
|
): PluginCommandKeybinding[] {
|
|
return keybindings.map((keybinding) => ({
|
|
key: keybinding.key,
|
|
when: keybinding.when ?? command.context ?? 'global'
|
|
}))
|
|
}
|