mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 17:20:28 -04:00
Vscode merge (#4582)
* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd * fix issues with merges * bump node version in azpipe * replace license headers * remove duplicate launch task * fix build errors * fix build errors * fix tslint issues * working through package and linux build issues * more work * wip * fix packaged builds * working through linux build errors * wip * wip * wip * fix mac and linux file limits * iterate linux pipeline * disable editor typing * revert series to parallel * remove optimize vscode from linux * fix linting issues * revert testing change * add work round for new node * readd packaging for extensions * fix issue with angular not resolving decorator dependencies
This commit is contained in:
80
src/vs/workbench/contrib/extensions/common/extensionQuery.ts
Normal file
80
src/vs/workbench/contrib/extensions/common/extensionQuery.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
|
||||
export class Query {
|
||||
|
||||
constructor(public value: string, public sortBy: string, public groupBy: string) {
|
||||
this.value = value.trim();
|
||||
}
|
||||
|
||||
static suggestions(query: string): string[] {
|
||||
const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'sort', 'category', 'tag', 'ext'];
|
||||
const subcommands = {
|
||||
'sort': ['installs', 'rating', 'name'],
|
||||
'category': ['"programming languages"', 'snippets', 'linters', 'themes', 'debuggers', 'formatters', 'keymaps', '"scm providers"', 'other', '"extension packs"', '"language packs"'],
|
||||
'tag': [''],
|
||||
'ext': ['']
|
||||
};
|
||||
|
||||
let queryContains = (substr: string) => query.indexOf(substr) > -1;
|
||||
let hasSort = subcommands.sort.some(subcommand => queryContains(`@sort:${subcommand}`));
|
||||
let hasCategory = subcommands.category.some(subcommand => queryContains(`@category:${subcommand}`));
|
||||
|
||||
return flatten(
|
||||
commands.map(command => {
|
||||
if (hasSort && command === 'sort' || hasCategory && command === 'category') {
|
||||
return [];
|
||||
}
|
||||
if (subcommands[command]) {
|
||||
return subcommands[command].map(subcommand => `@${command}:${subcommand}${subcommand === '' ? '' : ' '}`);
|
||||
}
|
||||
else {
|
||||
return [`@${command} `];
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
static parse(value: string): Query {
|
||||
let sortBy = '';
|
||||
value = value.replace(/@sort:(\w+)(-\w*)?/g, (match, by: string, order: string) => {
|
||||
sortBy = by;
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
let groupBy = '';
|
||||
value = value.replace(/@group:(\w+)(-\w*)?/g, (match, by: string, order: string) => {
|
||||
groupBy = by;
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
return new Query(value, sortBy, groupBy);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let result = this.value;
|
||||
|
||||
if (this.sortBy) {
|
||||
result = `${result}${result ? ' ' : ''}@sort:${this.sortBy}`;
|
||||
}
|
||||
if (this.groupBy) {
|
||||
result = `${result}${result ? ' ' : ''}@group:${this.groupBy}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
isValid(): boolean {
|
||||
return !/@outdated/.test(this.value);
|
||||
}
|
||||
|
||||
equals(other: Query): boolean {
|
||||
return this.value === other.value && this.sortBy === other.sortBy;
|
||||
}
|
||||
}
|
||||
158
src/vs/workbench/contrib/extensions/common/extensions.ts
Normal file
158
src/vs/workbench/contrib/extensions/common/extensions.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { IQueryOptions, EnablementState, ILocalExtension, IGalleryExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.extensions';
|
||||
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
|
||||
|
||||
export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json';
|
||||
|
||||
export interface IExtensionsViewlet extends IViewlet {
|
||||
search(text: string): void;
|
||||
}
|
||||
|
||||
export const enum ExtensionState {
|
||||
Installing,
|
||||
Installed,
|
||||
Uninstalling,
|
||||
Uninstalled
|
||||
}
|
||||
|
||||
export interface IExtension {
|
||||
type?: ExtensionType;
|
||||
state: ExtensionState;
|
||||
name: string;
|
||||
displayName: string;
|
||||
identifier: IExtensionIdentifier;
|
||||
publisher: string;
|
||||
publisherDisplayName: string;
|
||||
version: string;
|
||||
latestVersion: string;
|
||||
description: string;
|
||||
url?: string;
|
||||
// {{SQL CARBON EDIT}}
|
||||
downloadPage: string;
|
||||
repository?: string;
|
||||
iconUrl: string;
|
||||
iconUrlFallback: string;
|
||||
licenseUrl?: string;
|
||||
installCount?: number;
|
||||
rating?: number;
|
||||
ratingCount?: number;
|
||||
outdated: boolean;
|
||||
enablementState: EnablementState;
|
||||
dependencies: string[];
|
||||
extensionPack: string[];
|
||||
telemetryData: any;
|
||||
preview: boolean;
|
||||
getManifest(token: CancellationToken): Promise<IExtensionManifest | null>;
|
||||
getReadme(token: CancellationToken): Promise<string>;
|
||||
hasReadme(): boolean;
|
||||
getChangelog(token: CancellationToken): Promise<string>;
|
||||
hasChangelog(): boolean;
|
||||
local?: ILocalExtension;
|
||||
gallery?: IGalleryExtension;
|
||||
isMalicious: boolean;
|
||||
}
|
||||
|
||||
export interface IExtensionDependencies {
|
||||
dependencies: IExtensionDependencies[];
|
||||
hasDependencies: boolean;
|
||||
identifier: string;
|
||||
extension: IExtension;
|
||||
dependent: IExtensionDependencies | null;
|
||||
}
|
||||
|
||||
export const SERVICE_ID = 'extensionsWorkbenchService';
|
||||
|
||||
export const IExtensionsWorkbenchService = createDecorator<IExtensionsWorkbenchService>(SERVICE_ID);
|
||||
|
||||
export interface IExtensionsWorkbenchService {
|
||||
_serviceBrand: any;
|
||||
onChange: Event<IExtension | undefined>;
|
||||
local: IExtension[];
|
||||
queryLocal(): Promise<IExtension[]>;
|
||||
queryGallery(options?: IQueryOptions): Promise<IPager<IExtension>>;
|
||||
canInstall(extension: IExtension): boolean;
|
||||
install(vsix: string): Promise<IExtension>;
|
||||
install(extension: IExtension, promptToInstallDependencies?: boolean): Promise<IExtension>;
|
||||
uninstall(extension: IExtension): Promise<void>;
|
||||
installVersion(extension: IExtension, version: string): Promise<IExtension>;
|
||||
reinstall(extension: IExtension): Promise<IExtension>;
|
||||
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
|
||||
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null>;
|
||||
open(extension: IExtension, sideByside?: boolean): Promise<any>;
|
||||
checkForUpdates(): Promise<void>;
|
||||
allowedBadgeProviders: string[];
|
||||
}
|
||||
|
||||
export const ConfigurationKey = 'extensions';
|
||||
export const AutoUpdateConfigurationKey = 'extensions.autoUpdate';
|
||||
export const AutoCheckUpdatesConfigurationKey = 'extensions.autoCheckUpdates';
|
||||
export const ShowRecommendationsOnlyOnDemandKey = 'extensions.showRecommendationsOnlyOnDemand';
|
||||
export const CloseExtensionDetailsOnViewChangeKey = 'extensions.closeExtensionDetailsOnViewChange';
|
||||
// {{SQL CARBON EDIT}}
|
||||
export const ExtensionsPolicyKey = 'extensions.extensionsPolicy';
|
||||
|
||||
export interface IExtensionsConfiguration {
|
||||
autoUpdate: boolean;
|
||||
autoCheckUpdates: boolean;
|
||||
ignoreRecommendations: boolean;
|
||||
showRecommendationsOnlyOnDemand: boolean;
|
||||
closeExtensionDetailsOnViewChange: boolean;
|
||||
// {{SQL CARBON EDIT}}
|
||||
extensionsPolicy: string;
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export enum ExtensionsPolicy {
|
||||
allowAll = 'allowAll',
|
||||
allowNone = 'allowNone',
|
||||
allowMicrosoft = 'allowMicrosoft'
|
||||
}
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
export interface IExtensionContainer {
|
||||
extension: IExtension | null;
|
||||
update(): void;
|
||||
}
|
||||
|
||||
export class ExtensionContainers extends Disposable {
|
||||
|
||||
constructor(
|
||||
private readonly containers: IExtensionContainer[],
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService
|
||||
) {
|
||||
super();
|
||||
this._register(extensionsWorkbenchService.onChange(this.update, this));
|
||||
}
|
||||
|
||||
set extension(extension: IExtension) {
|
||||
this.containers.forEach(c => c.extension = extension);
|
||||
}
|
||||
|
||||
private update(extension: IExtension): void {
|
||||
for (const container of this.containers) {
|
||||
if (extension && container.extension) {
|
||||
if (areSameExtensions(container.extension.identifier, extension.identifier)) {
|
||||
container.extension = extension;
|
||||
}
|
||||
} else {
|
||||
container.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
||||
export const ExtensionsConfigurationSchemaId = 'vscode://schemas/extensions';
|
||||
export const ExtensionsConfigurationSchema: IJSONSchema = {
|
||||
id: ExtensionsConfigurationSchemaId,
|
||||
allowComments: true,
|
||||
type: 'object',
|
||||
title: localize('app.extensions.json.title', "Extensions"),
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
recommendations: {
|
||||
type: 'array',
|
||||
description: localize('app.extensions.json.recommendations', "List of extensions which should be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."),
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: EXTENSION_IDENTIFIER_PATTERN,
|
||||
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
|
||||
},
|
||||
},
|
||||
unwantedRecommendations: {
|
||||
type: 'array',
|
||||
description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by VS Code that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."),
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: EXTENSION_IDENTIFIER_PATTERN,
|
||||
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const ExtensionsConfigurationInitialContent: string = [
|
||||
'{',
|
||||
'\t// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.',
|
||||
'\t// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp',
|
||||
'',
|
||||
'\t// List of extensions which should be recommended for users of this workspace.',
|
||||
'\t"recommendations": [',
|
||||
'\t\t',
|
||||
'\t],',
|
||||
'\t// List of extensions recommended by VS Code that should not be recommended for users of this workspace.',
|
||||
'\t"unwantedRecommendations": [',
|
||||
'\t\t',
|
||||
'\t]',
|
||||
'}'
|
||||
].join('\n');
|
||||
@@ -0,0 +1,55 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export class ExtensionsInput extends EditorInput {
|
||||
|
||||
static readonly ID = 'workbench.extensions.input2';
|
||||
get extension(): IExtension { return this._extension; }
|
||||
|
||||
constructor(
|
||||
private _extension: IExtension,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return ExtensionsInput.ID;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return localize('extensionsInputName', "Extension: {0}", this.extension.displayName);
|
||||
}
|
||||
|
||||
matches(other: any): boolean {
|
||||
if (!(other instanceof ExtensionsInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const otherExtensionInput = other as ExtensionsInput;
|
||||
|
||||
// TODO@joao is this correct?
|
||||
return this.extension === otherExtensionInput.extension;
|
||||
}
|
||||
|
||||
resolve(): Promise<any> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
supportsSplitEditor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getResource(): URI {
|
||||
return URI.from({
|
||||
scheme: 'extension',
|
||||
path: this.extension.identifier.id
|
||||
});
|
||||
}
|
||||
}
|
||||
138
src/vs/workbench/contrib/extensions/common/extensionsUtils.ts
Normal file
138
src/vs/workbench/contrib/extensions/common/extensionsUtils.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, IExtensionIdentifier, EnablementState, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { Severity, INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export interface IExtensionStatus {
|
||||
identifier: IExtensionIdentifier;
|
||||
local: ILocalExtension;
|
||||
globallyEnabled: boolean;
|
||||
}
|
||||
|
||||
export class KeymapExtensions implements IWorkbenchContribution {
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
|
||||
@IExtensionTipsService private readonly tipsService: IExtensionTipsService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
this.disposables.push(
|
||||
lifecycleService.onShutdown(() => this.dispose()),
|
||||
instantiationService.invokeFunction(onExtensionChanged)((identifiers => {
|
||||
Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))
|
||||
.then(undefined, onUnexpectedError);
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): Promise<void> {
|
||||
return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
|
||||
const keymaps = extensions.filter(extension => isKeymapExtension(this.tipsService, extension));
|
||||
const extension = arrays.first(keymaps, extension => areSameExtensions(extension.identifier, extensionIdentifier));
|
||||
if (extension && extension.globallyEnabled) {
|
||||
const otherKeymaps = keymaps.filter(extension => !areSameExtensions(extension.identifier, extensionIdentifier) && extension.globallyEnabled);
|
||||
if (otherKeymaps.length) {
|
||||
return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void {
|
||||
const onPrompt = (confirmed: boolean) => {
|
||||
const telemetryData: { [key: string]: any; } = {
|
||||
newKeymap: newKeymap.identifier,
|
||||
oldKeymaps: oldKeymaps.map(k => k.identifier),
|
||||
confirmed
|
||||
};
|
||||
/* __GDPR__
|
||||
"disableOtherKeymaps" : {
|
||||
"newKeymap": { "${inline}": [ "${ExtensionIdentifier}" ] },
|
||||
"oldKeymaps": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"confirmed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('disableOtherKeymaps', telemetryData);
|
||||
if (confirmed) {
|
||||
this.extensionEnablementService.setEnablement(oldKeymaps.map(keymap => keymap.local), EnablementState.Disabled);
|
||||
}
|
||||
};
|
||||
|
||||
this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')),
|
||||
[{
|
||||
label: localize('yes', "Yes"),
|
||||
run: () => onPrompt(true)
|
||||
}, {
|
||||
label: localize('no', "No"),
|
||||
run: () => onPrompt(false)
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {
|
||||
const extensionService = accessor.get(IExtensionManagementService);
|
||||
const extensionEnablementService = accessor.get(IExtensionEnablementService);
|
||||
const onDidInstallExtension = Event.chain(extensionService.onDidInstallExtension)
|
||||
.filter(e => e.operation === InstallOperation.Install)
|
||||
.event;
|
||||
return Event.debounce<IExtensionIdentifier[], IExtensionIdentifier[]>(Event.any(
|
||||
Event.chain(Event.any(onDidInstallExtension, extensionService.onDidUninstallExtension))
|
||||
.map(e => [e.identifier])
|
||||
.event,
|
||||
Event.map(extensionEnablementService.onEnablementChanged, extensions => extensions.map(e => e.identifier))
|
||||
), (result: IExtensionIdentifier[] | undefined, identifiers: IExtensionIdentifier[]) => {
|
||||
result = result || [];
|
||||
for (const identifier of identifiers) {
|
||||
if (result.some(l => !areSameExtensions(l, identifier))) {
|
||||
result.push(identifier);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
export function getInstalledExtensions(accessor: ServicesAccessor): Promise<IExtensionStatus[]> {
|
||||
const extensionService = accessor.get(IExtensionManagementService);
|
||||
const extensionEnablementService = accessor.get(IExtensionEnablementService);
|
||||
return extensionService.getInstalled().then(extensions => {
|
||||
return extensionEnablementService.getDisabledExtensions()
|
||||
.then(disabledExtensions => {
|
||||
return extensions.map(extension => {
|
||||
return {
|
||||
identifier: extension.identifier,
|
||||
local: extension,
|
||||
globallyEnabled: disabledExtensions.every(disabled => !areSameExtensions(disabled, extension.identifier))
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function isKeymapExtension(tipsService: IExtensionTipsService, extension: IExtensionStatus): boolean {
|
||||
const cats = extension.local.manifest.categories;
|
||||
return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().some(({ extensionId }) => areSameExtensions({ id: extensionId }, extension.local.identifier));
|
||||
}
|
||||
Reference in New Issue
Block a user