1212import com .intellij .xdebugger .XDebugSession ;
1313import com .intellij .xdebugger .XDebuggerManager ;
1414import org .jetbrains .annotations .NotNull ;
15+ import org .jetbrains .annotations .Nullable ;
16+ import org .jetbrains .annotations .VisibleForTesting ;
1517
1618import java .lang .reflect .InvocationTargetException ;
1719import java .lang .reflect .Method ;
2123// See https://github.com/flutter/flutter-intellij/issues/8879.
2224public class FlutterDebugSessionUtils {
2325
24- private static final Method newSessionBuilderMethod ;
25- private static final Method environmentMethod ;
26- private static final Method startSessionMethod ;
26+ private static final @ Nullable BuilderHooks builderHooks = findBuilderHooks ();
2727
28- static {
29- Method nsb = null ;
30- Method env = null ;
31- Method ss = null ;
32- try {
33- nsb = XDebuggerManager .class .getMethod ("newSessionBuilder" , XDebugProcessStarter .class );
34- Class <?> builderClass = nsb .getReturnType ();
35- env = builderClass .getMethod ("environment" , ExecutionEnvironment .class );
36- ss = builderClass .getMethod ("startSession" );
37- } catch (NoSuchMethodException e ) {
38- // Fallback for older platforms
39- }
40- newSessionBuilderMethod = nsb ;
41- environmentMethod = env ;
42- startSessionMethod = ss ;
28+ public static @ NotNull RunContentDescriptor startSessionAndGetDescriptor (
29+ @ NotNull XDebuggerManager manager ,
30+ @ NotNull ExecutionEnvironment env ,
31+ @ NotNull XDebugProcessStarter starter ,
32+ boolean muteBreakpoints ) throws ExecutionException {
33+ return startSessionAndGetDescriptor (manager , env , starter , env .getRunProfile ().getName (), muteBreakpoints );
4334 }
4435
4536 public static @ NotNull RunContentDescriptor startSessionAndGetDescriptor (
4637 @ NotNull XDebuggerManager manager ,
4738 @ NotNull ExecutionEnvironment env ,
4839 @ NotNull XDebugProcessStarter starter ,
40+ @ NotNull String sessionName ,
4941 boolean muteBreakpoints ) throws ExecutionException {
50- try {
51- if (newSessionBuilderMethod == null ) {
52- throw new NoSuchMethodException ("newSessionBuilder is not available" );
53- }
54- Object builder = newSessionBuilderMethod .invoke (manager , starter );
55- builder = environmentMethod .invoke (builder , env );
56- Object sessionResult = startSessionMethod .invoke (builder );
57-
58- if (muteBreakpoints ) {
59- Method getSessionMethod = sessionResult .getClass ().getMethod ("getSession" );
60- XDebugSession session = (XDebugSession ) getSessionMethod .invoke (sessionResult );
61- session .setBreakpointMuted (true );
62- }
42+ if (builderHooks == null ) {
43+ return startLegacySessionAndGetDescriptor (manager , env , starter , muteBreakpoints );
44+ }
6345
64- Method getDescriptorMethod = sessionResult . getClass (). getMethod ( "getRunContentDescriptor" );
65- return ( RunContentDescriptor ) getDescriptorMethod . invoke ( sessionResult );
46+ return startSessionAndGetDescriptor ( builderHooks , manager , env , starter , sessionName , muteBreakpoints );
47+ }
6648
67- } catch (NoSuchMethodException e ) {
68- // Fallback to old API for 2025.1 and older
69- XDebugSession session = manager .startSession (env , starter );
70- if (muteBreakpoints ) {
71- session .setBreakpointMuted (true );
72- }
73- return session .getRunContentDescriptor ();
49+ @ VisibleForTesting
50+ static @ NotNull RunContentDescriptor startSessionAndGetDescriptor (
51+ @ NotNull BuilderHooks hooks ,
52+ @ NotNull Object manager ,
53+ @ NotNull ExecutionEnvironment env ,
54+ @ NotNull XDebugProcessStarter starter ,
55+ @ NotNull String sessionName ,
56+ boolean muteBreakpoints ) throws ExecutionException {
57+ try {
58+ final Object sessionResult = startBuilderSession (hooks , manager , env , starter , sessionName );
59+ muteBreakpointsIfNeeded (sessionResult , muteBreakpoints );
60+ return getDescriptor (sessionResult );
7461 } catch (InvocationTargetException e ) {
7562 Throwable cause = e .getCause ();
7663 if (cause instanceof ExecutionException ) {
@@ -81,4 +68,131 @@ public class FlutterDebugSessionUtils {
8168 throw new ExecutionException ("Failed with unexpected reflection error" , e );
8269 }
8370 }
71+
72+ private static @ Nullable BuilderHooks findBuilderHooks () {
73+ Method sn = null ;
74+ Method ctr = null ;
75+ Method st = null ;
76+ try {
77+ final Method nsb = XDebuggerManager .class .getMethod ("newSessionBuilder" , XDebugProcessStarter .class );
78+ final Class <?> builderClass = nsb .getReturnType ();
79+ final Method env = builderClass .getMethod ("environment" , ExecutionEnvironment .class );
80+ final Method ss = builderClass .getMethod ("startSession" );
81+ try {
82+ sn = builderClass .getMethod ("sessionName" , String .class );
83+ ctr = builderClass .getMethod ("contentToReuse" , RunContentDescriptor .class );
84+ st = builderClass .getMethod ("showTab" , boolean .class );
85+ } catch (NoSuchMethodException e ) {
86+ // Some newer SDKs may expose the builder without all of the tab configuration hooks.
87+ }
88+ return new BuilderHooks (nsb , env , sn , ctr , st , ss );
89+ } catch (NoSuchMethodException e ) {
90+ // Fallback for older platforms
91+ return null ;
92+ }
93+ }
94+
95+ private static @ NotNull RunContentDescriptor startLegacySessionAndGetDescriptor (
96+ @ NotNull XDebuggerManager manager ,
97+ @ NotNull ExecutionEnvironment env ,
98+ @ NotNull XDebugProcessStarter starter ,
99+ boolean muteBreakpoints ) throws ExecutionException {
100+ final XDebugSession session = manager .startSession (env , starter );
101+ if (muteBreakpoints ) {
102+ session .setBreakpointMuted (true );
103+ }
104+ return session .getRunContentDescriptor ();
105+ }
106+
107+ private static @ NotNull Object startBuilderSession (
108+ @ NotNull BuilderHooks hooks ,
109+ @ NotNull Object manager ,
110+ @ NotNull ExecutionEnvironment env ,
111+ @ NotNull XDebugProcessStarter starter ,
112+ @ NotNull String sessionName ) throws Exception {
113+ Object builder = hooks .newSessionBuilderMethod .invoke (manager , starter );
114+ builder = hooks .environmentMethod .invoke (builder , env );
115+ builder = configureNamedTab (hooks , builder , env , sessionName );
116+ return hooks .startSessionMethod .invoke (builder );
117+ }
118+
119+ private static @ NotNull Object configureNamedTab (
120+ @ NotNull BuilderHooks hooks ,
121+ @ NotNull Object builder ,
122+ @ NotNull ExecutionEnvironment env ,
123+ @ NotNull String sessionName ) throws Exception {
124+ // Split debugger builds derive the visible tab title from the builder configuration rather than later descriptor mutation.
125+ if (hooks .contentToReuseMethod != null ) {
126+ builder = hooks .contentToReuseMethod .invoke (builder , env .getContentToReuse ());
127+ }
128+ if (hooks .sessionNameMethod != null ) {
129+ builder = hooks .sessionNameMethod .invoke (builder , sessionName );
130+ }
131+ if (hooks .showTabMethod != null ) {
132+ builder = hooks .showTabMethod .invoke (builder , true );
133+ }
134+ return builder ;
135+ }
136+
137+ private static void muteBreakpointsIfNeeded (@ NotNull Object sessionResult , boolean muteBreakpoints ) throws Exception {
138+ if (!muteBreakpoints ) {
139+ return ;
140+ }
141+ final Method getSessionMethod = sessionResult .getClass ().getMethod ("getSession" );
142+ final XDebugSession session = (XDebugSession ) getSessionMethod .invoke (sessionResult );
143+ session .setBreakpointMuted (true );
144+ }
145+
146+ private static @ NotNull RunContentDescriptor getDescriptor (@ NotNull Object sessionResult ) throws Exception {
147+ final Method getDescriptorMethod = sessionResult .getClass ().getMethod ("getRunContentDescriptor" );
148+ return (RunContentDescriptor ) getDescriptorMethod .invoke (sessionResult );
149+ }
150+
151+ /**
152+ * Returns null when the current SDK exposes the reflective builder hooks needed to label split debug tabs.
153+ */
154+ @ VisibleForTesting
155+ static @ Nullable String getNamedTabSupportError () {
156+ if (builderHooks == null ) {
157+ return null ;
158+ }
159+ if (builderHooks .environmentMethod == null ) {
160+ return "environment not found on XDebugSessionBuilder" ;
161+ }
162+ if (builderHooks .startSessionMethod == null ) {
163+ return "startSession not found on XDebugSessionBuilder" ;
164+ }
165+ if (builderHooks .sessionNameMethod == null ) {
166+ return "sessionName not found on XDebugSessionBuilder" ;
167+ }
168+ if (builderHooks .showTabMethod == null ) {
169+ return "showTab not found on XDebugSessionBuilder" ;
170+ }
171+ return null ;
172+ }
173+
174+ @ VisibleForTesting
175+ static final class BuilderHooks {
176+ final Method newSessionBuilderMethod ;
177+ final Method environmentMethod ;
178+ final @ Nullable Method sessionNameMethod ;
179+ final @ Nullable Method contentToReuseMethod ;
180+ final @ Nullable Method showTabMethod ;
181+ final Method startSessionMethod ;
182+
183+ BuilderHooks (
184+ @ NotNull Method newSessionBuilderMethod ,
185+ @ NotNull Method environmentMethod ,
186+ @ Nullable Method sessionNameMethod ,
187+ @ Nullable Method contentToReuseMethod ,
188+ @ Nullable Method showTabMethod ,
189+ @ NotNull Method startSessionMethod ) {
190+ this .newSessionBuilderMethod = newSessionBuilderMethod ;
191+ this .environmentMethod = environmentMethod ;
192+ this .sessionNameMethod = sessionNameMethod ;
193+ this .contentToReuseMethod = contentToReuseMethod ;
194+ this .showTabMethod = showTabMethod ;
195+ this .startSessionMethod = startSessionMethod ;
196+ }
197+ }
84198}
0 commit comments