// ***************************************************************************** // Copyright (C) 2019 TypeFox 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 { inject, injectable } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; import { PreferenceScope } from '@theia/core/lib/common/preferences'; import { WorkspaceService, WorkspaceData } from '@theia/workspace/lib/browser/workspace-service'; import { AbstractResourcePreferenceProvider } from '../common/abstract-resource-preference-provider'; @injectable() export class WorkspaceFilePreferenceProviderOptions { workspaceUri: URI; } export const WorkspaceFilePreferenceProviderFactory = Symbol('WorkspaceFilePreferenceProviderFactory'); export type WorkspaceFilePreferenceProviderFactory = (options: WorkspaceFilePreferenceProviderOptions) => WorkspaceFilePreferenceProvider; @injectable() export class WorkspaceFilePreferenceProvider extends AbstractResourcePreferenceProvider { @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @inject(WorkspaceFilePreferenceProviderOptions) protected readonly options: WorkspaceFilePreferenceProviderOptions; protected sectionsInsideSettings = new Set(); protected getUri(): URI { return this.options.workspaceUri; } // eslint-disable-next-line @typescript-eslint/no-explicit-any protected override parse(content: string): any { const data = super.parse(content); if (WorkspaceData.is(data)) { const settings = { ...data.settings }; for (const key of this.configurations.getSectionNames().filter(name => name !== 'settings')) { // If the user has written configuration inside the "settings" object, we will respect that. if (settings[key]) { this.sectionsInsideSettings.add(key); } // Favor sections outside the "settings" object to agree with VSCode behavior if (data[key]) { settings[key] = data[key]; this.sectionsInsideSettings.delete(key); } } return settings; } return {}; } protected override getPath(preferenceName: string): string[] { const firstSegment = preferenceName.split('.', 1)[0]; const remainder = preferenceName.slice(firstSegment.length + 1); if (this.belongsInSection(firstSegment, remainder)) { // Default to writing sections outside the "settings" object. const path = [firstSegment]; if (remainder) { path.push(remainder); } // If the user has already written this section inside the "settings" object, modify it there. if (this.sectionsInsideSettings.has(firstSegment)) { path.unshift('settings'); } return path; } return ['settings'].concat(super.getPath(preferenceName) ?? []); } /** * @returns `true` if `firstSegment` is a section name (e.g. `tasks`, `launch`) */ protected belongsInSection(firstSegment: string, remainder: string): boolean { return this.configurations.isSectionName(firstSegment); } getScope(): PreferenceScope { return PreferenceScope.Workspace; } override getDomain(): string[] { // workspace file is treated as part of the workspace return this.workspaceService.tryGetRoots().map(r => r.resource.toString()).concat([this.options.workspaceUri.toString()]); } }