1
+ /*
2
+ * Copyright 2025 The Chromium Authors. All rights reserved.
3
+ * Use of this source code is governed by a BSD-style license that can be
4
+ * found in the LICENSE file.
5
+ */
6
+ package io .flutter .devtools ;
7
+
8
+ import com .intellij .openapi .project .Project ;
9
+ import com .intellij .openapi .wm .ToolWindow ;
10
+ import com .intellij .openapi .wm .ToolWindowFactory ;
11
+ import io .flutter .FlutterUtils ;
12
+ import io .flutter .actions .RefreshToolWindowAction ;
13
+ import io .flutter .run .daemon .DevToolsInstance ;
14
+ import io .flutter .run .daemon .DevToolsService ;
15
+ import io .flutter .sdk .FlutterSdk ;
16
+ import io .flutter .sdk .FlutterSdkVersion ;
17
+ import io .flutter .utils .AsyncUtils ;
18
+ import io .flutter .utils .OpenApiUtils ;
19
+ import io .flutter .view .EmbeddedBrowser ;
20
+ import io .flutter .view .ViewUtils ;
21
+ import kotlin .coroutines .Continuation ;
22
+ import org .jetbrains .annotations .NotNull ;
23
+
24
+ import java .util .List ;
25
+ import java .util .Optional ;
26
+
27
+ public abstract class AbstractDevToolsViewFactory implements ToolWindowFactory {
28
+
29
+ @ NotNull
30
+ protected final ViewUtils viewUtils = new ViewUtils ();
31
+
32
+ public abstract boolean versionSupportsThisTool (@ NotNull FlutterSdkVersion flutterSdkVersion );
33
+
34
+ @ NotNull
35
+ public abstract String getToolWindowId ();
36
+
37
+ @ NotNull
38
+ public abstract String getToolWindowTitle ();
39
+
40
+ @ NotNull
41
+ public abstract DevToolsUrl getDevToolsUrl (@ NotNull Project project ,
42
+ @ NotNull FlutterSdkVersion flutterSdkVersion ,
43
+ @ NotNull DevToolsInstance instance );
44
+
45
+ protected void doAfterBrowserOpened (@ NotNull Project project , @ NotNull EmbeddedBrowser browser ) {}
46
+
47
+ @ Override
48
+ public Object isApplicableAsync (@ NotNull Project project , @ NotNull Continuation <? super Boolean > $completion ) {
49
+ // Due to https://github.com/flutter/flutter/issues/142521, this always returns true when the
50
+ // Flutter IJ plugin is installed.
51
+
52
+ // The logic which asserts that the Flutter SDK is up to date enough for this particular feature
53
+ // is captured in the implementation of createToolWindowContent() below.
54
+
55
+ return true ;
56
+ }
57
+
58
+ @ Override
59
+ public void createToolWindowContent (@ NotNull Project project , @ NotNull ToolWindow toolWindow ) {
60
+ final FlutterSdk flutterSdk = FlutterSdk .getFlutterSdk (project );
61
+ final FlutterSdkVersion flutterSdkVersion = flutterSdk == null ? null : flutterSdk .getVersion ();
62
+
63
+ // There are four potential states for the Flutter SDK:
64
+ // 1. The Flutter SDK is null (flutterSdk == null) (an SDK path is invalid or not configured)
65
+ // 2. The Flutter SDK exists, but version file information is unavailable (flutterSdkVersion == null),
66
+ // see https://github.com/flutter/flutter/issues/142521.
67
+ // 3. The Flutter SDK exists, and the version file information is available, but this tool is not
68
+ // available on the version of the SDK that the user has.
69
+ // 4. The Flutter SDK exists with valid version information.
70
+
71
+ // First case:
72
+ if (flutterSdk == null ) {
73
+ viewUtils .presentLabels (toolWindow , List .of ("Set the Flutter SDK path in" ,
74
+ "Settings > Languages & Frameworks > Flutter," ,
75
+ "and then restart the IDE." ));
76
+ return ;
77
+ }
78
+
79
+ // Second case:
80
+ if (flutterSdk .getVersion ().fullVersion ().equals (FlutterSdkVersion .UNKNOWN_VERSION )) {
81
+ viewUtils .presentLabels (toolWindow , List .of ("A Flutter SDK was found at the location" ,
82
+ "specified in the settings, however the directory" ,
83
+ "is in an incomplete state. To fix, shut down the IDE," ,
84
+ "run `flutter doctor` or `flutter --version`" ,
85
+ "and then restart the IDE." ));
86
+ return ;
87
+ }
88
+
89
+ // Third case:
90
+ if (!versionSupportsThisTool (flutterSdkVersion )) {
91
+ final String versionText = flutterSdkVersion .fullVersion ();
92
+ viewUtils .presentLabels (toolWindow , List .of ("The version of your Flutter SDK," ,
93
+ versionText + "," ,
94
+ "is not recent enough to use this tool." ,
95
+ "Update the Flutter SDK, `flutter upgrade`," ,
96
+ "and then restart the IDE." ));
97
+ return ;
98
+ }
99
+
100
+ // Final case:
101
+ AsyncUtils .whenCompleteUiThread (
102
+ DevToolsService .getInstance (project ).getDevToolsInstance (),
103
+ (instance , error ) -> {
104
+ // Skip displaying if the project has been closed.
105
+ if (!project .isOpen ()) {
106
+ return ;
107
+ }
108
+
109
+ if (error != null ) {
110
+ return ;
111
+ }
112
+
113
+ if (instance == null ) {
114
+ return ;
115
+ }
116
+
117
+ final DevToolsUrl devToolsUrl = getDevToolsUrl (project , flutterSdkVersion , instance );
118
+
119
+ OpenApiUtils .safeInvokeLater (() -> {
120
+ Optional .ofNullable (
121
+ FlutterUtils .embeddedBrowser (project ))
122
+ .ifPresent (embeddedBrowser ->
123
+ {
124
+ embeddedBrowser .openPanel (toolWindow , getToolWindowTitle (), devToolsUrl , System .out ::println );
125
+ doAfterBrowserOpened (project , embeddedBrowser );
126
+ });
127
+ });
128
+ }
129
+ );
130
+
131
+ // TODO(helin24): It may be better to add this to the gear actions or to attach as a mouse event on individual tabs within a tool
132
+ // window, but I wasn't able to get either working immediately.
133
+ toolWindow .setTitleActions (List .of (new RefreshToolWindowAction (getToolWindowId ())));
134
+ }
135
+ }
0 commit comments