Recommend a curated list of remote extensions

This commit is contained in:
Joyce Er 2023-02-10 19:34:44 +00:00
parent 06fdf95f12
commit 0e78eb04b9
2 changed files with 78 additions and 12 deletions

View file

@ -85,6 +85,7 @@ export interface IProductConfiguration {
readonly configBasedExtensionTips?: { [id: string]: IConfigBasedExtensionTip };
readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip };
readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip };
readonly virtualWorkspaceExtensionTips?: { [virtualWorkspaceName: string]: IVirtualWorkspaceExtensionTip };
readonly extensionKeywords?: { [extension: string]: readonly string[] };
readonly keymapExtensionTips?: readonly string[];
readonly webExtensionTips?: readonly string[];
@ -199,6 +200,11 @@ export interface IRemoteExtensionTip {
extensionId: string;
}
export interface IVirtualWorkspaceExtensionTip {
friendlyName: string;
extensionId: string;
}
export interface ISurveyData {
surveyId: string;
surveyUrl: string;

View file

@ -16,7 +16,7 @@ import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/c
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Schemas } from 'vs/base/common/network';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { QuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { QuickPickItem, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
@ -31,16 +31,18 @@ import { getCodiconAriaLabel } from 'vs/base/common/iconLabels';
import { ILogService } from 'vs/platform/log/common/log';
import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionsViewPaneContainer, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { RemoteNameContext, VirtualWorkspaceContext } from 'vs/workbench/common/contextkeys';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IProductService } from 'vs/platform/product/common/productService';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { ThemeIcon } from 'vs/base/common/themables';
import { Codicon } from 'vs/base/common/codicons';
type ActionGroup = [string, Array<MenuItemAction | SubmenuItemAction>];
export class RemoteStatusIndicator extends Disposable implements IWorkbenchContribution {
@ -68,6 +70,9 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
private loggedInvalidGroupNames: { [group: string]: boolean } = Object.create(null);
private readonly recommendedExtensions = new Set<string>();
private extensionsToInstall = new Map<string, string>();
constructor(
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService,
@ -84,6 +89,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
@ILogService private readonly logService: ILogService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IProductService private readonly productService: IProductService,
) {
super();
@ -150,28 +156,56 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
}
if (this.extensionGalleryService.isEnabled()) {
const that = this;
registerAction2(class extends Action2 {
constructor() {
super({
id: RemoteStatusIndicator.INSTALL_REMOTE_EXTENSIONS_ID,
category,
// Only show this if there are missing remotes
title: { value: nls.localize('remote.install', "Install Remote Development Extensions"), original: 'Install Remote Development Extensions' },
f1: true
});
}
run = (accessor: ServicesAccessor, input: string) => {
const paneCompositeService = accessor.get(IPaneCompositePartService);
return paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true).then(viewlet => {
if (viewlet) {
(viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`tag:"remote-menu"`);
viewlet.focus();
const commandService = accessor.get(ICommandService);
const quickInputService = accessor.get(IQuickInputService);
const progressService = accessor.get(IProgressService);
const items: (IQuickPickItem & { extensionId: string })[] = [];
for (const [extensionId, label] of that.extensionsToInstall.entries()) {
items.push({ label, extensionId, buttons: [{ tooltip: nls.localize('remote.install.info', 'View Extension Description'), iconClass: ThemeIcon.asClassName(Codicon.info) }] });
}
items.sort((a, b) => a.label.localeCompare(b.label));
const quickPick = quickInputService.createQuickPick<IQuickPickItem & { extensionId: string }>();
quickPick.items = items;
quickPick.placeholder = nls.localize('remote.install.placeholder', "Select remote extensions to install");
quickPick.canSelectMany = true;
quickPick.selectedItems = items;
quickPick.ignoreFocusOut = true;
quickPick.onDidTriggerItemButton(e => {
commandService.executeCommand('extension.open', e.item.extensionId);
});
quickPick.onDidAccept(async () => {
try {
for (const item of quickPick.selectedItems) {
await progressService.withProgress(
{
location: ProgressLocation.Notification,
title: nls.localize("remote.install.progress", "Installing the {0} extension...", item.label),
},
async () => commandService.executeCommand('workbench.extensions.installExtension', item.extensionId)
);
}
} finally {
quickPick.dispose();
}
});
quickPick.show();
};
});
}
}
private registerListeners(): void {
@ -220,6 +254,22 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
this.updateRemoteStatusIndicator();
}));
}
this._register(this.extensionService.onDidChangeExtensions((e) => {
e.added.forEach((extensionIdentifier) => {
const id = extensionIdentifier.id?.toLowerCase();
if (id && this.recommendedExtensions.has(id)) {
this.extensionsToInstall.delete(id);
}
});
e.removed.forEach((extensionIdentifier) => {
let { id, displayName } = extensionIdentifier;
id = id?.toLowerCase();
if (id && displayName && this.recommendedExtensions.has(id)) {
this.extensionsToInstall.set(id, displayName);
}
});
}));
}
private updateVirtualWorkspaceLocation() {
@ -244,6 +294,16 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
})();
}
const extensionTips = { ...this.productService.remoteExtensionTips, ...this.productService.virtualWorkspaceExtensionTips };
for (const [_, extension] of Object.entries(extensionTips)) {
let { extensionId, friendlyName } = extension;
extensionId = extensionId.toLowerCase();
this.recommendedExtensions.add(extensionId);
if (!this.extensionService.extensions.some((extension) => extension.id?.toLowerCase() === extensionId)) {
this.extensionsToInstall.set(extensionId, friendlyName);
}
}
this.updateRemoteStatusIndicator();
}
@ -464,7 +524,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
}
}
if (!this.remoteAuthority && !this.virtualWorkspaceLocation && this.extensionGalleryService.isEnabled()) {
if (!this.remoteAuthority && !this.virtualWorkspaceLocation && this.extensionGalleryService.isEnabled() && this.extensionsToInstall.size > 0) {
items.push({
id: RemoteStatusIndicator.INSTALL_REMOTE_EXTENSIONS_ID,
label: nls.localize('installRemotes', "Install Additional Remote Extensions..."),