Skip to content

Commit

Permalink
Implement workspace extensions (#207465)
Browse files Browse the repository at this point in the history
* #206783 Implement workspace extensions

* fix compliaion

* fix web - add scanner service

* Web: add extensionsProfileScannerService to workbench
  • Loading branch information
sandy081 authored Mar 12, 2024
1 parent a6a3ae5 commit f944c75
Show file tree
Hide file tree
Showing 42 changed files with 981 additions and 192 deletions.
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

0 comments on commit f944c75

Please sign in to comment.