@@ -12,15 +12,16 @@ import { once } from 'vs/base/common/functional';
1212import { IProductService } from 'vs/platform/product/common/productService' ;
1313import { Action2 , MenuRegistry , registerAction2 } from 'vs/platform/actions/common/actions' ;
1414import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions' ;
15- import { EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT , IExtensionGalleryService , IExtensionManagementService , isTargetPlatformCompatible } from 'vs/platform/extensionManagement/common/extensionManagement' ;
15+ import { EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT , IExtensionGalleryService , IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement' ;
1616import { retry } from 'vs/base/common/async' ;
1717import { Registry } from 'vs/platform/registry/common/platform' ;
1818import { ConfigurationScope , Extensions as ConfigurationExtensions , IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry' ;
1919import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration' ;
2020import { CancellationToken } from 'vs/base/common/cancellation' ;
2121import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
2222import { ContextKeyExpr , IContextKeyService } from 'vs/platform/contextkey/common/contextkey' ;
23- import { TargetPlatform } from 'vs/platform/extensions/common/extensions' ;
23+ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions' ;
24+ import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement' ;
2425
2526const STATUSBAR_REMOTEINDICATOR_CONTRIBUTION = 'statusBar/remoteIndicator' ;
2627
@@ -36,28 +37,33 @@ type RemoteStartActionEvent = {
3637 remoteExtensionId ?: string ;
3738} ;
3839
40+ interface RemoteCommand {
41+ command : string ;
42+ commandContext : string | undefined ;
43+ }
44+
3945interface RemoteExtensionMetadata {
4046 id : string ;
4147 friendlyName : string ;
42- remoteCommands : string [ ] ;
48+ remoteCommands : RemoteCommand [ ] ;
4349 installed : boolean ;
44- dependenciesStr : string ;
45- isPlatformCompatible : boolean | undefined ;
50+ dependencies : string [ ] ;
51+ isPlatformCompatible : boolean ;
4652}
4753
4854export class RemoteStartEntry extends Disposable implements IWorkbenchContribution {
4955
5056 private static readonly REMOTE_START_ENTRY_ACTIONS_COMMAND_ID = 'workbench.action.remote.showStartEntryActions' ;
5157 private readonly remoteExtensionMetadata : RemoteExtensionMetadata [ ] ;
5258 private _isInitialized : boolean = false ;
53- private targetPlatform : TargetPlatform = TargetPlatform . UNKNOWN ;
5459
5560 constructor (
5661 @IQuickInputService private readonly quickInputService : IQuickInputService ,
5762 @ICommandService private readonly commandService : ICommandService ,
5863 @IProductService private readonly productService : IProductService ,
5964 @IExtensionService private readonly extensionService : IExtensionService ,
6065 @IExtensionManagementService private readonly extensionManagementService : IExtensionManagementService ,
66+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService : IWorkbenchExtensionEnablementService ,
6167 @IExtensionGalleryService private readonly extensionGalleryService : IExtensionGalleryService ,
6268 @ITelemetryService private readonly telemetryService : ITelemetryService ,
6369 @IContextKeyService private readonly contextKeyService : IContextKeyService ) {
@@ -66,7 +72,7 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
6672 registerConfiguration ( this . productService . quality !== 'stable' ) ;
6773 const remoteExtensionTips = { ...this . productService . remoteExtensionTips , ...this . productService . virtualWorkspaceExtensionTips } ;
6874 this . remoteExtensionMetadata = Object . values ( remoteExtensionTips ) . filter ( value => value . showInStartEntry === true ) . map ( value => {
69- return { id : value . extensionId , installed : false , friendlyName : value . friendlyName , remoteCommands : [ ] , isPlatformCompatible : undefined , dependenciesStr : '' } ;
75+ return { id : value . extensionId , installed : false , friendlyName : value . friendlyName , remoteCommands : [ ] , isPlatformCompatible : false , dependencies : [ ] } ;
7076 } ) ;
7177
7278 this . registerActions ( ) ;
@@ -95,66 +101,69 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
95101 }
96102
97103 private registerListeners ( ) : void {
98-
99104 this . _register ( this . extensionManagementService . onDidInstallExtensions ( async ( result ) => {
100105 for ( const ext of result ) {
101- await this . updateInstallStatus ( ext . identifier . id , true ) ;
106+ const index = this . remoteExtensionMetadata . findIndex ( value => ExtensionIdentifier . equals ( value . id , ext . identifier . id ) ) ;
107+ if ( index > - 1 ) {
108+ this . remoteExtensionMetadata [ index ] . installed = true ;
109+ this . remoteExtensionMetadata [ index ] . remoteCommands = await this . getRemoteCommands ( ext . identifier . id ) ;
110+ }
102111 }
103112 } ) ) ;
104113
105114 this . _register ( this . extensionManagementService . onDidUninstallExtension ( async ( result ) => {
106- await this . updateInstallStatus ( result . identifier . id , false ) ;
115+ const index = this . remoteExtensionMetadata . findIndex ( value => ExtensionIdentifier . equals ( value . id , result . identifier . id ) ) ;
116+ if ( index > - 1 ) {
117+ this . remoteExtensionMetadata [ index ] . installed = false ;
118+ }
107119 } ) ) ;
108- }
109120
121+ this . _register ( this . extensionEnablementService . onEnablementChanged ( async ( result ) => {
122+ for ( const ext of result ) {
123+ const index = this . remoteExtensionMetadata . findIndex ( value => ExtensionIdentifier . equals ( value . id , ext . identifier . id ) ) ;
124+ if ( index > - 1 ) {
125+ // update remote commands for extension if we never fetched it when it was disabled.
126+ if ( this . extensionEnablementService . isEnabled ( ext ) && this . remoteExtensionMetadata [ index ] . remoteCommands . length === 0 ) {
127+ this . remoteExtensionMetadata [ index ] . remoteCommands = await this . getRemoteCommands ( ext . identifier . id ) ;
128+ }
129+ }
130+ }
131+ } ) ) ;
132+ }
110133
111134 private async _init ( ) : Promise < void > {
112135 if ( this . _isInitialized ) {
113136 return ;
114137 }
115138
116- this . targetPlatform = await this . extensionManagementService . getTargetPlatform ( ) ;
117139 for ( let i = 0 ; i < this . remoteExtensionMetadata . length ; i ++ ) {
118- const installed = this . extensionService . extensions . some ( ( e ) => e . id ?. toLowerCase ( ) === this . remoteExtensionMetadata [ i ] . id ) ;
119- if ( installed ) {
120- await this . updateInstallStatus ( this . remoteExtensionMetadata [ i ] . id , true ) ;
140+ const extensionId = this . remoteExtensionMetadata [ i ] . id ;
141+
142+ // Update compatibility
143+ const galleryExtension = ( await this . extensionGalleryService . getExtensions ( [ { id : extensionId } ] , CancellationToken . None ) ) [ 0 ] ;
144+ if ( ! await this . extensionManagementService . canInstall ( galleryExtension ) ) {
145+ this . remoteExtensionMetadata [ i ] . isPlatformCompatible = false ;
121146 }
122147 else {
123- await this . updateInstallStatus ( this . remoteExtensionMetadata [ i ] . id , false ) ;
148+ this . remoteExtensionMetadata [ i ] . isPlatformCompatible = true ;
149+ this . remoteExtensionMetadata [ i ] . dependencies = galleryExtension . properties . extensionPack ?? [ ] ;
124150 }
125- }
126- this . _isInitialized = true ;
127- }
128151
129- private async updateInstallStatus ( extensionId : string , installed : boolean ) : Promise < RemoteExtensionMetadata | undefined > {
130- const index = this . remoteExtensionMetadata . findIndex ( value => value . id === extensionId ) ;
131- if ( index > - 1 ) {
152+ // Check if installed and enabled
153+ const installed = ( await this . extensionManagementService . getInstalled ( ) ) . find ( value => ExtensionIdentifier . equals ( value . identifier . id , extensionId ) ) ;
132154 if ( installed ) {
133- const commands = await this . getRemoteCommands ( extensionId ) ;
134- if ( commands ) {
135- this . remoteExtensionMetadata [ index ] . remoteCommands = commands ;
136- this . remoteExtensionMetadata [ index ] . isPlatformCompatible = true ;
137- } else {
138- this . remoteExtensionMetadata [ index ] . isPlatformCompatible = false ;
139- }
140- this . remoteExtensionMetadata [ index ] . installed = true ;
141- } else if ( ! installed ) {
142- const dependenciesStr = await this . getDependenciesFromGallery ( this . remoteExtensionMetadata [ index ] . id ) ;
143- if ( dependenciesStr !== undefined ) {
144- this . remoteExtensionMetadata [ index ] . dependenciesStr = dependenciesStr ;
145- this . remoteExtensionMetadata [ index ] . isPlatformCompatible = true ;
146- }
147- else {
148- this . remoteExtensionMetadata [ index ] . isPlatformCompatible = false ;
155+ this . remoteExtensionMetadata [ i ] . installed = true ;
156+ if ( this . extensionEnablementService . isEnabled ( installed ) ) {
157+ // update commands if enabled
158+ this . remoteExtensionMetadata [ i ] . remoteCommands = await this . getRemoteCommands ( extensionId ) ;
149159 }
150- this . remoteExtensionMetadata [ index ] . installed = false ;
151160 }
152- return this . remoteExtensionMetadata [ index ] ;
153161 }
154- return undefined ;
162+
163+ this . _isInitialized = true ;
155164 }
156165
157- private async getRemoteCommands ( remoteExtensionId : string ) : Promise < string [ ] | undefined > {
166+ private async getRemoteCommands ( remoteExtensionId : string ) : Promise < RemoteCommand [ ] > {
158167
159168 const extension = await retry ( async ( ) => {
160169 const ext = await this . extensionService . getExtension ( remoteExtensionId ) ;
@@ -165,22 +174,21 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
165174 } , 300 , 10 ) ;
166175
167176 const menus = extension ?. contributes ?. menus ;
168- if ( menus ) {
169- const commands : string [ ] = [ ] ;
170- for ( const contextMenu in menus ) {
171- // The remote start entry pulls the first command from the statusBar/remoteIndicator menu contribution
172- if ( contextMenu === STATUSBAR_REMOTEINDICATOR_CONTRIBUTION ) {
173- for ( const command of menus [ contextMenu ] ) {
174- const expression = ContextKeyExpr . deserialize ( command . when ) ;
175- if ( this . contextKeyService . contextMatchesRules ( expression ) ) {
176- commands . push ( command . command ) ;
177- }
178- }
177+ if ( ! menus ) {
178+ throw Error ( 'Failed to find remoteIndicator menu' ) ;
179+ }
180+
181+ const commands : RemoteCommand [ ] = [ ] ;
182+ for ( const contextMenu in menus ) {
183+ // The remote start entry pulls the first command from the statusBar/remoteIndicator menu contribution
184+ if ( contextMenu === STATUSBAR_REMOTEINDICATOR_CONTRIBUTION ) {
185+ for ( const command of menus [ contextMenu ] ) {
186+ commands . push ( { command : command . command , commandContext : command . when } ) ;
179187 }
180188 }
181- return commands ;
182189 }
183- return undefined ;
190+
191+ return commands ;
184192 }
185193
186194 private async showRemoteStartActions ( ) {
@@ -193,26 +201,31 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
193201 if ( metadata . installed && metadata . remoteCommands ) {
194202 installedItems . push ( { type : 'separator' , label : metadata . friendlyName } ) ;
195203 for ( const command of metadata . remoteCommands ) {
196- const commandAction = MenuRegistry . getCommand ( command ) ;
204+
205+ const expression = ContextKeyExpr . deserialize ( command . commandContext ) ;
206+ if ( ! this . contextKeyService . contextMatchesRules ( expression ) ) {
207+ continue ;
208+ }
209+
210+ const commandAction = MenuRegistry . getCommand ( command . command ) ;
197211 const label = typeof commandAction ?. title === 'string' ? commandAction . title : commandAction ?. title ?. value ;
198212 if ( label ) {
199213 installedItems . push ( {
200214 type : 'item' ,
201215 label : label ,
202- id : command
216+ id : command . command
203217 } ) ;
204218 }
205219 }
206220 }
207221 else if ( ! metadata . installed && metadata . isPlatformCompatible ) {
208222 const label = nls . localize ( 'remote.startActions.connectTo' , 'Connect to {0}... ' , metadata . friendlyName ) ;
209- const tooltip = metadata . dependenciesStr ? nls . localize ( 'remote.startActions.tooltip' , 'Also installs dependencies - {0} ' , metadata . dependenciesStr ) : '' ;
210- notInstalledItems . push ( { type : 'item' , id : metadata . id , label : label , tooltip : tooltip } ) ;
223+ notInstalledItems . push ( { type : 'item' , id : metadata . id , label : label } ) ;
211224 }
212225 }
213226
214227 installedItems . push ( {
215- type : 'separator' , label : nls . localize ( 'remote.startActions.downloadAndInstall ' , 'Download and Install' )
228+ type : 'separator' , label : nls . localize ( 'remote.startActions.install ' , 'Install' )
216229 } ) ;
217230 return installedItems . concat ( notInstalledItems ) ;
218231 } ;
@@ -223,16 +236,15 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
223236 quickPick . sortByLabel = false ;
224237 quickPick . canSelectMany = false ;
225238 quickPick . ignoreFocusOut = false ;
226- quickPick . busy = false ;
227239 once ( quickPick . onDidAccept ) ( async ( ) => {
228240
229241 const selectedItems = quickPick . selectedItems ;
230242 if ( selectedItems . length === 1 ) {
231243 const selectedItem = selectedItems [ 0 ] . id ! ;
232-
233- const remoteExtension = this . remoteExtensionMetadata . find ( value => value . id === selectedItem ) ;
244+ quickPick . busy = true ;
245+ const remoteExtension = this . remoteExtensionMetadata . find ( value => ExtensionIdentifier . equals ( value . id , selectedItem ) ) ;
234246 if ( remoteExtension ) {
235- quickPick . busy = true ;
247+
236248 quickPick . placeholder = nls . localize ( 'remote.startActions.installingExtension' , 'Installing extension... ' ) ;
237249
238250 const galleryExtension = ( await this . extensionGalleryService . getExtensions ( [ { id : selectedItem } ] , CancellationToken . None ) ) [ 0 ] ;
@@ -243,14 +255,15 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
243255 } ) ;
244256
245257 this . telemetryService . publicLog2 < RemoteStartActionEvent , RemoteStartActionClassification > ( 'remoteStartList.ActionExecuted' , { command : 'workbench.extensions.installExtension' , remoteExtensionId : selectedItem } ) ;
258+ const commands = await this . getRemoteCommands ( selectedItem ) ;
246259
247- quickPick . busy = false ;
260+ await this . extensionService . activateByEvent ( `onCommand: ${ commands [ 0 ] } ` ) ;
248261
249- const metadata = await this . updateInstallStatus ( selectedItem , true ) ;
250- if ( metadata ) {
251- this . telemetryService . publicLog2 < RemoteStartActionEvent , RemoteStartActionClassification > ( 'remoteStartList.ActionExecuted' , { command : metadata ?. remoteCommands [ 0 ] , remoteExtensionId : metadata ?. id } ) ;
252- this . commandService . executeCommand ( metadata ?. remoteCommands [ 0 ] ) ;
253- }
262+ const command = commands [ 0 ] . command ;
263+ this . commandService . executeCommand ( command ) ;
264+
265+ this . telemetryService . publicLog2 < RemoteStartActionEvent , RemoteStartActionClassification > ( 'remoteStartList.ActionExecuted' , { command : command , remoteExtensionId : selectedItem } ) ;
266+ quickPick . busy = false ;
254267 }
255268 else {
256269 this . commandService . executeCommand ( selectedItem ) ;
@@ -260,25 +273,6 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
260273 } ) ;
261274 quickPick . show ( ) ;
262275 }
263-
264- private async getDependenciesFromGallery ( extensionId : string ) : Promise < string | undefined > {
265-
266- const galleryExtension = ( await this . extensionGalleryService . getExtensions ( [ { id : extensionId } ] , CancellationToken . None ) ) [ 0 ] ;
267- const canInstall = galleryExtension . allTargetPlatforms . some ( targetPlatform => isTargetPlatformCompatible ( targetPlatform , galleryExtension . allTargetPlatforms , this . targetPlatform ) ) ;
268- if ( ! canInstall ) {
269- return undefined ;
270- }
271-
272- const friendlyNames : string [ ] = [ ] ;
273- if ( galleryExtension . properties . extensionPack ) {
274- for ( const extensionPackItem of galleryExtension . properties . extensionPack ) {
275- const extensionPack = ( await this . extensionGalleryService . getExtensions ( [ { id : extensionPackItem } ] , CancellationToken . None ) ) [ 0 ] ;
276- friendlyNames . push ( extensionPack . displayName ) ;
277- }
278- }
279-
280- return friendlyNames . join ( ', ' ) ;
281- }
282276}
283277
284278function registerConfiguration ( enabled : boolean ) : void {
0 commit comments