@@ -714,8 +714,13 @@ async function webviewPreloads(ctx: PreloadContext) {
714714 }
715715 } ;
716716
717- interface ExtendedOutputItem {
718- readonly _allOutputItems : ReadonlyArray < { readonly mime : string ; getItem ( ) : Promise < rendererApi . OutputItem | undefined > } > ;
717+ interface AdditionalOutputItemInfo {
718+ readonly mime : string ;
719+ getItem ( ) : Promise < rendererApi . OutputItem | undefined > ;
720+ }
721+
722+ interface ExtendedOutputItem extends rendererApi . OutputItem {
723+ readonly _allOutputItems : ReadonlyArray < AdditionalOutputItemInfo > ;
719724 }
720725
721726 let hasWarnedAboutAllOutputItemsProposal = false ;
@@ -726,15 +731,15 @@ async function webviewPreloads(ctx: PreloadContext) {
726731 metadata : unknown ,
727732 valueBytes : Uint8Array ,
728733 allOutputItemData : ReadonlyArray < { readonly mime : string } >
729- ) : rendererApi . OutputItem & ExtendedOutputItem {
734+ ) : ExtendedOutputItem {
730735
731736 function create (
732737 id : string ,
733738 mime : string ,
734739 metadata : unknown ,
735740 valueBytes : Uint8Array ,
736- ) : rendererApi . OutputItem & ExtendedOutputItem {
737- return Object . freeze < rendererApi . OutputItem & ExtendedOutputItem > ( {
741+ ) : ExtendedOutputItem {
742+ return Object . freeze < ExtendedOutputItem > ( {
738743 id,
739744 mime,
740745 metadata,
@@ -1268,6 +1273,8 @@ async function webviewPreloads(ctx: PreloadContext) {
12681273 }
12691274 } ) ;
12701275
1276+ const renderFallbackErrorName = 'vscode.fallbackToNextRenderer' ;
1277+
12711278 class Renderer {
12721279
12731280 private _onMessageEvent = createEmitter ( ) ;
@@ -1305,11 +1312,16 @@ async function webviewPreloads(ctx: PreloadContext) {
13051312 this . postDebugMessage ( 'Rendered output item' , { id : item . id , duration : `${ performance . now ( ) - renderStart } ms` } ) ;
13061313
13071314 } catch ( e ) {
1308- if ( ! signal . aborted ) {
1309- showRenderError ( `Error rendering output item using '${ this . data . id } '` , element , e instanceof Error ? [ e ] : [ ] ) ;
1315+ if ( signal . aborted ) {
1316+ return ;
1317+ }
13101318
1311- this . postDebugMessage ( 'Rendering output item failed' , { id : item . id , error : e + '' } ) ;
1319+ if ( e instanceof Error && e . name === renderFallbackErrorName ) {
1320+ throw e ;
13121321 }
1322+
1323+ showRenderError ( `Error rendering output item using '${ this . data . id } '` , element , e instanceof Error ? [ e ] : [ ] ) ;
1324+ this . postDebugMessage ( 'Rendering output item failed' , { id : item . id , error : e + '' } ) ;
13131325 }
13141326 }
13151327
@@ -1560,7 +1572,63 @@ async function webviewPreloads(ctx: PreloadContext) {
15601572 this . _renderers . get ( rendererId ) ?. disposeOutputItem ( outputId ) ;
15611573 }
15621574
1563- public async render ( info : rendererApi . OutputItem , preferredRendererId : string | undefined , element : HTMLElement , signal : AbortSignal ) : Promise < void > {
1575+ public async render ( item : ExtendedOutputItem , preferredRendererId : string | undefined , element : HTMLElement , signal : AbortSignal ) : Promise < void > {
1576+ const primaryRenderer = this . findRenderer ( preferredRendererId , item ) ;
1577+ if ( ! primaryRenderer ) {
1578+ const errorMessage = ( document . documentElement . style . getPropertyValue ( '--notebook-cell-renderer-not-found-error' ) || '' ) . replace ( '$0' , ( ) => item . mime ) ;
1579+ this . showRenderError ( item , element , errorMessage ) ;
1580+ return ;
1581+ }
1582+
1583+ // Try primary renderer first
1584+ if ( ! ( await this . _doRender ( item , element , primaryRenderer , signal ) ) . continue ) {
1585+ return ;
1586+ }
1587+
1588+ // Primary renderer failed in an expected way. Fallback to render the next mime types
1589+ for ( const additionalItemData of item . _allOutputItems ) {
1590+ if ( additionalItemData . mime === item . mime ) {
1591+ continue ;
1592+ }
1593+
1594+ const additionalItem = await additionalItemData . getItem ( ) ;
1595+ if ( signal . aborted ) {
1596+ return ;
1597+ }
1598+
1599+ if ( additionalItem ) {
1600+ const renderer = this . findRenderer ( undefined , additionalItem ) ;
1601+ if ( renderer ) {
1602+ if ( ! ( await this . _doRender ( additionalItem , element , renderer , signal ) ) . continue ) {
1603+ return ; // We rendered successfully
1604+ }
1605+ }
1606+ }
1607+ }
1608+
1609+ // All renderers have failed and there is nothing left to fallback to
1610+ const errorMessage = ( document . documentElement . style . getPropertyValue ( '--notebook-cell-renderer-fallbacks-exhausted' ) || '' ) . replace ( '$0' , ( ) => item . mime ) ;
1611+ this . showRenderError ( item , element , errorMessage ) ;
1612+ }
1613+
1614+ private async _doRender ( item : rendererApi . OutputItem , element : HTMLElement , renderer : Renderer , signal : AbortSignal ) : Promise < { continue : boolean } > {
1615+ try {
1616+ await renderer . renderOutputItem ( item , element , signal ) ;
1617+ return { continue : false } ; // We rendered successfully
1618+ } catch ( e ) {
1619+ if ( signal . aborted ) {
1620+ return { continue : false } ;
1621+ }
1622+
1623+ if ( e instanceof Error && e . name === renderFallbackErrorName ) {
1624+ return { continue : true } ;
1625+ } else {
1626+ throw e ; // Bail and let callers handle unknown errors
1627+ }
1628+ }
1629+ }
1630+
1631+ private findRenderer ( preferredRendererId : string | undefined , info : rendererApi . OutputItem ) {
15641632 let renderer : Renderer | undefined ;
15651633
15661634 if ( typeof preferredRendererId === 'string' ) {
@@ -1578,26 +1646,24 @@ async function webviewPreloads(ctx: PreloadContext) {
15781646 renderer = renderers [ 0 ] ;
15791647 }
15801648 }
1649+ return renderer ;
1650+ }
15811651
1582- if ( renderer ) {
1583- await renderer . renderOutputItem ( info , element , signal ) ;
1584- } else {
1585- const errorContainer = document . createElement ( 'div' ) ;
1652+ private showRenderError ( info : rendererApi . OutputItem , element : HTMLElement , errorMessage : string ) {
1653+ const errorContainer = document . createElement ( 'div' ) ;
15861654
1587- const error = document . createElement ( 'div' ) ;
1588- error . className = 'no-renderer-error' ;
1589- const errorText = ( document . documentElement . style . getPropertyValue ( '--notebook-cell-renderer-not-found-error' ) || '' ) . replace ( '$0' , ( ) => info . mime ) ;
1590- error . innerText = errorText ;
1655+ const error = document . createElement ( 'div' ) ;
1656+ error . className = 'no-renderer-error' ;
1657+ error . innerText = errorMessage ;
15911658
1592- const cellText = document . createElement ( 'div' ) ;
1593- cellText . innerText = info . text ( ) ;
1659+ const cellText = document . createElement ( 'div' ) ;
1660+ cellText . innerText = info . text ( ) ;
15941661
1595- errorContainer . appendChild ( error ) ;
1596- errorContainer . appendChild ( cellText ) ;
1662+ errorContainer . appendChild ( error ) ;
1663+ errorContainer . appendChild ( cellText ) ;
15971664
1598- element . innerText = '' ;
1599- element . appendChild ( errorContainer ) ;
1600- }
1665+ element . innerText = '' ;
1666+ element . appendChild ( errorContainer ) ;
16011667 }
16021668 } ( ) ;
16031669
@@ -1819,7 +1885,7 @@ async function webviewPreloads(ctx: PreloadContext) {
18191885 public readonly id : string ;
18201886 public readonly element : HTMLElement ;
18211887
1822- private readonly outputItem : rendererApi . OutputItem ;
1888+ private readonly outputItem : ExtendedOutputItem ;
18231889
18241890 /// Internal field that holds text content
18251891 private _content : { readonly value : string ; readonly version : number ; readonly metadata : NotebookCellMetadata } ;
@@ -1840,7 +1906,7 @@ async function webviewPreloads(ctx: PreloadContext) {
18401906 } ) ;
18411907
18421908 let cachedData : { readonly version : number ; readonly value : Uint8Array } | undefined ;
1843- this . outputItem = Object . freeze < rendererApi . OutputItem > ( {
1909+ this . outputItem = Object . freeze < ExtendedOutputItem > ( {
18441910 id,
18451911 mime,
18461912
@@ -1868,7 +1934,12 @@ async function webviewPreloads(ctx: PreloadContext) {
18681934
18691935 blob ( ) : Blob {
18701936 return new Blob ( [ this . data ( ) ] , { type : this . mime } ) ;
1871- }
1937+ } ,
1938+
1939+ _allOutputItems : [ {
1940+ mime,
1941+ getItem : async ( ) => this . outputItem ,
1942+ } ]
18721943 } ) ;
18731944
18741945 const root = document . getElementById ( 'container' ) ! ;
0 commit comments