Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,69 @@ import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
import { IMarkdownString, MarkdownString } from '../../../base/common/htmlContent.js';

// --- Start Positron ---
/**
* List of extension IDs that conflict with Positron built-in features.
* Please use lower case for everything in here.
*/
const kPositronDuplicativeExtensions = [
'ikuyadeu.r',
'reditorsupport.r-lsp',
'reditorsupport.r',
'rdebugger.r-debugger',
'mikhail-arkhipov.r',
'vscode.r',
'jeanp413.open-remote-ssh',
'ms-python.python',
'ms-python.vscode-python-envs',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new code won't delete these extensions if you already had them. Should we go that far? (making sure we don't delete builtin ones like our version of ms-python.python of course)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think deleting them is very aggressive, since we can't know if we installed anything here or if the user did. I think the best option is a notification like in #10712.

'github.copilot-chat'
];

export interface PositronExtensionCompatibility {
compatible: boolean;
reason?: string;
}

/**
* Check if an extension is compatible with Positron (simple boolean check)
* @param extension Extension to check
* @returns true if compatible, false if it conflicts with Positron features
*/
function isPositronExtensionCompatible(extension: { name: string; publisher: string }): boolean {
const id = `${extension.publisher}.${extension.name}`.toLowerCase();
return !kPositronDuplicativeExtensions.includes(id);
}

/**
* Check if an extension is compatible with Positron
* @param extension Extension to check
* @returns Compatibility result with optional reason if incompatible
*/
export function positronExtensionCompatibility(extension: { name: string; publisher: string; displayName?: string }): PositronExtensionCompatibility {
if (!isPositronExtensionCompatible(extension)) {
return {
compatible: false,
reason: nls.localize(
'positronExtensionConflicts',
"Cannot install the '{0}' extension because it conflicts with Positron built-in features.", extension.displayName || extension.name
)
};
}
return { compatible: true };
}

/**
* Create an error for Positron extension incompatibility
* @param reason Optional reason for incompatibility
* @returns Error with ExtensionManagementErrorCode.Incompatible
*/
export function positronExtensionCompatibilityError(reason?: string): Error {
const error = new Error(reason || nls.localize('positronExtensionIncompatible', "Cannot install the extension because it is incompatible with Positron"));
error.name = ExtensionManagementErrorCode.Incompatible;
return error;
}
// --- End Positron ---

export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions };

export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI; readonly productVersion: IProductVersion };
Expand Down Expand Up @@ -362,6 +425,13 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
if (existing && existing.isApplicationScoped === !!options.isApplicationScoped) {
continue;
}
// --- Start Positron ---
// Skip Positron-incompatible dependencies and pack extensions
if (!isPositronExtensionCompatible(gallery)) {
this.logService.info(`Skipping dependency/packed extension '${gallery.identifier.id}' because it conflicts with Positron built-in features`);
continue;
}
// --- End Positron ---
createInstallExtensionTask(manifest, gallery, options, task);
}
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { createCommandUri, IMarkdownString, MarkdownString } from '../../../../b
import { verifiedPublisherIcon } from './extensionsIcons.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { IStringDictionary } from '../../../../base/common/collections.js';
import { CommontExtensionManagementService } from '../../../../platform/extensionManagement/common/abstractExtensionManagementService.js';
import { CommontExtensionManagementService, positronExtensionCompatibility, positronExtensionCompatibilityError } from '../../../../platform/extensionManagement/common/abstractExtensionManagementService.js';

const TrustedPublishersStorageKey = 'extensions.trustedPublishers';

Expand Down Expand Up @@ -461,11 +461,35 @@ export class ExtensionManagementService extends CommontExtensionManagementServic
return manifest;
}));

// --- Start Positron ---
// Check Positron compatibility for all extensions
for (let i = 0; i < extensions.length; i++) {
const { extension, options } = extensions[i];
const compat = positronExtensionCompatibility(extension);
if (!compat.compatible) {
results.set(extension.identifier.id.toLowerCase(), {
identifier: extension.identifier,
source: extension,
error: positronExtensionCompatibilityError(compat.reason),
operation: InstallOperation.Install,
profileLocation: options.profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource
});
}
}
// --- End Positron ---

if (extensions.some(e => e.options?.context?.[EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT] !== true)) {
await this.checkForTrustedPublishers(extensions.map((e, index) => ({ extension: e.extension, manifest: manifests[index], checkForPackAndDependencies: !e.options?.donotIncludePackAndDependencies })));
}

await Promise.all(extensions.map(async ({ extension, options }) => {
// --- Start Positron ---
// Skip extensions that already failed Positron compatibility check
if (results.has(extension.identifier.id.toLowerCase())) {
return;
}
// --- End Positron ---

try {
const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);
if (!manifest) {
Expand Down Expand Up @@ -523,22 +547,22 @@ export class ExtensionManagementService extends CommontExtensionManagementServic
}
// --- End Positron ---
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
if (!manifest) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strange formatting must have been left over from an upstream merge or something.

throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
}
if (!manifest) {
throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
}

if (installOptions?.context?.[EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT] !== true) {
await this.checkForTrustedPublishers([{ extension: gallery, manifest, checkForPackAndDependencies: !installOptions?.donotIncludePackAndDependencies }],);
}
if (installOptions?.context?.[EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT] !== true) {
await this.checkForTrustedPublishers([{ extension: gallery, manifest, checkForPackAndDependencies: !installOptions?.donotIncludePackAndDependencies }],);
}

if (installOptions?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== ExtensionInstallSource.SETTINGS_SYNC) {
if (installOptions?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== ExtensionInstallSource.SETTINGS_SYNC) {

await this.checkForWorkspaceTrust(manifest, false);
await this.checkForWorkspaceTrust(manifest, false);

if (!installOptions?.donotIncludePackAndDependencies) {
await this.checkInstallingExtensionOnWeb(gallery, manifest);
}
}
if (!installOptions?.donotIncludePackAndDependencies) {
await this.checkInstallingExtensionOnWeb(gallery, manifest);
}
}

servers = servers?.length ? this.validServers(gallery, manifest, servers) : await this.getExtensionManagementServersToInstall(gallery, manifest);
if (!installOptions || isUndefined(installOptions.isMachineScoped)) {
Expand Down Expand Up @@ -1201,46 +1225,6 @@ export class ExtensionManagementService extends CommontExtensionManagementServic
}
}

// --- Start Positron ---
const kPositronDuplicativeExtensions = [
'ikuyadeu.r',
'reditorsupport.r-lsp',
'reditorsupport.r',
'rdebugger.r-debugger',
'mikhail-arkhipov.r',
'vscode.r',
'jeanp413.open-remote-ssh',
'ms-python.python',
'GitHub.copilot-chat'
];

interface PositronExtensionCompatibilty {
compatible: boolean;
reason?: string;
}

function positronExtensionCompatibility(extension: { name: string; publisher: string; displayName?: string }): PositronExtensionCompatibilty {
const id = `${extension.publisher}.${extension.name}`.toLowerCase();
if (kPositronDuplicativeExtensions.includes(id)) {
return {
compatible: false,
reason: localize(
'positronExtensionConflicts',
"Cannot install the '{0}' extension because it conflicts with Positron built-in features.", extension.displayName || extension.name
)
};
} else {
return {
compatible: true
};
}
}
function positronExtensionCompatibilityError(reason?: string) {
const error = new Error(reason || localize('positronExtensionIncompatible', "Cannot install the extension because it is incompatible with Positron"));
error.name = ExtensionManagementErrorCode.Incompatible;
return error;
}
// --- End Positron ---

class WorkspaceExtensionsManagementService extends Disposable {

Expand Down
Loading