import { Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di'; import { AppConfig, ClientAppContribution, CommandContribution, CommandRegistry, CommandService, CorePreferences, Disposable, Domain, EDITOR_COMMANDS, FILE_COMMANDS, IClientApp, IClipboardService, IContextKeyService, IEditorExtensionContribution, IEventBus, IOpenerService, IPreferenceSettingsService, IQuickInputService, IQuickOpenHandlerRegistry, KeybindingContribution, KeybindingRegistry, MonacoContribution, MonacoOverrideServiceRegistry, MonacoService, OpenerContribution, PreferenceService, PrefixQuickOpenService, QuickOpenContribution, QuickPickItem, QuickPickService, SUPPORTED_ENCODINGS, Schemes, ServiceNames, URI, formatLocalize, getIcon, getLanguageIdFromMonaco, localize, } from '@opensumi/ide-core-browser'; import { ComponentContribution, ComponentRegistry } from '@opensumi/ide-core-browser/lib/layout'; import { IMenuRegistry, MenuContribution, MenuId } from '@opensumi/ide-core-browser/lib/menu/next'; import { AbstractContextMenuService } from '@opensumi/ide-core-browser/lib/menu/next/menu.interface'; import { ICtxMenuRenderer } from '@opensumi/ide-core-browser/lib/menu/next/renderer/ctxmenu/base'; import { IRelaxedOpenMergeEditorArgs } from '@opensumi/ide-core-browser/lib/monaco/merge-editor-widget'; import { IDisposable, ILogger, PreferenceScope, UriComponents, isWindows } from '@opensumi/ide-core-common'; import { MergeEditorService } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/merge-editor.service'; import { ITextmateTokenizer, ITextmateTokenizerService } from '@opensumi/ide-monaco/lib/browser/contrib/tokenizer'; import { EOL } from '@opensumi/ide-monaco/lib/browser/monaco-api/types'; import * as monaco from '@opensumi/ide-monaco/lib/common/common'; import { EditorContextKeys } from '@opensumi/monaco-editor-core/esm/vs/editor/common/editorContextKeys'; import { IFormattingEditProviderSelector } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/format/browser/format'; import { ContextKeyExpr } from '@opensumi/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from '@opensumi/monaco-editor-core/esm/vs/platform/instantiation/common/descriptors'; import { DIFF_SCHEME, Direction, EditorGroupSplitAction, IDiffResource, IDocPersistentCacheProvider, IEditor, ILanguageService, IResourceOpenOptions, ResourceService, SaveReason, WorkbenchEditorService, } from '../common'; import { AUTO_SAVE_MODE } from '../common/editor'; import { MultiDiffEditorItem } from '../common/multi-diff'; import { MonacoTextModelService } from './doc-model/override'; import { IEditorDocumentModelContentRegistry, IEditorDocumentModelService } from './doc-model/types'; import { EditorOpener } from './editor-opener'; import { MonacoCodeService, MonacoContextViewService } from './editor.override'; import { EditorStatusBarService } from './editor.status-bar.service'; import { EditorView } from './editor.view'; import { DocumentFormatService } from './format/format.service'; import { FormattingSelector } from './format/formatter-selector'; import { EditorHistoryService } from './history'; import { EditorContextMenuController } from './menu/editor.context'; import { MultiDiffResolver } from './multi-diff/multi-diff-resolver'; import { NavigationMenuContainer } from './navigation.view'; import { GoToLineQuickOpenHandler } from './quick-open/go-to-line'; import { WorkspaceSymbolQuickOpenHandler } from './quick-open/workspace-symbol-quickopen'; import { BrowserEditorContribution, EditorGroupsResetSizeEvent, IEditorFeatureRegistry } from './types'; import { EditorSuggestWidgetContribution } from './view/suggest-widget'; import { EditorTopPaddingContribution } from './view/topPadding'; import { EditorGroup, WorkbenchEditorServiceImpl } from './workbench-editor.service'; interface ResourceArgs { group: EditorGroup; uri: URI; } @Domain( CommandContribution, ClientAppContribution, KeybindingContribution, MonacoContribution, ComponentContribution, MenuContribution, OpenerContribution, QuickOpenContribution, ) export class EditorContribution extends Disposable implements CommandContribution, ClientAppContribution, KeybindingContribution, MonacoContribution, ComponentContribution, MenuContribution, OpenerContribution, QuickOpenContribution { @Autowired(INJECTOR_TOKEN) injector: Injector; @Autowired(AppConfig) private readonly appConfig: AppConfig; @Autowired(WorkbenchEditorService) private workbenchEditorService: WorkbenchEditorServiceImpl; @Autowired() private editorStatusBarService: EditorStatusBarService; @Autowired(QuickPickService) private quickPickService: QuickPickService; @Autowired(ILanguageService) private languagesService: ILanguageService; @Autowired(IEditorDocumentModelService) private editorDocumentModelService: IEditorDocumentModelService; @Autowired(IDocPersistentCacheProvider) private cacheProvider: IDocPersistentCacheProvider; @Autowired() private historyService: EditorHistoryService; @Autowired() private monacoService: MonacoService; @Autowired(IOpenerService) private readonly openerService: IOpenerService; @Autowired(ILogger) private readonly logger: ILogger; @Autowired() private editorOpener: EditorOpener; @Autowired(IClipboardService) private readonly clipboardService: IClipboardService; @Autowired() private readonly workspaceSymbolQuickOpenHandler: WorkspaceSymbolQuickOpenHandler; @Autowired(PrefixQuickOpenService) private readonly prefixQuickOpenService: PrefixQuickOpenService; @Autowired() private readonly goToLineQuickOpenHandler: GoToLineQuickOpenHandler; @Autowired(PreferenceService) private readonly preferenceService: PreferenceService; @Autowired(ITextmateTokenizer) private readonly textmateService: ITextmateTokenizerService; @Autowired(CorePreferences) private readonly corePreferences: CorePreferences; @Autowired(MergeEditorService) private readonly mergeEditorService: MergeEditorService; @Autowired(IEditorDocumentModelContentRegistry) contentRegistry: IEditorDocumentModelContentRegistry; @Autowired(IQuickInputService) private readonly quickInputService: IQuickInputService; @Autowired(CommandService) private readonly commandService: CommandService; @Autowired(ResourceService) private readonly resourceService: ResourceService; registerComponent(registry: ComponentRegistry) { registry.register('@opensumi/ide-editor', { id: 'ide-editor', component: EditorView, }); registry.register('breadcrumb-menu', { id: 'breadcrumb-menu', component: NavigationMenuContainer, }); } registerOverrideService(registry: MonacoOverrideServiceRegistry): void { const codeEditorService = this.injector.get(MonacoCodeService); // Monaco Editor ContextKeyService // 经过这个Override, 所有编辑器的 contextKeyService 都是 editorContextKeyService 的孩子 const globalContextKeyService: IContextKeyService = this.injector.get(IContextKeyService); const editorContextKeyService = globalContextKeyService.createScoped(); this.workbenchEditorService.setEditorContextKeyService(editorContextKeyService); registry.registerOverrideService(ServiceNames.CONTEXT_KEY_SERVICE, editorContextKeyService.contextKeyService); // Monaco CodeEditorService registry.registerOverrideService(ServiceNames.CODE_EDITOR_SERVICE, codeEditorService); // Monaco ContextViewService registry.registerOverrideService( ServiceNames.CONTEXT_VIEW_SERVICE, new MonacoContextViewService(codeEditorService), ); // Monaco TextModelService registry.registerOverrideService(ServiceNames.TEXT_MODEL_SERVICE, this.injector.get(MonacoTextModelService)); } @Autowired(AbstractContextMenuService) private readonly contextMenuService: AbstractContextMenuService; @Autowired(IContextKeyService) private readonly globalContextKeyService: IContextKeyService; @Autowired(ICtxMenuRenderer) private readonly contextMenuRenderer: ICtxMenuRenderer; @Autowired(MultiDiffResolver) private readonly multiDiffResolver: MultiDiffResolver; registerMonacoDefaultFormattingSelector(register: (selector: IFormattingEditProviderSelector) => IDisposable): void { const formatSelector = this.injector.get(FormattingSelector); this.addDispose(register(formatSelector.selectFormatter.bind(formatSelector))); } registerEditorExtensionContribution(register: IEditorExtensionContribution): void { register( EditorContextMenuController.ID, /** * 如果使用 opensumi/di 的 Injectable 装饰,在内部会无法被 monaco 实例化 * 这里借用 monaco 内置的 DI 注入方式,将依赖的 Services 通过参数传递进去 * 在内部重新实例化时会拼接两份参数,对于 EditorContextMenuController * monaco 将会自动补充另一个 editor 实例作为参数 * ref: https://github.com/microsoft/vscode/blob/3820f34dcabb3060715e24abfd05ec2455e71786/src/vs/platform/instantiation/common/instantiationService.ts#L73 */ new SyncDescriptor(EditorContextMenuController, [ this.contextMenuService, this.globalContextKeyService, this.contextMenuRenderer, ]), ); } protected getMimeForMode(langId: string): string | undefined { for (const language of this.textmateService.getLanguages()) { if (language.id === langId && language.mimetypes) { return language.mimetypes[0]; } } return undefined; } registerPlatformLanguageAssociations(register) { const association = this.corePreferences['files.associations']; if (!association) { return; } const mimeAssociation = Object.keys(association).map((filepattern) => ({ id: association[filepattern], filepattern, mime: this.getMimeForMode(association[filepattern]) || `text/x-${association.id}`, })); register(mimeAssociation); } protected async interceptOpen(uri: URI) { try { await this.openerService.open(uri); return true; } catch (e) { this.logger.error(e); return false; } } onWillStop(app: IClientApp) { if (!this.appConfig.isElectronRenderer) { return this.workbenchEditorService.hasDirty() || !this.cacheProvider.isFlushed(); } } // editorTitle出现了参数不统一。。 private extractGroupAndUriFromArgs( resource: ResourceArgs | URI, editorGroup?: EditorGroup, ): { group?: EditorGroup; uri?: URI; } { let group: EditorGroup; let uri: URI; if (resource instanceof URI) { group = editorGroup || this.workbenchEditorService.currentEditorGroup; uri = resource || (group && group.currentResource && group.currentResource.uri); } else { const resourceArgs = resource || {}; group = resourceArgs.group || this.workbenchEditorService.currentEditorGroup; uri = resourceArgs.uri || (group && group.currentResource && group.currentResource.uri); } return { group, uri, }; } private isElectronRenderer(): boolean { return this.appConfig.isElectronRenderer; } registerKeybindings(keybindings: KeybindingRegistry): void { keybindings.registerKeybinding({ command: EDITOR_COMMANDS.SAVE_CURRENT.id, keybinding: 'ctrlcmd+s', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.FOCUS_IF_NOT_ACTIVATE_ELEMENT.id, keybinding: 'ctrlcmd+f', when: '!editorFocus', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.CLOSE.id, keybinding: this.isElectronRenderer() ? 'ctrlcmd+w' : 'alt+shift+w', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.PREVIOUS.id, keybinding: this.isElectronRenderer() ? 'alt+cmd+left' : 'ctrlcmd+ctrl+left', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.NEXT.id, keybinding: this.isElectronRenderer() ? 'alt+cmd+right' : 'ctrlcmd+ctrl+right', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.PREVIOUS.id, keybinding: this.isElectronRenderer() ? 'ctrlcmd+pageup' : 'alt+pageup', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.NEXT.id, keybinding: this.isElectronRenderer() ? 'ctrlcmd+pagedown' : 'alt+pagedown', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.GO_FORWARD.id, keybinding: isWindows ? 'alt+right' : 'ctrl+shift+-', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.GO_BACK.id, keybinding: isWindows ? 'alt+left' : 'ctrl+-', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.CHANGE_LANGUAGE.id, keybinding: 'ctrlcmd+k m', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.SPLIT_TO_RIGHT.id, keybinding: 'ctrlcmd+\\', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.NAVIGATE_NEXT.id, keybinding: 'ctrlcmd+k ctrlcmd+right', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.NAVIGATE_PREVIOUS.id, keybinding: 'ctrlcmd+k ctrlcmd+left', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.SAVE_ALL.id, keybinding: 'alt+ctrlcmd+s', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.CLOSE_ALL_IN_GROUP.id, keybinding: 'ctrlcmd+k w', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.CLOSE_ALL.id, keybinding: 'ctrlcmd+k ctrlcmd+w', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.CLOSE_SAVED.id, keybinding: 'ctrlcmd+k u', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.PIN_CURRENT.id, keybinding: 'ctrlcmd+k enter', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.COPY_CURRENT_PATH.id, keybinding: 'ctrlcmd+k p', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.REOPEN_CLOSED.id, keybinding: this.isElectronRenderer() ? 'ctrlcmd+shift+t' : 'alt+shift+t', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.NEW_UNTITLED_FILE.id, keybinding: this.isElectronRenderer() ? 'ctrlcmd+n' : 'alt+n', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.SEARCH_WORKSPACE_SYMBOL.id, keybinding: this.isElectronRenderer() ? 'ctrlcmd+t' : 'ctrlcmd+o', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.SEARCH_WORKSPACE_SYMBOL_CLASS.id, keybinding: this.isElectronRenderer() ? 'ctrlcmd+alt+t' : 'ctrlcmd+alt+o', }); for (let i = 1; i < 10; i++) { keybindings.registerKeybinding({ command: EDITOR_COMMANDS.GO_TO_GROUP.id, keybinding: 'ctrlcmd+' + i, args: [i], }); } ['left', 'up', 'down', 'right'].forEach((direction) => { keybindings.registerKeybinding({ command: EDITOR_COMMANDS.MOVE_GROUP.id, keybinding: 'ctrlcmd+k ' + direction, args: [direction], }); }); // The native `undo/redo` capability is retained in the native Input keybindings.registerKeybinding({ command: EDITOR_COMMANDS.COMPONENT_UNDO.id, keybinding: 'ctrlcmd+z', when: 'inEditorComponent && !inputFocus', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.COMPONENT_REDO.id, keybinding: 'shift+ctrlcmd+z', when: 'inEditorComponent && !inputFocus', }); keybindings.registerKeybinding({ command: EDITOR_COMMANDS.TOGGLE_WORD_WRAP.id, keybinding: 'alt+z', when: 'editorFocus', }); } initialize() { this.editorStatusBarService.setListener(); this.historyService.init(); } registerCommands(commands: CommandRegistry): void { commands.registerCommand(EDITOR_COMMANDS.GO_FORWARD, { execute: () => { this.historyService.forward(); }, }); commands.registerCommand(EDITOR_COMMANDS.FOCUS_IF_NOT_ACTIVATE_ELEMENT, { execute: () => { if (!document.activeElement || document.activeElement === document.body) { const group = this.workbenchEditorService.currentEditorGroup; group?.focus(); group?.currentCodeEditor?.monacoEditor?.trigger('api', 'actions.find', null); } }, }); commands.registerCommand(EDITOR_COMMANDS.GO_BACK, { execute: () => { this.historyService.back(); }, }); commands.registerCommand(EDITOR_COMMANDS.OPEN_RESOURCE, { execute: async (uri: URI, options?: IResourceOpenOptions) => { const openResult = await this.workbenchEditorService.open(uri, options); if (openResult) { return { groupId: openResult?.group.name, }; } }, }); commands.registerCommand(EDITOR_COMMANDS.OPEN_RESOURCES, { execute: ({ uris }: { uris: URI[] }) => { this.workbenchEditorService.openUris(uris); }, }); commands.registerCommand(EDITOR_COMMANDS.COMPARE, { execute: ( { original, modified, name }: { original: URI; modified: URI; name?: string }, options: IResourceOpenOptions = {}, ) => { name = name || `${original.displayName} <=> ${modified.displayName}`; return this.workbenchEditorService.open( URI.from({ scheme: DIFF_SCHEME, query: URI.stringifyQuery({ name, original, modified, }), }), options, ); }, }); commands.registerCommand(EDITOR_COMMANDS.OPEN_MERGEEDITOR_DEV, { execute: async () => { try { const value = await this.quickInputService.open({ value: '' }); if (!value) { return; } const toArgs = JSON.parse(value); /** * @example * { * input1: { * uri: 'current 分支的 uri' * }, * input2: { * uri: 'incoming 分支的 uri' * }, * output: '中间视图的文件 uri', * base: '共同祖先分支的文件 uri' * } */ this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_MERGEEDITOR.id, toArgs); } catch (error) {} }, }); commands.registerCommand(EDITOR_COMMANDS.OPEN_MERGEEDITOR, { execute: (args: unknown) => { const validatedArgs = IRelaxedOpenMergeEditorArgs.validate(args); this.workbenchEditorService.open( URI.from({ scheme: 'mergeEditor', query: URI.stringifyQuery({ name: formatLocalize('mergeEditor.workbench.tab.name', validatedArgs.output.displayName), openMetadata: IRelaxedOpenMergeEditorArgs.toString(validatedArgs), }), }), ); }, }); commands.registerCommand(EDITOR_COMMANDS.MERGEEDITOR_RESET, { execute: () => { const nutrition = this.mergeEditorService.getNutrition(); if (!nutrition) { return; } const { output } = nutrition; const { uri } = output; // 重置状态 this.mergeEditorService.fireRestoreState(uri); // 然后再重新 compare this.mergeEditorService.compare(); }, }); commands.registerCommand(EDITOR_COMMANDS.SAVE_CURRENT, { execute: async () => { const group = this.workbenchEditorService.currentEditorGroup; if (group && group.currentResource) { group.pin(group.currentResource!.uri); group.saveCurrent(); } }, }); commands.registerCommand(EDITOR_COMMANDS.SAVE_URI, { execute: async (uri: URI) => { for (const g of this.workbenchEditorService.editorGroups) { const r = g.resources.find((r) => r.uri.isEqual(uri)); if (r) { g.saveResource(r); } } }, }); commands.registerCommand(EDITOR_COMMANDS.CLOSE_ALL_IN_GROUP, { execute: async (args0: ResourceArgs | URI, args1?: EditorGroup) => { const { group } = this.extractGroupAndUriFromArgs(args0, args1); if (group) { await group.closeAll(); } }, }); commands.registerCommand(EDITOR_COMMANDS.CLOSE_SAVED, { execute: async (resource: ResourceArgs) => { resource = resource || {}; const { group = this.workbenchEditorService.currentEditorGroup } = resource; if (group) { await group.closeSaved(); } }, }); commands.registerCommand(EDITOR_COMMANDS.CLOSE_OTHER_IN_GROUP, { execute: async (resource: ResourceArgs) => { resource = resource || {}; const { group = this.workbenchEditorService.currentEditorGroup, uri = group && group.currentResource && group.currentResource.uri, } = resource; if (group && uri) { await group.closeOthers(uri); } }, }); commands.registerCommand(EDITOR_COMMANDS.CLOSE, { execute: async (resource: ResourceArgs) => { resource = resource || {}; const { group = this.workbenchEditorService.currentEditorGroup, uri = group && group.currentResource && group.currentResource.uri, } = resource; if (group && uri) { await group.close(uri); } }, }); commands.registerCommand(EDITOR_COMMANDS.CLOSE_TO_RIGHT, { execute: async (resource: ResourceArgs) => { resource = resource || {}; const { group = this.workbenchEditorService.currentEditorGroup, uri = group && group.currentResource && group.currentResource.uri, } = resource; if (group && uri) { await group.closeToRight(uri); } }, }); commands.registerCommand(EDITOR_COMMANDS.GET_CURRENT, { execute: () => this.workbenchEditorService.currentEditorGroup, }); commands.registerCommand(EDITOR_COMMANDS.GET_CURRENT_RESOURCE, { execute: () => this.workbenchEditorService.currentResource, }); commands.registerCommand(EDITOR_COMMANDS.PIN_CURRENT, { execute: () => { const group = this.workbenchEditorService.currentEditorGroup; if (group) { group.pinPreviewed(); } }, }); commands.registerCommand(EDITOR_COMMANDS.COPY_CURRENT_PATH, { execute: () => { const resource = this.workbenchEditorService.currentResource; if (resource && resource.uri.scheme === Schemes.file) { this.clipboardService.writeText(resource.uri.codeUri.fsPath); } }, }); commands.registerCommand(EDITOR_COMMANDS.SPLIT_TO_LEFT, { execute: async (resource: ResourceArgs) => { resource = resource || {}; const { group = this.workbenchEditorService.currentEditorGroup, uri = group && group.currentResource && group.currentResource.uri, } = resource; if (group && uri) { await group.split(EditorGroupSplitAction.Left, uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.SPLIT_TO_RIGHT, { execute: async (resource: ResourceArgs | URI, editorGroup?: EditorGroup) => { const { group, uri } = this.extractGroupAndUriFromArgs(resource, editorGroup); if (group && uri) { await group.split(EditorGroupSplitAction.Right, uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.GO_TO_GROUP, { execute: async (index = 1) => { const group = this.workbenchEditorService.sortedEditorGroups[index - 1]; if (group) { group.focus(); return; } // 如果找的索引比 editorGroups 的数量大1,就向右拆分一个 const groupLength = this.workbenchEditorService.sortedEditorGroups.length; if (groupLength === index - 1) { const rightEditorGroup = this.workbenchEditorService.sortedEditorGroups[groupLength - 1]; const uri = rightEditorGroup?.currentResource?.uri; if (rightEditorGroup && uri) { await rightEditorGroup.split(EditorGroupSplitAction.Right, uri, { focus: true }); } } }, }); commands.registerCommand(EDITOR_COMMANDS.MOVE_GROUP, { execute: async (direction?: Direction) => { if (direction) { const group = this.workbenchEditorService.currentEditorGroup; if (group) { group.grid.move(direction); } } }, }); commands.registerCommand(EDITOR_COMMANDS.FOCUS_ACTIVE_EDITOR_GROUP, { execute: async () => { const group = this.workbenchEditorService.currentEditorGroup; if (group) { group.focus(); } }, }); commands.registerCommand(EDITOR_COMMANDS.SPLIT_TO_TOP, { execute: async (resource: ResourceArgs) => { resource = resource || {}; const { group = this.workbenchEditorService.currentEditorGroup, uri = group && group.currentResource && group.currentResource.uri, } = resource; if (group && uri) { await group.split(EditorGroupSplitAction.Top, uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.SPLIT_TO_BOTTOM, { execute: async (resource: ResourceArgs) => { resource = resource || {}; const { group = this.workbenchEditorService.currentEditorGroup, uri = group && group.currentResource && group.currentResource.uri, } = resource; if (group && uri) { await group.split(EditorGroupSplitAction.Bottom, uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.CHANGE_LANGUAGE, { execute: async (currentLanguageId) => { const allLanguages = this.languagesService.languages; const allLanguageItems = allLanguages.map((language) => ({ label: language.name, value: language.id, description: `(${language.id})`, })); const targetLanguageId = await this.quickPickService.show(allLanguageItems, { placeholder: localize('editor.changeLanguageId'), selectIndex: () => allLanguageItems.findIndex( (item) => item.value === this.workbenchEditorService.currentCodeEditor?.currentDocumentModel?.languageId, ), }); if (targetLanguageId && currentLanguageId !== targetLanguageId) { if (this.workbenchEditorService.currentEditor) { const currentDocModel = this.workbenchEditorService.currentEditor.currentDocumentModel; if (currentDocModel) { this.editorDocumentModelService.changeModelOptions(currentDocModel.uri, { languageId: targetLanguageId, }); } } } }, }); commands.registerCommand(EDITOR_COMMANDS.GET_ENCODING, { execute: (uri: URI) => { if (!uri) { return; } const ref = this.editorDocumentModelService.getModelDescription(uri); if (!ref) { return; } return ref.encoding; }, }); commands.registerCommand(EDITOR_COMMANDS.CHANGE_ENCODING, { execute: async (uri: URI) => { // TODO: 这里应该和 vscode 一样,可以 通过编码打开 和 通过编码保存 // 但目前的磁盘文件对比使用的是文件字符串 md5 对比,导致更改编码时必定触发 diff,因此编码保存无法生效 // 长期看 md5 应该更改为 mtime 和 size 才更可靠 uri = uri ?? this.workbenchEditorService.currentResource?.uri; const documentModel = this.workbenchEditorService.currentEditor?.currentDocumentModel; if (!uri) { return; } const configuredEncoding = this.preferenceService.get( 'files.encoding', 'utf8', uri.toString(), getLanguageIdFromMonaco(uri)!, ); const provider = await this.contentRegistry.getProvider(uri); const guessedEncoding = await provider?.guessEncoding?.(uri); const ref = this.editorDocumentModelService.getModelDescription(uri); const currentEncoding = documentModel?.encoding ?? ref?.encoding; let matchIndex: number | undefined; const encodingItems: QuickPickItem[] = Object.keys(SUPPORTED_ENCODINGS) .sort((k1, k2) => { if (k1 === configuredEncoding) { return -1; } else if (k2 === configuredEncoding) { return 1; } return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order; }) .filter((k) => { // 猜测的编码和配置的编码不一致不现实,单独在最上方显示 if (k === guessedEncoding && guessedEncoding !== configuredEncoding) { return false; } return !SUPPORTED_ENCODINGS[k].encodeOnly; // 对于只用于 encode 编码不展示 }) .map((key, index) => { if (key === currentEncoding || SUPPORTED_ENCODINGS[key].alias === currentEncoding) { matchIndex = index; } return { label: SUPPORTED_ENCODINGS[key].labelLong, value: key, description: key }; }); // Insert guessed encoding if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { if (encodingItems[0]) { encodingItems[0].showBorder = true; } encodingItems.unshift({ label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, value: guessedEncoding, description: localize('editor.guessEncodingFromContent'), }); if (typeof matchIndex === 'number') { matchIndex++; } } const selectedFileEncoding = await this.quickPickService.show(encodingItems, { placeholder: localize('editor.chooseEncoding'), selectIndex(lookFor) { if (!lookFor) { return typeof matchIndex === 'number' ? matchIndex : -1; } return -1; }, }); if (!selectedFileEncoding) { return; } const uris: URI[] = []; if (uri.scheme === DIFF_SCHEME) { const resource = (await this.resourceService.getResource(uri)) as IDiffResource; if (resource.metadata) { uris.push(resource.metadata.original); uris.push(resource.metadata.modified); } } else { uris.push(uri); } uris.forEach((uri) => { this.editorDocumentModelService.changeModelOptions(uri, { encoding: selectedFileEncoding, }); }); }, }); commands.registerCommand(EDITOR_COMMANDS.CHANGE_EOL, { execute: async () => { const resource = this.workbenchEditorService.currentResource; const currentCodeEditor = this.workbenchEditorService.currentCodeEditor; if (currentCodeEditor && currentCodeEditor.currentDocumentModel && resource) { const res: EOL | undefined = await this.quickPickService.show( [ { label: 'LF', value: EOL.LF }, { label: 'CRLF', value: EOL.CRLF }, ], { placeholder: localize('editor.changeEol'), selectIndex: () => (currentCodeEditor.currentDocumentModel!.eol === EOL.LF ? 0 : 1), }, ); if (res) { this.editorDocumentModelService.changeModelOptions(resource.uri, { eol: res, }); } } }, }); commands.registerCommand(EDITOR_COMMANDS.FOCUS, { execute: async () => { if (this.workbenchEditorService.currentEditor) { this.workbenchEditorService.currentEditor.monacoEditor.focus(); } }, }); commands.registerCommand(EDITOR_COMMANDS.NAVIGATE_NEXT, { execute: async () => { let i = this.workbenchEditorService.currentEditorGroup.index + 1; if (this.workbenchEditorService.editorGroups.length <= i) { i = 0; } return this.workbenchEditorService.sortedEditorGroups[i].focus(); }, }); commands.registerCommand(EDITOR_COMMANDS.NAVIGATE_PREVIOUS, { execute: async () => { let i = this.workbenchEditorService.currentEditorGroup.index - 1; if (i < 0) { i = this.workbenchEditorService.editorGroups.length - 1; } return this.workbenchEditorService.sortedEditorGroups[i].focus(); }, }); commands.registerCommand(EDITOR_COMMANDS.NAVIGATE_UP, { execute: async () => { const currentGrid = this.workbenchEditorService.currentEditorGroup.grid; const targetGrid = currentGrid.findGird(Direction.UP); if (targetGrid) { return (targetGrid.editorGroup! as EditorGroup).focus(); } }, }); commands.registerCommand(EDITOR_COMMANDS.NAVIGATE_DOWN, { execute: async () => { const currentGrid = this.workbenchEditorService.currentEditorGroup.grid; const targetGrid = currentGrid.findGird(Direction.DOWN); if (targetGrid) { return (targetGrid.editorGroup! as EditorGroup).focus(); } }, }); commands.registerCommand(EDITOR_COMMANDS.NAVIGATE_LEFT, { execute: async () => { const currentGrid = this.workbenchEditorService.currentEditorGroup.grid; const targetGrid = currentGrid.findGird(Direction.LEFT); if (targetGrid) { return (targetGrid.editorGroup! as EditorGroup).focus(); } }, }); commands.registerCommand(EDITOR_COMMANDS.NAVIGATE_RIGHT, { execute: async () => { const currentGrid = this.workbenchEditorService.currentEditorGroup.grid; const targetGrid = currentGrid.findGird(Direction.RIGHT); if (targetGrid) { return (targetGrid.editorGroup! as EditorGroup).focus(); } }, }); commands.registerCommand(EDITOR_COMMANDS.PREVIOUS_IN_GROUP, { execute: async () => { const editorGroup = this.workbenchEditorService.currentEditorGroup; if (!editorGroup.currentResource) { return; } const index = editorGroup.resources.findIndex((r) => r.uri.isEqual(editorGroup.currentResource!.uri)) - 1; if (editorGroup.resources[index]) { return editorGroup.open(editorGroup.resources[index].uri, { focus: true }); } else { return editorGroup.open(editorGroup.resources[0].uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.NEXT_IN_GROUP, { execute: async () => { const editorGroup = this.workbenchEditorService.currentEditorGroup; if (!editorGroup.currentResource) { return; } const index = editorGroup.resources.findIndex((r) => r.uri.isEqual(editorGroup.currentResource!.uri)) + 1; if (editorGroup.resources[index]) { return editorGroup.open(editorGroup.resources[index].uri, { focus: true }); } else { return editorGroup.open(editorGroup.resources[editorGroup.resources.length - 1].uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.NEXT, { execute: async () => { const editorGroup = this.workbenchEditorService.currentEditorGroup; if (!editorGroup.currentResource) { return; } const index = editorGroup.resources.findIndex((r) => r.uri.isEqual(editorGroup.currentResource!.uri)) + 1; if (editorGroup.resources[index]) { return editorGroup.open(editorGroup.resources[index].uri, { focus: true }); } else { const nextEditorGroupIndex = editorGroup.index === this.workbenchEditorService.editorGroups.length - 1 ? 0 : editorGroup.index + 1; const nextEditorGroup = this.workbenchEditorService.sortedEditorGroups[nextEditorGroupIndex]; nextEditorGroup.focus(); return nextEditorGroup.open(nextEditorGroup.resources[0].uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.PREVIOUS, { execute: async () => { const editorGroup = this.workbenchEditorService.currentEditorGroup; if (!editorGroup.currentResource) { return; } const index = editorGroup.resources.findIndex((r) => r.uri.isEqual(editorGroup.currentResource!.uri)) - 1; if (editorGroup.resources[index]) { return editorGroup.open(editorGroup.resources[index].uri, { focus: true }); } else { const nextEditorGroupIndex = editorGroup.index === 0 ? this.workbenchEditorService.editorGroups.length - 1 : editorGroup.index - 1; const nextEditorGroup = this.workbenchEditorService.sortedEditorGroups[nextEditorGroupIndex]; nextEditorGroup.focus(); return nextEditorGroup.open(nextEditorGroup.resources[nextEditorGroup.resources.length - 1].uri, { focus: true, }); } }, }); commands.registerCommand(EDITOR_COMMANDS.LAST_IN_GROUP, { execute: async () => { const editorGroup = this.workbenchEditorService.currentEditorGroup; if (editorGroup.resources.length > 0) { return editorGroup.open(editorGroup.resources[editorGroup.resources.length - 1].uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.EVEN_EDITOR_GROUPS, { execute: async () => { const eventBus: IEventBus = this.injector.get(IEventBus); eventBus.fire(new EditorGroupsResetSizeEvent()); }, }); commands.registerCommand(EDITOR_COMMANDS.CLOSE_OTHER_GROUPS, { execute: async () => { const editorGroup = this.workbenchEditorService.currentEditorGroup; const groupsToClose = this.workbenchEditorService.editorGroups.filter((e) => e !== editorGroup); groupsToClose.forEach((g) => { g.dispose(); }); }, }); commands.registerCommand(EDITOR_COMMANDS.OPEN_EDITOR_AT_INDEX, { execute: async (index) => { const editorGroup = this.workbenchEditorService.currentEditorGroup; const target = editorGroup.resources[index]; if (target) { await editorGroup.open(target.uri, { focus: true }); } }, }); commands.registerCommand(EDITOR_COMMANDS.REVERT_DOCUMENT, { execute: async () => { const group = this.workbenchEditorService.currentEditorGroup; if (group.isCodeEditorMode()) { const documentModel = group.codeEditor.currentDocumentModel; if (documentModel) { await documentModel.revert(); } } }, }); commands.registerCommand(EDITOR_COMMANDS.REVERT_AND_CLOSE, { execute: async () => { const group = this.workbenchEditorService.currentEditorGroup; if (group.isCodeEditorMode()) { const documentModel = group.codeEditor.currentDocumentModel; if (documentModel) { await documentModel.revert(); } group.close(group.currentResource!.uri); } }, }); commands.registerCommand(EDITOR_COMMANDS.SAVE_ALL, { execute: async (reason?: SaveReason) => { this.workbenchEditorService.saveAll(undefined, reason); }, }); commands.registerCommand(EDITOR_COMMANDS.CLOSE_ALL, { execute: async (uri?: URI) => { this.workbenchEditorService.closeAll(uri); }, }); commands.registerCommand(EDITOR_COMMANDS.REOPEN_CLOSED, { execute: async () => { this.historyService.popClosed(); }, }); commands.registerCommand(EDITOR_COMMANDS.NEW_UNTITLED_FILE, { execute: () => { this.workbenchEditorService.createUntitledResource(); }, }); commands.registerCommand(EDITOR_COMMANDS.COMPONENT_UNDO, { execute: () => { this.workbenchEditorService.currentEditorGroup.componentUndo(); }, }); commands.registerCommand(EDITOR_COMMANDS.COMPONENT_REDO, { execute: () => { this.workbenchEditorService.currentEditorGroup.componentRedo(); }, }); commands.registerCommand(EDITOR_COMMANDS.TEST_TOKENIZE, { execute: () => { const currentCodeEditor = this.workbenchEditorService.currentCodeEditor; if (currentCodeEditor) { const selections = currentCodeEditor.getSelections(); if (selections && selections.length > 0 && currentCodeEditor.currentDocumentModel) { const { selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn } = selections[0]; const selectionText = currentCodeEditor.currentDocumentModel.getText( new monaco.Range(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn), ); this.monacoService.testTokenize(selectionText, currentCodeEditor.currentDocumentModel.languageId); } } }, }); commands.registerCommand(EDITOR_COMMANDS.SEARCH_WORKSPACE_SYMBOL, { execute: () => this.prefixQuickOpenService.open('#'), }); commands.registerCommand(EDITOR_COMMANDS.SEARCH_WORKSPACE_SYMBOL_CLASS, { execute: () => this.prefixQuickOpenService.open('##'), }); commands.registerCommand(EDITOR_COMMANDS.GO_TO_LINE, { execute: () => this.prefixQuickOpenService.open(':'), }); commands.registerCommand(EDITOR_COMMANDS.TOGGLE_COLUMN_SELECTION, { execute: () => { const isColumnSelection = this.preferenceService.get('editor.columnSelection'); this.preferenceService.set('editor.columnSelection', !isColumnSelection, PreferenceScope.User); }, }); commands.registerCommand(EDITOR_COMMANDS.TOGGLE_WORD_WRAP, { execute: () => { const wordWrap = this.preferenceService.get('editor.wordWrap'); if (wordWrap) { const values: string[] = ['off', 'on']; const index = values.indexOf(wordWrap) + 1; if (index > -1) { this.preferenceService.set('editor.wordWrap', values[index % values.length], PreferenceScope.User); } } }, }); commands.registerCommand(EDITOR_COMMANDS.FORMAT_DOCUMENT_WITH, { execute: async () => { const formatService = this.injector.get(DocumentFormatService); formatService.formatDocumentWith(); }, }); commands.registerCommand(EDITOR_COMMANDS.FORMAT_SELECTION_WITH, { execute: async () => { const formatService = this.injector.get(DocumentFormatService); formatService.formatSelectionWith(); }, }); commands.registerCommand( { id: EDITOR_COMMANDS.VSCODE_OPEN_MULTI_DIFF_EDITOR_COMMAND_ID, }, { execute: async (options: { title: string; multiDiffSourceUri: UriComponents; resources: { originalUri?: UriComponents; modifiedUri?: UriComponents }[]; }) => { const sources: MultiDiffEditorItem[] = []; for (const { originalUri, modifiedUri } of options.resources) { sources.push( new MultiDiffEditorItem( originalUri ? URI.from(originalUri) : undefined, modifiedUri ? URI.from(modifiedUri) : undefined, modifiedUri ? URI.from(modifiedUri) : undefined, ), ); } const multiDiffSourceUri = URI.from(options.multiDiffSourceUri); // 提前注册到 resolver 内,相同的multiDiffSourceUri支持更新 this.multiDiffResolver.registerSources(multiDiffSourceUri, sources); await this.workbenchEditorService.open(multiDiffSourceUri, { label: options.title, }); }, }, ); } registerMenus(menus: IMenuRegistry) { menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.COPY_PATH.id, group: '10_path', order: 1, }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.COPY_RELATIVE_PATH.id, group: '10_path', order: 2, }); menus.registerMenuItem(MenuId.BreadcrumbsTitleContext, { command: EDITOR_COMMANDS.COPY_PATH.id, group: '0_path', order: 1, }); menus.registerMenuItem(MenuId.BreadcrumbsTitleContext, { command: EDITOR_COMMANDS.COPY_RELATIVE_PATH.id, group: '0_path', order: 2, }); menus.registerMenuItem(MenuId.BreadcrumbsTitleContext, { command: { id: FILE_COMMANDS.REVEAL_IN_EXPLORER.id, label: localize('file.revealInExplorer'), }, group: '1_file', order: 3, }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: { id: FILE_COMMANDS.REVEAL_IN_EXPLORER.id, label: localize('file.revealInExplorer'), }, group: '6_file', order: 3, }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.SPLIT_TO_LEFT.id, group: '9_split', }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.SPLIT_TO_RIGHT.id, group: '9_split', }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.SPLIT_TO_TOP.id, group: '9_split', }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.SPLIT_TO_BOTTOM.id, group: '9_split', }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: { id: EDITOR_COMMANDS.CLOSE.id, label: localize('editor.title.context.close'), }, group: '0_tab', order: 1, }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.CLOSE_ALL_IN_GROUP.id, group: '0_tab', order: 2, }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.CLOSE_SAVED.id, group: '0_tab', order: 3, }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.CLOSE_OTHER_IN_GROUP.id, group: '0_tab', order: 4, }); menus.registerMenuItem(MenuId.EditorTitleContext, { command: EDITOR_COMMANDS.CLOSE_TO_RIGHT.id, group: '0_tab', order: 5, }); menus.registerMenuItem(MenuId.EditorTitle, { command: EDITOR_COMMANDS.CLOSE_ALL_IN_GROUP.id, group: '0_internal', }); menus.registerMenuItem(MenuId.EditorTitle, { command: EDITOR_COMMANDS.SPLIT_TO_RIGHT.id, iconClass: getIcon('embed'), group: 'navigation', when: 'resource', order: 5, }); menus.registerMenuItem(MenuId.EditorContext, { command: EDITOR_COMMANDS.FORMAT_DOCUMENT_WITH.id, group: '1_modification', order: 1.3, when: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasMultipleDocumentFormattingProvider), }); menus.registerMenuItem(MenuId.EditorContext, { command: EDITOR_COMMANDS.FORMAT_SELECTION_WITH.id, when: ContextKeyExpr.and( EditorContextKeys.writable, EditorContextKeys.hasMultipleDocumentSelectionFormattingProvider, EditorContextKeys.hasNonEmptySelection, ), group: '1_modification', order: 1.31, }); } registerOpener(regisry: IOpenerService) { regisry.registerOpener(this.editorOpener); } registerQuickOpenHandlers(handlers: IQuickOpenHandlerRegistry): void { handlers.registerHandler(this.workspaceSymbolQuickOpenHandler, { title: localize('quickopen.tab.symbol'), order: 3, commandId: EDITOR_COMMANDS.SEARCH_WORKSPACE_SYMBOL.id, sub: { // 将类单独作为一个 tab,Java 场景比较常见,其它技术栈可能不一定 '#': { title: localize('quickopen.tab.class'), order: 2, commandId: EDITOR_COMMANDS.SEARCH_WORKSPACE_SYMBOL_CLASS.id, }, }, }); handlers.registerHandler(this.goToLineQuickOpenHandler, { title: localize('quickopen.tab.goToLine'), commandId: EDITOR_COMMANDS.GO_TO_LINE.id, order: 5, }); } } @Domain(BrowserEditorContribution, CommandContribution) export class EditorAutoSaveEditorContribution implements BrowserEditorContribution, CommandContribution { @Autowired(INJECTOR_TOKEN) injector: Injector; @Autowired(PreferenceService) preferenceService: PreferenceService; @Autowired(WorkbenchEditorService) private workbenchEditorService: WorkbenchEditorServiceImpl; @Autowired(CommandService) commandService: CommandService; @Autowired(IEditorDocumentModelService) editorDocumentService: IEditorDocumentModelService; @Autowired(IPreferenceSettingsService) preferenceSettings: IPreferenceSettingsService; registerEditorFeature(registry: IEditorFeatureRegistry) { registry.registerEditorFeatureContribution({ contribute: (editor: IEditor) => { const disposable = new Disposable(); disposable.addDispose( editor.monacoEditor.onDidBlurEditorWidget(() => { if (this.preferenceService.get('editor.autoSave') === AUTO_SAVE_MODE.EDITOR_FOCUS_CHANGE) { if ( editor.currentDocumentModel && !editor.currentDocumentModel.closeAutoSave && editor.currentDocumentModel.dirty && editor.currentDocumentModel.savable ) { editor.currentDocumentModel.save(undefined, SaveReason.FocusOut); } } }), ); disposable.addDispose( editor.monacoEditor.onDidChangeModel((e) => { if (this.preferenceService.get('editor.autoSave') === AUTO_SAVE_MODE.EDITOR_FOCUS_CHANGE) { if (e.oldModelUrl) { const oldUri = new URI(e.oldModelUrl.toString()); const docRef = this.editorDocumentService.getModelReference(oldUri, 'editor-focus-autosave'); if (docRef && !docRef.instance.closeAutoSave && docRef.instance.dirty && docRef.instance.savable) { docRef.instance.save(undefined, SaveReason.FocusOut); } docRef?.dispose(); } } }), ); return disposable; }, }); window.addEventListener('blur', () => { if (this.preferenceService.get('editor.autoSave') === AUTO_SAVE_MODE.WINDOWS_LOST_FOCUS) { this.commandService.executeCommand(EDITOR_COMMANDS.SAVE_ALL.id, SaveReason.FocusOut); } }); this.preferenceSettings.setEnumLabels('editor.autoSave', { [AUTO_SAVE_MODE.OFF]: localize('editor.autoSave.enum.off'), [AUTO_SAVE_MODE.AFTER_DELAY]: localize('editor.autoSave.enum.afterDelay'), [AUTO_SAVE_MODE.EDITOR_FOCUS_CHANGE]: localize('editor.autoSave.enum.editorFocusChange'), [AUTO_SAVE_MODE.WINDOWS_LOST_FOCUS]: localize('editor.autoSave.enum.windowLostFocus'), }); registry.registerEditorFeatureContribution(new EditorTopPaddingContribution()); registry.registerEditorFeatureContribution(this.injector.get(EditorSuggestWidgetContribution)); this.registerAutoSaveConfigurationChange(); } registerAutoSaveConfigurationChange() { this.preferenceService.onSpecificPreferenceChange('editor.autoSave', (change) => { const mode = change.newValue; if (mode !== AUTO_SAVE_MODE.OFF) { // 只有两种原因:丢失焦点和延迟保存,非此即彼 let reason = SaveReason.FocusOut; if (mode === AUTO_SAVE_MODE.AFTER_DELAY) { reason = SaveReason.AfterDelay; } // 只保存被该设置影响的文档 // 比如在当前工作区写代码,然后打开了一个 ~/.xxx 文件 // 然后用户修改了设置,这里就只保存当前工作区的文件。 for (const group of this.workbenchEditorService.editorGroups) { for (const resource of group.resources) { if (change.affects(resource?.uri.toString())) { group.saveResource(resource, reason); } } } } }); } registerCommands(commands: CommandRegistry): void { commands.registerCommand(EDITOR_COMMANDS.AUTO_SAVE, { execute: () => { const autoSavePreferenceField = 'editor.autoSave'; const value = (this.preferenceSettings.getPreference(autoSavePreferenceField, PreferenceScope.User).value as string) || AUTO_SAVE_MODE.OFF; const nextValue = [ AUTO_SAVE_MODE.AFTER_DELAY, AUTO_SAVE_MODE.EDITOR_FOCUS_CHANGE, AUTO_SAVE_MODE.WINDOWS_LOST_FOCUS, ].includes(value) ? AUTO_SAVE_MODE.OFF : AUTO_SAVE_MODE.AFTER_DELAY; return this.preferenceSettings.setPreference(autoSavePreferenceField, nextValue, PreferenceScope.User); }, }); commands.registerCommand(EDITOR_COMMANDS.COPY_PATH, { execute: (resource: ResourceArgs) => { if (!resource) { return; } this.commandService.executeCommand(FILE_COMMANDS.COPY_PATH.id, resource.uri); }, }); commands.registerCommand(EDITOR_COMMANDS.COPY_RELATIVE_PATH, { execute: (resource: ResourceArgs) => { if (!resource) { return; } this.commandService.executeCommand(FILE_COMMANDS.COPY_RELATIVE_PATH.id, resource.uri); }, }); } }