@@ -19,11 +19,11 @@ import { Registry } from 'vs/platform/registry/common/platform';
19
19
import { ThemeIcon } from 'vs/platform/theme/common/themeService' ;
20
20
import { Extensions as WorkbenchExtensions , IWorkbenchContribution , IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions' ;
21
21
import { ViewContainerLocation } from 'vs/workbench/common/views' ;
22
- import { IExtensionsViewPaneContainer , VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions' ;
22
+ import { IExtensionsViewPaneContainer , IExtensionsWorkbenchService , VIEWLET_ID as EXTENSION_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions' ;
23
23
import { CENTER_ACTIVE_CELL } from 'vs/workbench/contrib/notebook/browser/contrib/navigation/arrow' ;
24
24
import { NOTEBOOK_ACTIONS_CATEGORY , SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions' ;
25
25
import { NOTEBOOK_MISSING_KERNEL_EXTENSION , NOTEBOOK_IS_ACTIVE_EDITOR , NOTEBOOK_KERNEL_COUNT , NOTEBOOK_KERNEL_SOURCE_COUNT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys' ;
26
- import { getNotebookEditorFromEditorPane , INotebookEditor , KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser' ;
26
+ import { getNotebookEditorFromEditorPane , INotebookEditor , INotebookExtensionRecommendation , KERNEL_RECOMMENDATIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser' ;
27
27
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget' ;
28
28
import { configureKernelIcon , selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons' ;
29
29
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel' ;
@@ -34,6 +34,11 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
34
34
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite' ;
35
35
import { IStatusbarEntry , IStatusbarEntryAccessor , IStatusbarService , StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar' ;
36
36
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures' ;
37
+ import { CancellationToken } from 'vs/base/common/cancellation' ;
38
+ import { ProgressLocation } from 'vs/platform/progress/common/progress' ;
39
+ import { IProductService } from 'vs/platform/product/common/productService' ;
40
+ import { Codicon } from 'vs/base/common/codicons' ;
41
+ import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions' ;
37
42
38
43
registerAction2 ( class extends Action2 {
39
44
constructor ( ) {
@@ -103,10 +108,13 @@ registerAction2(class extends Action2 {
103
108
) : Promise < boolean > {
104
109
const notebookKernelService = accessor . get ( INotebookKernelService ) ;
105
110
const editorService = accessor . get ( IEditorService ) ;
111
+ const productService = accessor . get ( IProductService ) ;
106
112
const quickInputService = accessor . get ( IQuickInputService ) ;
107
113
const labelService = accessor . get ( ILabelService ) ;
108
114
const logService = accessor . get ( ILogService ) ;
109
115
const paneCompositeService = accessor . get ( IPaneCompositePartService ) ;
116
+ const extensionWorkbenchService = accessor . get ( IExtensionsWorkbenchService ) ;
117
+ const extensionHostService = accessor . get ( IExtensionService ) ;
110
118
111
119
let editor : INotebookEditor | undefined ;
112
120
if ( context !== undefined && 'notebookEditorId' in context ) {
@@ -229,11 +237,25 @@ registerAction2(class extends Action2 {
229
237
} ) ;
230
238
}
231
239
240
+ let suggestedExtension : INotebookExtensionRecommendation | undefined ;
232
241
if ( ! all . length && ! sourceActions . length ) {
242
+ const activeNotebookModel = getNotebookEditorFromEditorPane ( editorService . activeEditorPane ) ?. textModel ;
243
+ if ( activeNotebookModel ) {
244
+ const language = this . getSuggestedLanguage ( activeNotebookModel ) ;
245
+ suggestedExtension = language ? this . getSuggestedKernelFromLanguage ( activeNotebookModel . viewType , language ) : undefined ;
246
+ }
247
+ if ( suggestedExtension ) {
248
+ // We have a suggested kernel, show an option to install it
249
+ quickPickItems . push ( {
250
+ id : 'installSuggested' ,
251
+ description : suggestedExtension . displayName ?? suggestedExtension . extensionId ,
252
+ label : nls . localize ( 'installSuggestedKernel' , '$({0}) Install suggested extensions' , Codicon . lightbulb . id ) ,
253
+ } ) ;
254
+ }
233
255
// there is no kernel, show the install from marketplace
234
256
quickPickItems . push ( {
235
257
id : 'install' ,
236
- label : nls . localize ( 'installKernels ' , "Install kernels from the marketplace " ) ,
258
+ label : nls . localize ( 'searchForKernels ' , "Browse marketplace for kernel extensions " ) ,
237
259
} ) ;
238
260
}
239
261
@@ -258,7 +280,22 @@ registerAction2(class extends Action2 {
258
280
// actions
259
281
260
282
if ( pick . id === 'install' ) {
261
- await this . _showKernelExtension ( paneCompositeService , notebook . viewType ) ;
283
+ await this . _showKernelExtension (
284
+ paneCompositeService ,
285
+ extensionWorkbenchService ,
286
+ extensionHostService ,
287
+ notebook . viewType
288
+ ) ;
289
+ // suggestedExtension must be defined for this option to be shown, but still check to make TS happy
290
+ } else if ( pick . id === 'installSuggested' && suggestedExtension ) {
291
+ await this . _showKernelExtension (
292
+ paneCompositeService ,
293
+ extensionWorkbenchService ,
294
+ extensionHostService ,
295
+ notebook . viewType ,
296
+ suggestedExtension . extensionId ,
297
+ productService . quality !== 'stable'
298
+ ) ;
262
299
} else if ( 'action' in pick ) {
263
300
// selected explicilty, it should trigger the execution?
264
301
pick . action . runAction ( ) ;
@@ -268,17 +305,69 @@ registerAction2(class extends Action2 {
268
305
return false ;
269
306
}
270
307
271
- private async _showKernelExtension ( paneCompositePartService : IPaneCompositePartService , viewType : string ) {
272
- const viewlet = await paneCompositePartService . openPaneComposite ( EXTENSION_VIEWLET_ID , ViewContainerLocation . Sidebar , true ) ;
273
- const view = viewlet ?. getViewPaneContainer ( ) as IExtensionsViewPaneContainer | undefined ;
308
+ /**
309
+ * Examine the most common language in the notebook
310
+ * @param notebookTextModel The notebook text model
311
+ * @returns What the suggested language is for the notebook. Used for kernal installing
312
+ */
313
+ private getSuggestedLanguage ( notebookTextModel : NotebookTextModel ) : string | undefined {
314
+ const metaData = notebookTextModel . metadata ;
315
+ let suggestedKernelLanguage : string | undefined = ( metaData . custom as any ) ?. metadata ?. language_info ?. name ;
316
+ // TODO how do we suggest multi language notebooks?
317
+ if ( ! suggestedKernelLanguage ) {
318
+ const cellLanguages = notebookTextModel . cells . map ( cell => cell . language ) . filter ( language => language !== 'markdown' ) ;
319
+ // Check if cell languages is all the same
320
+ if ( cellLanguages . length > 1 ) {
321
+ const firstLanguage = cellLanguages [ 0 ] ;
322
+ if ( cellLanguages . every ( language => language === firstLanguage ) ) {
323
+ suggestedKernelLanguage = firstLanguage ;
324
+ }
325
+ }
326
+ }
327
+ return suggestedKernelLanguage ;
328
+ }
274
329
275
- const extId = KERNEL_EXTENSIONS . get ( viewType ) ;
330
+ /**
331
+ * Given a language and notebook view type suggest a kernel for installation
332
+ * @param language The language to find a suggested kernel extension for
333
+ * @returns A recommednation object for the recommended extension, else undefined
334
+ */
335
+ private getSuggestedKernelFromLanguage ( viewType : string , language : string ) : INotebookExtensionRecommendation | undefined {
336
+ const recommendation = KERNEL_RECOMMENDATIONS . get ( viewType ) ?. get ( language ) ;
337
+ return recommendation ;
338
+ }
339
+
340
+ private async _showKernelExtension (
341
+ paneCompositePartService : IPaneCompositePartService ,
342
+ extensionWorkbenchService : IExtensionsWorkbenchService ,
343
+ extensionService : IExtensionService ,
344
+ viewType : string ,
345
+ extId ?: string ,
346
+ isInsiders ?: boolean
347
+ ) {
348
+ // If extension id is provided attempt to install the extension as the user has requested the suggested ones be installed
276
349
if ( extId ) {
277
- view ?. search ( `@id:${ extId } ` ) ;
278
- } else {
279
- const pascalCased = viewType . split ( / [ ^ a - z 0 - 9 ] / ig) . map ( uppercaseFirstLetter ) . join ( '' ) ;
280
- view ?. search ( `@tag:notebookKernel${ pascalCased } ` ) ;
350
+ const extension = ( await extensionWorkbenchService . getExtensions ( [ { id : extId } ] , CancellationToken . None ) ) [ 0 ] ;
351
+ const canInstall = await extensionWorkbenchService . canInstall ( extension ) ;
352
+ // If we can install then install it, otherwise we will fall out into searching the viewlet
353
+ if ( canInstall ) {
354
+ await extensionWorkbenchService . install (
355
+ extension ,
356
+ {
357
+ installPreReleaseVersion : isInsiders ?? false ,
358
+ context : { skipWalkthrough : true }
359
+ } ,
360
+ ProgressLocation . Notification
361
+ ) ;
362
+ await extensionService . activateByEvent ( `onNotebook:${ viewType } ` ) ;
363
+ return ;
364
+ }
281
365
}
366
+
367
+ const viewlet = await paneCompositePartService . openPaneComposite ( EXTENSION_VIEWLET_ID , ViewContainerLocation . Sidebar , true ) ;
368
+ const view = viewlet ?. getViewPaneContainer ( ) as IExtensionsViewPaneContainer | undefined ;
369
+ const pascalCased = viewType . split ( / [ ^ a - z 0 - 9 ] / ig) . map ( uppercaseFirstLetter ) . join ( '' ) ;
370
+ view ?. search ( `@tag:notebookKernel${ pascalCased } ` ) ;
282
371
}
283
372
} ) ;
284
373
0 commit comments