Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement workspace extensions #207465

Merged
merged 4 commits into from
Mar 12, 2024
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
10 changes: 10 additions & 0 deletions src/vs/platform/extensionManagement/common/extensionManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ export interface IGalleryExtension {
supportLink?: string;
}

export type InstallSource = 'gallery' | 'vsix' | 'resource';

export interface IGalleryMetadata {
id: string;
publisherId: string;
Expand All @@ -245,9 +247,11 @@ export type Metadata = Partial<IGalleryMetadata & {
hasPreReleaseVersion: boolean;
installedTimestamp: number;
pinned: boolean;
source: InstallSource;
}>;

export interface ILocalExtension extends IExtension {
isWorkspaceScoped: boolean;
isMachineScoped: boolean;
isApplicationScoped: boolean;
publisherId: string | null;
Expand All @@ -258,6 +262,7 @@ export interface ILocalExtension extends IExtension {
preRelease: boolean;
updated: boolean;
pinned: boolean;
source: InstallSource;
}

export const enum SortBy {
Expand Down Expand Up @@ -372,6 +377,7 @@ export interface InstallExtensionEvent {
readonly source: URI | IGalleryExtension;
readonly profileLocation?: URI;
readonly applicationScoped?: boolean;
readonly workspaceScoped?: boolean;
}

export interface InstallExtensionResult {
Expand All @@ -383,19 +389,22 @@ export interface InstallExtensionResult {
readonly context?: IStringDictionary<any>;
readonly profileLocation?: URI;
readonly applicationScoped?: boolean;
readonly workspaceScoped?: boolean;
}

export interface UninstallExtensionEvent {
readonly identifier: IExtensionIdentifier;
readonly profileLocation?: URI;
readonly applicationScoped?: boolean;
readonly workspaceScoped?: boolean;
}

export interface DidUninstallExtensionEvent {
readonly identifier: IExtensionIdentifier;
readonly error?: string;
readonly profileLocation?: URI;
readonly applicationScoped?: boolean;
readonly workspaceScoped?: boolean;
}

export enum ExtensionManagementErrorCode {
Expand Down Expand Up @@ -450,6 +459,7 @@ export class ExtensionGalleryError extends Error {

export type InstallOptions = {
isBuiltin?: boolean;
isWorkspaceScoped?: boolean;
isMachineScoped?: boolean;
isApplicationScoped?: boolean;
pinned?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
}

uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
if (extension.isWorkspaceScoped) {
throw new Error('Cannot uninstall a workspace extension');
}
return Promise.resolve(this.channel.call<void>('uninstall', [extension, options]));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { AbstractExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { AbstractExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IFileService } from 'vs/platform/files/common/files';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';

export class ExtensionsProfileScannerService extends AbstractExtensionsProfileScannerService {
constructor(
Expand All @@ -24,3 +25,5 @@ export class ExtensionsProfileScannerService extends AbstractExtensionsProfileSc
super(URI.file(environmentService.extensionsPath), fileService, userDataProfilesService, uriIdentityService, telemetryService, logService);
}
}

registerSingleton(IExtensionsProfileScannerService, ExtensionsProfileScannerService, InstantiationType.Delayed);
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
if (!local || !local.manifest.name || !local.manifest.version) {
throw new Error(`Cannot find a valid extension from the location ${location.toString()}`);
}
await this.addExtensionsToProfile([[local, undefined]], profileLocation);
await this.addExtensionsToProfile([[local, { source: 'resource' }]], profileLocation);
this.logService.info('Successfully installed extension', local.identifier.id, profileLocation.toString());
return local;
}
Expand Down Expand Up @@ -715,6 +715,8 @@ export class ExtensionsScanner extends Disposable {
installedTimestamp: extension.metadata?.installedTimestamp,
updated: !!extension.metadata?.updated,
pinned: !!extension.metadata?.pinned,
isWorkspaceScoped: false,
source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix')
};
}

Expand Down Expand Up @@ -906,7 +908,8 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask {
pinned: this.options.installGivenVersion ? true : (this.options.pinned ?? existingExtension?.pinned),
preRelease: isBoolean(this.options.preRelease)
? this.options.preRelease
: this.options.installPreReleaseVersion || this.gallery.properties.isPreReleaseVersion || existingExtension?.preRelease
: this.options.installPreReleaseVersion || this.gallery.properties.isPreReleaseVersion || existingExtension?.preRelease,
source: 'gallery',
};

if (existingExtension?.manifest.version === this.gallery.version) {
Expand Down Expand Up @@ -978,6 +981,7 @@ class InstallVSIXTask extends InstallExtensionTask {
isBuiltin: this.options.isBuiltin || existing?.isBuiltin,
installedTimestamp: Date.now(),
pinned: this.options.installGivenVersion ? true : (this.options.pinned ?? existing?.pinned),
source: 'vsix',
};

if (existing) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';

export const enum RecommendationSource {
Expand Down Expand Up @@ -43,6 +44,6 @@ export interface IExtensionRecommendationNotificationService {
hasToIgnoreRecommendationNotifications(): boolean;

promptImportantExtensionsInstallNotification(recommendations: IExtensionRecommendations): Promise<RecommendationsNotificationResult>;
promptWorkspaceRecommendations(recommendations: string[]): Promise<void>;
promptWorkspaceRecommendations(recommendations: Array<string | URI>): Promise<void>;
}

Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {

private toExtensionRecommendation(tip: IConfigBasedExtensionTip): ConfigBasedExtensionRecommendation {
return {
extensionId: tip.extensionId,
extension: tip.extensionId,
reason: {
reasonId: ExtensionRecommendationReason.WorkspaceConfig,
reasonText: localize('exeBasedRecommendation', "This extension is recommended because of the current workspace configuration")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class ExeBasedRecommendations extends ExtensionRecommendations {

private toExtensionRecommendation(tip: IExecutableBasedExtensionTip): ExtensionRecommendation {
return {
extensionId: tip.extensionId.toLowerCase(),
extension: tip.extensionId.toLowerCase(),
reason: {
reasonId: ExtensionRecommendationReason.Executable,
reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", tip.exeFriendlyName)
Expand Down
39 changes: 39 additions & 0 deletions src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { defaultCheckboxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { buttonForeground, buttonHoverBackground, editorBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { ViewContainerLocation } from 'vs/workbench/common/views';
Expand Down Expand Up @@ -78,6 +79,7 @@ import { ExtensionData, ExtensionsGridView, ExtensionsTree, getExtensions } from
import { ExtensionRecommendationWidget, ExtensionStatusWidget, ExtensionWidget, InstallCountWidget, RatingsWidget, RemoteBadgeWidget, SponsorWidget, VerifiedPublisherWidget, onClick } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
import { ExtensionContainers, ExtensionEditorTab, ExtensionState, IExtension, IExtensionContainer, IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { IExplorerService } from 'vs/workbench/contrib/files/browser/files';
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer';
import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update';
import { IWebview, IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview';
Expand All @@ -86,6 +88,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { VIEW_ID as EXPLORER_VIEW_ID } from 'vs/workbench/contrib/files/common/files';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';

class NavBar extends Disposable {

Expand Down Expand Up @@ -155,6 +160,7 @@ interface IExtensionEditorTemplate {
builtin: HTMLElement;
publisher: HTMLElement;
publisherDisplayName: HTMLElement;
resource: HTMLElement;
installCount: HTMLElement;
rating: HTMLElement;
description: HTMLElement;
Expand Down Expand Up @@ -245,6 +251,10 @@ export class ExtensionEditor extends EditorPane {
@ILanguageService private readonly languageService: ILanguageService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IExplorerService private readonly explorerService: IExplorerService,
@IViewsService private readonly viewsService: IViewsService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
) {
super(ExtensionEditor.ID, group, telemetryService, themeService, storageService);
this.extensionReadme = null;
Expand Down Expand Up @@ -291,6 +301,9 @@ export class ExtensionEditor extends EditorPane {
const publisherDisplayName = append(publisher, $('.publisher-name'));
const verifiedPublisherWidget = this.instantiationService.createInstance(VerifiedPublisherWidget, append(publisher, $('.verified-publisher')), false);

const resource = append(append(subtitle, $('.subtitle-entry.resource')), $('', { tabIndex: 0 }));
resource.setAttribute('role', 'button');

const installCount = append(append(subtitle, $('.subtitle-entry')), $('span.install', { tabIndex: 0 }));
this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), installCount, localize('install count', "Install count")));
const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCount, false);
Expand Down Expand Up @@ -419,6 +432,7 @@ export class ExtensionEditor extends EditorPane {
preview,
publisher,
publisherDisplayName,
resource,
rating,
actionsAndStatusContainer,
extensionActionBar,
Expand Down Expand Up @@ -473,6 +487,12 @@ export class ExtensionEditor extends EditorPane {
}

private async getGalleryVersionToShow(extension: IExtension, preRelease?: boolean): Promise<IGalleryExtension | null> {
if (extension.resourceExtension) {
return null;
}
if (extension.local?.source === 'resource') {
return null;
}
if (isUndefined(preRelease)) {
return null;
}
Expand Down Expand Up @@ -521,6 +541,25 @@ export class ExtensionEditor extends EditorPane {
// subtitle
template.publisher.classList.toggle('clickable', !!extension.url);
template.publisherDisplayName.textContent = extension.publisherDisplayName;
template.publisher.parentElement?.classList.toggle('hide', !!extension.resourceExtension || extension.local?.source === 'resource');

const location = extension.resourceExtension?.location ?? (extension.local?.source === 'resource' ? extension.local?.location : undefined);
template.resource.parentElement?.classList.toggle('hide', !location);
if (location) {
const workspaceFolder = this.contextService.getWorkspaceFolder(location);
if (workspaceFolder && extension.isWorkspaceScoped) {
template.resource.parentElement?.classList.add('clickable');
this.transientDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), template.resource, this.uriIdentityService.extUri.relativePath(workspaceFolder.uri, location)));
template.resource.textContent = localize('workspace extension', "Workspace Extension");
this.transientDisposables.add(onClick(template.resource, () => {
this.viewsService.openView(EXPLORER_VIEW_ID, true).then(() => this.explorerService.select(location, true));
}));
} else {
template.resource.parentElement?.classList.remove('clickable');
this.transientDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), template.resource, location.path));
template.resource.textContent = localize('local extension', "Local Extension");
}
}

template.installCount.parentElement?.classList.toggle('hide', !extension.url);
template.rating.parentElement?.classList.toggle('hide', !extension.url);
Expand Down
Loading
Loading