@@ -5,7 +5,7 @@ import { FlutterCapabilities } from "../../shared/capabilities/flutter";
55import { CommandSource , debugLaunchProgressId , debugTerminatingProgressId , devToolsPages , doNotAskAgainAction , doNotShowAgainAction , moreInfoAction , widgetInspectorPage } from "../../shared/constants" ;
66import { isInDartDebugSessionContext , isInFlutterDebugModeDebugSessionContext , isInFlutterProfileModeDebugSessionContext , isInFlutterReleaseModeDebugSessionContext } from "../../shared/constants.contexts" ;
77import { DebugOption , DebuggerType , LogCategory , LogSeverity , VmService , VmServiceExtension , debugOptionNames } from "../../shared/enums" ;
8- import { DartWorkspaceContext , IAmDisposable , LogMessage , Logger , WidgetErrorInspectData } from "../../shared/interfaces" ;
8+ import { DartWorkspaceContext , DevToolsDeepLinkData , IAmDisposable , LegacyWidgetErrorInspectData , LogMessage , Logger } from "../../shared/interfaces" ;
99import { PromiseCompleter , disposeAll } from "../../shared/utils" ;
1010import { fsPath , isFlutterProjectFolder , isWithinPath } from "../../shared/utils/fs" ;
1111import { ANALYSIS_FILTERS } from "../../shared/vscode/constants" ;
@@ -964,11 +964,11 @@ export class DebugCommands implements IAmDisposable {
964964 }
965965 progress . complete ( ) ;
966966 }
967- } else if ( event === "dart.flutter.widgetErrorInspectData" ) {
967+ } else if ( event === "dart.flutter.widgetErrorInspectData" ) { // Legacy DAP deep links
968968 if ( this . suppressFlutterWidgetErrors || ! config . showInspectorNotificationsForWidgetErrors )
969969 return ;
970970
971- const data = e . body as WidgetErrorInspectData ;
971+ const data = e . body as LegacyWidgetErrorInspectData ;
972972 if ( data . devToolsUrl !== ( await this . devTools . devtoolsUrl ) )
973973 return ;
974974
@@ -993,6 +993,41 @@ export class DebugCommands implements IAmDisposable {
993993 }
994994 clearTimeout ( timer ) ;
995995 this . suppressFlutterWidgetErrors = false ;
996+ } else if ( event === "dart.flutter.devToolsDeepLink" ) { // New DAP deep links
997+ if ( this . suppressFlutterWidgetErrors || ! config . showInspectorNotificationsForWidgetErrors )
998+ return ;
999+
1000+ // "event":"dart.flutter.devToolsDeepLink",
1001+ // "body":{
1002+ // "summary":"A RenderFlex overflowed by 266 pixels on the right.",
1003+ // "deepLinkUrl":"http://example.org/#/inspector?uri&inspectorRef=inspector-0"
1004+ // }
1005+ const data = e . body as DevToolsDeepLinkData ;
1006+
1007+ // To avoid spam, when we show this dialog we will set a flag that prevents any more
1008+ // of these types of dialogs until it is dismissed or 5 seconds have passed.
1009+ this . suppressFlutterWidgetErrors = true ;
1010+ const timer = setTimeout ( ( ) => this . suppressFlutterWidgetErrors = false , 5000 ) ;
1011+
1012+ const inspectAction = `Inspect Widget` ;
1013+ const choice = await vs . window . showWarningMessage ( data . summary , inspectAction , doNotAskAgainAction ) ;
1014+ if ( choice === inspectAction && session . vmServiceUri ) {
1015+ const link = this . parseDeepLinkUri ( data . deepLinkUrl ) ;
1016+ if ( link !== null ) {
1017+ void this . devTools . spawn (
1018+ session as DartDebugSessionInformation & { vmServiceUri : string } ,
1019+ {
1020+ inspectorRef : link ?. inspectorRef ,
1021+ pageId : link ?. pageId ,
1022+ } ,
1023+ true ,
1024+ ) ;
1025+ }
1026+ } else if ( choice === doNotAskAgainAction ) {
1027+ void config . setShowInspectorNotificationsForWidgetErrors ( false ) ;
1028+ }
1029+ clearTimeout ( timer ) ;
1030+ this . suppressFlutterWidgetErrors = false ;
9961031 } else if ( event === "flutter.appStarted" ) {
9971032 session . hasStarted = true ;
9981033 debugSessionChangedEmitter . fire ( session ) ;
@@ -1010,6 +1045,22 @@ export class DebugCommands implements IAmDisposable {
10101045 }
10111046 }
10121047
1048+ private parseDeepLinkUri ( deepLinkUrl : string ) : { pageId : string , inspectorRef : string | undefined } | undefined {
1049+ try {
1050+ // http://example.org/#/inspector?uri&inspectorRef=inspector-0
1051+ const url = new URL ( deepLinkUrl . replace ( "#/" , "" ) ) ;
1052+ const pageId = url . pathname . split ( "/" ) [ 1 ] ;
1053+ const inspectorRef = url . searchParams . get ( "inspectorRef" ) ?? undefined ;
1054+ if ( pageId !== "" ) {
1055+ return { pageId, inspectorRef } ;
1056+ }
1057+ return undefined ;
1058+ } catch {
1059+ this . logger . error ( `Failed to parse DevTools deep link URL: ${ deepLinkUrl } ` ) ;
1060+ return undefined ;
1061+ }
1062+ }
1063+
10131064 private toggleDebugOptions ( ) {
10141065 // -1 is because we skip the last combination when toggling since it seems uncommon.
10151066 this . currentDebugOption = ( this . currentDebugOption + 1 ) % ( debugOptionNames . length - 1 ) ;
0 commit comments