import { Autowired, Injectable } from '@opensumi/di'; import { Command, CommandHandler, CommandRegistry, CommandService, EDITOR_COMMANDS, Emitter, HANDLER_NOT_FOUND, ILogger, IReporterService, MonacoOverrideServiceRegistry, REPORT_NAME, ServiceNames, Uri, memoize, } from '@opensumi/ide-core-browser'; import { DELEGATE_COMMANDS } from '@opensumi/ide-monaco'; import { EditorExtensionsRegistry, ICommandEvent, ICommandService, IMonacoActionRegistry, IMonacoCommandService, IMonacoCommandsRegistry, CommandsRegistry as MonacoCommandsRegistry, MonacoEditorCommandHandler, } from '@opensumi/ide-monaco/lib/browser/contrib/command'; import { URI } from '@opensumi/ide-monaco/lib/browser/monaco-api'; import { StandaloneServices } from '@opensumi/ide-monaco/lib/browser/monaco-api/services'; import { Event, ICodeEditor, IEvent } from '@opensumi/ide-monaco/lib/browser/monaco-api/types'; import { MonacoCodeService } from '../../editor.override'; import { EditorCollectionService, WorkbenchEditorService } from '../../types'; /** * vscode 会有一些别名 command,如果直接执行这些别名 command 会报错,做一个转换 */ const MonacoCommandAlias = { 'editor.action.smartSelect.grow': 'editor.action.smartSelect.expand', cursorWordPartStartLeft: 'cursorWordPartLeft', cursorWordPartStartLeftSelect: 'cursorWordPartLeftSelect', 'editor.action.previewDeclaration': 'editor.action.peekDefinition', 'editor.action.openDeclarationToTheSide': 'editor.action.revealDefinitionAside', 'editor.action.goToDeclaration': 'editor.action.revealDefinition', }; /** * monaco 命令分两种 * 一种命令不需要带参数,是封装过的命令,即为 action * 一种是正常命令,执行可以带参数 */ export enum MonacoCommandType { ACTION, COMMAND, } export type MonacoCommand = Command & { type: MonacoCommandType }; @Injectable() export class MonacoCommandService implements IMonacoCommandService { _serviceBrand: undefined; private _onDidExecuteCommand: Emitter = new Emitter(); onDidExecuteCommand: Event = this._onDidExecuteCommand.event; private delegate: ICommandService; /** * 事件触发器,在执行命令的时候会触发 * @type {Emitter} * @memberof MonacoCommandService */ _onWillExecuteCommand: Emitter = new Emitter(); @Autowired(ILogger) private logger: ILogger; @Autowired(CommandRegistry) commandRegistry: CommandRegistry; @Autowired(CommandService) commandService: CommandService; @Autowired(IReporterService) reporterService: IReporterService; /** * 设置委托对象 * @param delegate 真正要执行 monaco 内部 command 的 commandSerice */ setDelegate(delegate: ICommandService) { this.delegate = delegate; } get onWillExecuteCommand(): IEvent { return this._onWillExecuteCommand.event; } /** * 执行命令 * 先去全局 commands 里找,若没有尝试执行 delegate 的 command * @param commandId * @param args */ async executeCommand(commandId: string, ...args: any[]): Promise { this.logger.debug('command: ' + commandId); this._onWillExecuteCommand.fire({ commandId, args }); try { const res = await this.commandService.executeCommand(commandId, ...args); this._onDidExecuteCommand.fire({ commandId, args }); return res; } catch (err) { // 如果不是当前命令的 handler 未找到直接抛错,否则执行 delegate 逻辑 if (err?.name !== `${HANDLER_NOT_FOUND}:${commandId}`) { throw err; } } if (this.delegate) { const res = this.delegate.executeCommand( MonacoCommandAlias[commandId] ? MonacoCommandAlias[commandId] : commandId, ...args, ); this._onDidExecuteCommand.fire({ commandId, args }); return res; } this.reporterService.point(REPORT_NAME.NOT_FOUND_COMMAND, commandId); return Promise.reject(new Error(`command '${commandId}' not found`)); } } @Injectable() export class MonacoCommandRegistry implements IMonacoCommandsRegistry { @Autowired(CommandRegistry) private commands: CommandRegistry; @Autowired(WorkbenchEditorService) private workbenchEditorService: WorkbenchEditorService; @Autowired(EditorCollectionService) private editorCollectionService: EditorCollectionService; @Autowired(MonacoOverrideServiceRegistry) private readonly overrideServiceRegistry: MonacoOverrideServiceRegistry; /** * 校验 command id 是否是 monaco id * @param command 要校验的 id * @returns 若找到则为转换过 monaco id,否则为 undefined */ validate(command: string): string | undefined { return this.commands.getRawCommand(command)?.id; } /** * 注册 monaco 命令 * 命令 id 会统一加入 monaco 前缀 * monaco handler 会注入当前 editor 参数 * @param command 注册的命令 * @param handler 命令处理函数 */ registerCommand(command: Command, handler: MonacoEditorCommandHandler): void { this.commands.registerCommand(command, this.newHandler(handler)); } /** * 注册处理函数函数 * monaco handler 会注入当前 editor 参数 * @param command 命令 id * @param handler 命令处理函数 */ registerHandler(commandID: string, handler: MonacoEditorCommandHandler): void { this.commands.registerHandler(commandID, this.newHandler(handler)); } /** * 包装 monaco 命令处理函数为内部处理函数 * @param monacoHandler 要处理的 monaco 命令处理函数 */ protected newHandler(monacoHandler: MonacoEditorCommandHandler): CommandHandler { return { execute: (...args) => this.execute(monacoHandler, ...args), }; } /** * 给 monacoHandler 传递 editor 参数 * @param monacoHandler 要处理的 monaco 命令函数 * @param args 要透传的参数 */ protected execute(monacoHandler: MonacoEditorCommandHandler, ...args: any[]): any { const editor = this.getActiveCodeEditor(); if (editor) { return Promise.resolve(monacoHandler.execute(editor, ...args)); } return Promise.resolve(); } get codeEditorService(): MonacoCodeService | undefined { return this.overrideServiceRegistry.getRegisteredService(ServiceNames.CODE_EDITOR_SERVICE); } /** * 获取当前活动的编辑器 * 此处的活动编辑器和 workbenchEditorService.currentEditor 的概念不同,对于diffEditor,需要获取确实的那个editor而不是modifiedEditor */ public getActiveCodeEditor(): ICodeEditor | undefined { // 先从 monaco 内部获取到当前 focus 的 editor const editor = this.codeEditorService?.getFocusedCodeEditor(); if (editor) { return editor; } // 如果获取不到再从 editor-collection 的焦点追踪,contextMenu追踪中取 if (this.editorCollectionService.currentEditor) { return this.editorCollectionService.currentEditor.monacoEditor; } // 使用当前 editorGroup.editor 兜底 const editorGroup = this.workbenchEditorService.currentEditorGroup; if (editorGroup) { const editor = editorGroup.currentOrPreviousFocusedEditor || editorGroup.currentEditor; if (editor) { return editor.monacoEditor; } } } } @Injectable() export class MonacoActionRegistry implements IMonacoActionRegistry { private static COMMON_ACTIONS = new Map([ [DELEGATE_COMMANDS.UNDO, EDITOR_COMMANDS.UNDO.id], [DELEGATE_COMMANDS.REDO, EDITOR_COMMANDS.REDO.id], [DELEGATE_COMMANDS.SELECT_ALL, EDITOR_COMMANDS.SELECT_ALL.id], ]); private static CONVERT_MONACO_COMMAND_ARGS = new Map any[]>([ ['editor.action.showReferences', (uri, ...args) => [URI.parse(uri), ...args]], ['editor.action.goToLocations', (uri, ...args) => [URI.parse(uri), ...args]], ]); private static CONVERT_MONACO_ACTIONS_TO_CONTRIBUTION_ID = new Map([ ['editor.action.rename', 'editor.contrib.renameController'], ]); /** * 要排除注册的 Action * * @protected * @memberof MonacoActionModule */ protected static readonly EXCLUDE_ACTIONS = [ 'setContext', 'editor.action.quickCommand', 'editor.action.quickOutline', 'editor.action.toggleHighContrast', 'editor.action.gotoLine', ]; @Autowired() monacoCommandRegistry: MonacoCommandRegistry; @Autowired(MonacoOverrideServiceRegistry) private readonly overrideServiceRegistry: MonacoOverrideServiceRegistry; @memoize get globalInstantiationService() { const codeEditorService = this.overrideServiceRegistry.getRegisteredService(ServiceNames.CODE_EDITOR_SERVICE); const textModelService = this.overrideServiceRegistry.getRegisteredService(ServiceNames.TEXT_MODEL_SERVICE); const contextKeyService = this.overrideServiceRegistry.getRegisteredService(ServiceNames.CONTEXT_KEY_SERVICE); const globalInstantiationService = StandaloneServices.initialize({ codeEditorService, textModelService, contextKeyService, }); return globalInstantiationService; } @memoize get monacoEditorRegistry() { return EditorExtensionsRegistry; } @memoize get monacoCommands() { return MonacoCommandsRegistry.getCommands(); } registerMonacoActions() { const editorActions = new Map( Array.from(this.monacoEditorRegistry.getEditorActions()).map(({ id, label, alias }) => [ id, { label, alias, }, ]), ); for (const id of this.monacoCommands.keys()) { if (MonacoActionRegistry.EXCLUDE_ACTIONS.includes(id)) { continue; } const data: { label?: string; alias?: string } = editorActions.get(id) ?? {}; const handler = this.actAndComHandler(editorActions, id); this.monacoCommandRegistry.registerCommand( { id, label: data.label, labelLocalized: data.label && data.alias ? { alias: data.alias, localized: data.label, raw: data.label, } : undefined, }, handler, ); // 将 monaco 命令处理函数代理到有 label 的新的命令上 const command = MonacoActionRegistry.COMMON_ACTIONS.get(id); if (command) { this.monacoCommandRegistry.registerHandler(command, handler); } } } /** * monaco 内部有些 contribution 既注册了 actions 又注册了 commands,在这里优先调取 commands */ private actAndComHandler( actions: Map< string, { label: string; alias: string; } >, id: string, ): MonacoEditorCommandHandler { if (MonacoActionRegistry.CONVERT_MONACO_ACTIONS_TO_CONTRIBUTION_ID.has(id)) { const toConver = MonacoActionRegistry.CONVERT_MONACO_ACTIONS_TO_CONTRIBUTION_ID.get(id)!; if (this.monacoEditorRegistry.getSomeEditorContributions([toConver]).length > 0) { return this.newCommandHandler(id); } } return actions.has(id) ? this.newActionHandler(id) : this.newCommandHandler(id); } /** * 是否是 _execute 开头的 monaco 命令 */ private isInternalExecuteCommand(commandId: string) { return commandId.startsWith('_execute'); } /** * monaco 内部会判断 uri 执行是否是 Uri 实例,执行改类命令统一转换一下 * @param args */ private processInternalCommandArgument(commandId: string, args: any[] = []): any[] { if (this.isInternalExecuteCommand(commandId)) { return args.map((arg) => (arg instanceof Uri ? URI.revive(arg) : arg)); } else if (MonacoActionRegistry.CONVERT_MONACO_COMMAND_ARGS.has(commandId)) { return MonacoActionRegistry.CONVERT_MONACO_COMMAND_ARGS.get(commandId)!(...args); } return args; } /** * 调用 monaco 内部 _commandService 执行命令 * 实际执行的就是 MonacoCommandService * @param commandId 命令名称 */ protected newCommandHandler(commandId: string): MonacoEditorCommandHandler { return { execute: (editor, ...args) => { if (!editor) { return; } const editorCommand = !!this.monacoEditorRegistry.getEditorCommand(commandId) || !( this.isInternalExecuteCommand(commandId) || commandId === 'setContext' || MonacoActionRegistry.COMMON_ACTIONS.has(commandId) ); const instantiationService = editorCommand ? editor && editor['_instantiationService'] : this.globalInstantiationService; if (!instantiationService) { return; } const commandArgs = this.processInternalCommandArgument(commandId, args); return instantiationService.invokeFunction(this.monacoCommands.get(commandId)?.handler, ...commandArgs); }, }; } /** * 包装 action 为命令处理函数 * 调用 getAction 执行 run 命令 * @param id action id */ protected newActionHandler(id: string): MonacoEditorCommandHandler { return { execute: (editor, ...args) => { const action = editor.getAction(id); if (action && action.isSupported()) { return this.runAction(id, editor, ...args); } }, }; } /** * 执行 action * @param id 要执行的 action * @param editor 执行 action 的 editor,默认为当前 editor */ protected runAction(id: string, editor: ICodeEditor, ...args: any[]): Promise { if (editor) { const action = editor.getAction(id); if (action) { return action.run(...args); } } return Promise.resolve(); } /** * 生成键盘处理函数 * @param action 对应 action */ protected newKeyboardHandler(action: string): MonacoEditorCommandHandler { return { execute: (editor, ...args) => editor.trigger('keyboard', action, args), }; } }