@@ -12,15 +12,16 @@ import { once } from 'vs/base/common/functional';
12
12
import { IProductService } from 'vs/platform/product/common/productService' ;
13
13
import { Action2 , MenuRegistry , registerAction2 } from 'vs/platform/actions/common/actions' ;
14
14
import { 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' ;
16
16
import { retry } from 'vs/base/common/async' ;
17
17
import { Registry } from 'vs/platform/registry/common/platform' ;
18
18
import { ConfigurationScope , Extensions as ConfigurationExtensions , IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry' ;
19
19
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration' ;
20
20
import { CancellationToken } from 'vs/base/common/cancellation' ;
21
21
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
22
22
import { 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' ;
24
25
25
26
const STATUSBAR_REMOTEINDICATOR_CONTRIBUTION = 'statusBar/remoteIndicator' ;
26
27
@@ -36,28 +37,33 @@ type RemoteStartActionEvent = {
36
37
remoteExtensionId ?: string ;
37
38
} ;
38
39
40
+ interface RemoteCommand {
41
+ command : string ;
42
+ commandContext : string | undefined ;
43
+ }
44
+
39
45
interface RemoteExtensionMetadata {
40
46
id : string ;
41
47
friendlyName : string ;
42
- remoteCommands : string [ ] ;
48
+ remoteCommands : RemoteCommand [ ] ;
43
49
installed : boolean ;
44
- dependenciesStr : string ;
45
- isPlatformCompatible : boolean | undefined ;
50
+ dependencies : string [ ] ;
51
+ isPlatformCompatible : boolean ;
46
52
}
47
53
48
54
export class RemoteStartEntry extends Disposable implements IWorkbenchContribution {
49
55
50
56
private static readonly REMOTE_START_ENTRY_ACTIONS_COMMAND_ID = 'workbench.action.remote.showStartEntryActions' ;
51
57
private readonly remoteExtensionMetadata : RemoteExtensionMetadata [ ] ;
52
58
private _isInitialized : boolean = false ;
53
- private targetPlatform : TargetPlatform = TargetPlatform . UNKNOWN ;
54
59
55
60
constructor (
56
61
@IQuickInputService private readonly quickInputService : IQuickInputService ,
57
62
@ICommandService private readonly commandService : ICommandService ,
58
63
@IProductService private readonly productService : IProductService ,
59
64
@IExtensionService private readonly extensionService : IExtensionService ,
60
65
@IExtensionManagementService private readonly extensionManagementService : IExtensionManagementService ,
66
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService : IWorkbenchExtensionEnablementService ,
61
67
@IExtensionGalleryService private readonly extensionGalleryService : IExtensionGalleryService ,
62
68
@ITelemetryService private readonly telemetryService : ITelemetryService ,
63
69
@IContextKeyService private readonly contextKeyService : IContextKeyService ) {
@@ -66,7 +72,7 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
66
72
registerConfiguration ( this . productService . quality !== 'stable' ) ;
67
73
const remoteExtensionTips = { ...this . productService . remoteExtensionTips , ...this . productService . virtualWorkspaceExtensionTips } ;
68
74
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 : [ ] } ;
70
76
} ) ;
71
77
72
78
this . registerActions ( ) ;
@@ -95,66 +101,69 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
95
101
}
96
102
97
103
private registerListeners ( ) : void {
98
-
99
104
this . _register ( this . extensionManagementService . onDidInstallExtensions ( async ( result ) => {
100
105
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
+ }
102
111
}
103
112
} ) ) ;
104
113
105
114
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
+ }
107
119
} ) ) ;
108
- }
109
120
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
+ }
110
133
111
134
private async _init ( ) : Promise < void > {
112
135
if ( this . _isInitialized ) {
113
136
return ;
114
137
}
115
138
116
- this . targetPlatform = await this . extensionManagementService . getTargetPlatform ( ) ;
117
139
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 ;
121
146
}
122
147
else {
123
- await this . updateInstallStatus ( this . remoteExtensionMetadata [ i ] . id , false ) ;
148
+ this . remoteExtensionMetadata [ i ] . isPlatformCompatible = true ;
149
+ this . remoteExtensionMetadata [ i ] . dependencies = galleryExtension . properties . extensionPack ?? [ ] ;
124
150
}
125
- }
126
- this . _isInitialized = true ;
127
- }
128
151
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 ) ) ;
132
154
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 ) ;
149
159
}
150
- this . remoteExtensionMetadata [ index ] . installed = false ;
151
160
}
152
- return this . remoteExtensionMetadata [ index ] ;
153
161
}
154
- return undefined ;
162
+
163
+ this . _isInitialized = true ;
155
164
}
156
165
157
- private async getRemoteCommands ( remoteExtensionId : string ) : Promise < string [ ] | undefined > {
166
+ private async getRemoteCommands ( remoteExtensionId : string ) : Promise < RemoteCommand [ ] > {
158
167
159
168
const extension = await retry ( async ( ) => {
160
169
const ext = await this . extensionService . getExtension ( remoteExtensionId ) ;
@@ -165,22 +174,21 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
165
174
} , 300 , 10 ) ;
166
175
167
176
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 } ) ;
179
187
}
180
188
}
181
- return commands ;
182
189
}
183
- return undefined ;
190
+
191
+ return commands ;
184
192
}
185
193
186
194
private async showRemoteStartActions ( ) {
@@ -193,26 +201,31 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
193
201
if ( metadata . installed && metadata . remoteCommands ) {
194
202
installedItems . push ( { type : 'separator' , label : metadata . friendlyName } ) ;
195
203
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 ) ;
197
211
const label = typeof commandAction ?. title === 'string' ? commandAction . title : commandAction ?. title ?. value ;
198
212
if ( label ) {
199
213
installedItems . push ( {
200
214
type : 'item' ,
201
215
label : label ,
202
- id : command
216
+ id : command . command
203
217
} ) ;
204
218
}
205
219
}
206
220
}
207
221
else if ( ! metadata . installed && metadata . isPlatformCompatible ) {
208
222
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 } ) ;
211
224
}
212
225
}
213
226
214
227
installedItems . push ( {
215
- type : 'separator' , label : nls . localize ( 'remote.startActions.downloadAndInstall ' , 'Download and Install' )
228
+ type : 'separator' , label : nls . localize ( 'remote.startActions.install ' , 'Install' )
216
229
} ) ;
217
230
return installedItems . concat ( notInstalledItems ) ;
218
231
} ;
@@ -223,16 +236,15 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
223
236
quickPick . sortByLabel = false ;
224
237
quickPick . canSelectMany = false ;
225
238
quickPick . ignoreFocusOut = false ;
226
- quickPick . busy = false ;
227
239
once ( quickPick . onDidAccept ) ( async ( ) => {
228
240
229
241
const selectedItems = quickPick . selectedItems ;
230
242
if ( selectedItems . length === 1 ) {
231
243
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 ) ) ;
234
246
if ( remoteExtension ) {
235
- quickPick . busy = true ;
247
+
236
248
quickPick . placeholder = nls . localize ( 'remote.startActions.installingExtension' , 'Installing extension... ' ) ;
237
249
238
250
const galleryExtension = ( await this . extensionGalleryService . getExtensions ( [ { id : selectedItem } ] , CancellationToken . None ) ) [ 0 ] ;
@@ -243,14 +255,15 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
243
255
} ) ;
244
256
245
257
this . telemetryService . publicLog2 < RemoteStartActionEvent , RemoteStartActionClassification > ( 'remoteStartList.ActionExecuted' , { command : 'workbench.extensions.installExtension' , remoteExtensionId : selectedItem } ) ;
258
+ const commands = await this . getRemoteCommands ( selectedItem ) ;
246
259
247
- quickPick . busy = false ;
260
+ await this . extensionService . activateByEvent ( `onCommand: ${ commands [ 0 ] } ` ) ;
248
261
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 ;
254
267
}
255
268
else {
256
269
this . commandService . executeCommand ( selectedItem ) ;
@@ -260,25 +273,6 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi
260
273
} ) ;
261
274
quickPick . show ( ) ;
262
275
}
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
- }
282
276
}
283
277
284
278
function registerConfiguration ( enabled : boolean ) : void {
0 commit comments