// ***************************************************************************** // Copyright (C) 2018 Ericsson and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { injectable, inject, optional } from '@theia/core/shared/inversify'; import { MenuModelRegistry, CommandRegistry, nls, PreferenceScope, PreferenceService } from '@theia/core'; import { CommonMenus, AbstractViewContribution, CommonCommands, KeybindingRegistry, Widget, QuickInputService, QuickPickItem, isFirefox, } from '@theia/core/lib/browser'; import { isOSX } from '@theia/core/lib/common/os'; import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { EditorManager, EditorWidget } from '@theia/editor/lib/browser'; import URI from '@theia/core/lib/common/uri'; import { PreferencesWidget } from './views/preference-widget'; import { Preference, PreferencesCommands, PreferenceMenus } from './util/preference-types'; import { ClipboardService } from '@theia/core/lib/browser/clipboard-service'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { FileStat } from '@theia/filesystem/lib/common/files'; @injectable() export class PreferencesContribution extends AbstractViewContribution { @inject(FileService) protected readonly fileService: FileService; @inject(EditorManager) protected readonly editorManager: EditorManager; @inject(PreferenceService) protected readonly preferenceService: PreferenceService; @inject(ClipboardService) protected readonly clipboardService: ClipboardService; @inject(PreferencesWidget) protected readonly scopeTracker: PreferencesWidget; @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; constructor() { super({ widgetId: PreferencesWidget.ID, widgetName: PreferencesWidget.LABEL, defaultWidgetOptions: { area: 'main', }, }); } override registerCommands(commands: CommandRegistry): void { commands.registerCommand(CommonCommands.OPEN_PREFERENCES, { execute: async (query?: string) => { const widget = await this.openView({ activate: true }); if (typeof query === 'string') { widget.setSearchTerm(query); } }, }); commands.registerCommand(PreferencesCommands.OPEN_PREFERENCES_JSON_TOOLBAR, { isEnabled: () => true, isVisible: w => this.withWidget(w, () => true), execute: (preferenceId: string) => { this.openPreferencesJSON(preferenceId); } }); commands.registerCommand(PreferencesCommands.COPY_JSON_NAME, { isEnabled: Preference.EditorCommandArgs.is, isVisible: Preference.EditorCommandArgs.is, execute: ({ id, value }: Preference.EditorCommandArgs) => { this.clipboardService.writeText(id); } }); commands.registerCommand(PreferencesCommands.COPY_JSON_VALUE, { isEnabled: Preference.EditorCommandArgs.is, isVisible: Preference.EditorCommandArgs.is, execute: ({ id, value }: { id: string, value: string; }) => { const jsonString = `"${id}": ${JSON.stringify(value)}`; this.clipboardService.writeText(jsonString); } }); commands.registerCommand(PreferencesCommands.RESET_PREFERENCE, { isEnabled: Preference.EditorCommandArgs.is, isVisible: Preference.EditorCommandArgs.is, execute: ({ id }: Preference.EditorCommandArgs) => { this.preferenceService.set(id, undefined, Number(this.scopeTracker.currentScope.scope), this.scopeTracker.currentScope.uri); } }); commands.registerCommand(PreferencesCommands.OPEN_USER_PREFERENCES, { execute: async () => { const widget = await this.openView({ activate: true }); widget.setScope(PreferenceScope.User); } }); commands.registerCommand(PreferencesCommands.OPEN_WORKSPACE_PREFERENCES, { isEnabled: () => !!this.workspaceService.workspace, isVisible: () => !!this.workspaceService.workspace, execute: async () => { const widget = await this.openView({ activate: true }); widget.setScope(PreferenceScope.Workspace); } }); commands.registerCommand(PreferencesCommands.OPEN_FOLDER_PREFERENCES, { isEnabled: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0, isVisible: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0, execute: () => this.openFolderPreferences(root => { this.openView({ activate: true }); this.scopeTracker.setScope(root.resource); }) }); commands.registerCommand(PreferencesCommands.OPEN_USER_PREFERENCES_JSON, { execute: async () => this.openJson(PreferenceScope.User) }); commands.registerCommand(PreferencesCommands.OPEN_WORKSPACE_PREFERENCES_JSON, { isEnabled: () => !!this.workspaceService.workspace, isVisible: () => !!this.workspaceService.workspace, execute: async () => this.openJson(PreferenceScope.Workspace) }); commands.registerCommand(PreferencesCommands.OPEN_FOLDER_PREFERENCES_JSON, { isEnabled: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0, isVisible: () => !!this.workspaceService.isMultiRootWorkspaceOpened && this.workspaceService.tryGetRoots().length > 0, execute: () => this.openFolderPreferences(root => this.openJson(PreferenceScope.Folder, root.resource.toString())) }); } override registerMenus(menus: MenuModelRegistry): void { menus.registerMenuAction(CommonMenus.FILE_SETTINGS_SUBMENU_OPEN, { commandId: CommonCommands.OPEN_PREFERENCES.id, label: nls.localizeByDefault('Settings'), order: 'a10', }); menus.registerMenuAction(CommonMenus.MANAGE_SETTINGS, { commandId: CommonCommands.OPEN_PREFERENCES.id, label: nls.localizeByDefault('Settings'), order: 'a10', }); menus.registerMenuAction(PreferenceMenus.PREFERENCE_EDITOR_CONTEXT_MENU, { commandId: PreferencesCommands.RESET_PREFERENCE.id, label: PreferencesCommands.RESET_PREFERENCE.label, order: 'a' }); menus.registerMenuAction(PreferenceMenus.PREFERENCE_EDITOR_COPY_ACTIONS, { commandId: PreferencesCommands.COPY_JSON_VALUE.id, label: PreferencesCommands.COPY_JSON_VALUE.label, order: 'b' }); menus.registerMenuAction(PreferenceMenus.PREFERENCE_EDITOR_COPY_ACTIONS, { commandId: PreferencesCommands.COPY_JSON_NAME.id, label: PreferencesCommands.COPY_JSON_NAME.label, order: 'c' }); } override registerKeybindings(keybindings: KeybindingRegistry): void { keybindings.registerKeybinding({ command: CommonCommands.OPEN_PREFERENCES.id, keybinding: (isOSX && !isFirefox) ? 'cmd+,' : 'ctrl+,' }); } registerToolbarItems(toolbar: TabBarToolbarRegistry): void { toolbar.registerItem({ id: PreferencesCommands.OPEN_PREFERENCES_JSON_TOOLBAR.id, command: PreferencesCommands.OPEN_PREFERENCES_JSON_TOOLBAR.id, tooltip: PreferencesCommands.OPEN_USER_PREFERENCES_JSON.label, priority: 0, }); } protected async openPreferencesJSON(opener: string | PreferencesWidget): Promise { const { scope, activeScopeIsFolder, uri } = this.scopeTracker.currentScope; const scopeID = Number(scope); let preferenceId = ''; if (typeof opener === 'string') { preferenceId = opener; const currentPreferenceValue = this.preferenceService.inspect(preferenceId, uri); const valueInCurrentScope = Preference.getValueInScope(currentPreferenceValue, scopeID) ?? currentPreferenceValue?.defaultValue; this.preferenceService.set(preferenceId, valueInCurrentScope, scopeID, uri); } let jsonEditorWidget: EditorWidget; const jsonUriToOpen = await this.obtainConfigUri(scopeID, activeScopeIsFolder, uri); if (jsonUriToOpen) { jsonEditorWidget = await this.editorManager.open(jsonUriToOpen); if (preferenceId) { const text = jsonEditorWidget.editor.document.getText(); if (preferenceId) { const { index } = text.match(preferenceId)!; const numReturns = text.slice(0, index).match(new RegExp('\n', 'g'))!.length; jsonEditorWidget.editor.cursor = { line: numReturns, character: 4 + preferenceId.length + 4 }; } } } } protected async openJson(scope: PreferenceScope, resource?: string): Promise { const jsonUriToOpen = await this.obtainConfigUri(scope, false, resource); if (jsonUriToOpen) { await this.editorManager.open(jsonUriToOpen); } } /** * Prompts which workspace root folder to open the JSON settings. */ protected async openFolderPreferences(callback: (root: FileStat) => unknown): Promise { const roots = this.workspaceService.tryGetRoots(); if (roots.length === 1) { callback(roots[0]); } else { const items: QuickPickItem[] = roots.map(root => ({ label: root.name, description: root.resource.path.fsPath(), execute: () => callback(root) })); this.quickInputService?.showQuickPick(items, { placeholder: 'Select workspace folder' }); } } private async obtainConfigUri(serializedScope: number, activeScopeIsFolder: boolean, resource?: string): Promise { let scope: PreferenceScope = serializedScope; if (activeScopeIsFolder) { scope = PreferenceScope.Folder; } const resourceUri = !!resource ? resource : undefined; const configUri = this.preferenceService.getConfigUri(scope, resourceUri); if (!configUri) { return undefined; } if (configUri && !await this.fileService.exists(configUri)) { await this.fileService.create(configUri); } return configUri; } /** * Determine if the current widget is the PreferencesWidget. */ protected withWidget(widget: Widget | undefined = this.tryGetWidget(), fn: (widget: PreferencesWidget) => T): T | false { if (widget instanceof PreferencesWidget && widget.id === PreferencesWidget.ID) { return fn(widget); } return false; } }