Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .gemini/styleguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ enforce standard modern Java/Kotlin coding conventions, but strictly police the
- `[NIT]`: Idiomatic improvements or minor naming suggestions.
- **Focus:** Prioritize logic, performance on the UI thread, and architectural consistency.
- **No Empty Praise:** Do not leave "Looks good" or "Nice change" comments. If there are no issues, leave no comments.
- **Copyright Headers:** Ensure all new files have a proper copyright header (e.g., `Copyright 2026 The Chromium Authors`). Flag any missing
headers as `[MUST-FIX]`.

## 2. IntelliJ Platform Best Practices

Expand All @@ -30,8 +32,10 @@ enforce standard modern Java/Kotlin coding conventions, but strictly police the
as recommended by the modern SDK.
- **Backward Compatibility:** Avoid using `@ApiStatus.Internal` or `@ApiStatus.ScheduledForRemoval` APIs unless strictly necessary.
- **Logging:**
- Reject any use of `System.out.println` or `System.err.println` for logging in `src/` code (integration tests may use them for milestone logging).
- Enforce the use of the IntelliJ SDK's built-in logger (`com.intellij.openapi.diagnostic.Logger`) or our own (`io.flutter.logging.PluginLogger`).
- Reject any use of `System.out.println` or `System.err.println` for logging in `src/` code (integration tests may use them for
milestone logging).
- Enforce the use of the IntelliJ SDK's built-in logger (`com.intellij.openapi.diagnostic.Logger`) or our own (
`io.flutter.logging.PluginLogger`).
- **Actions:**
- Classes extending `AnAction` must be completely stateless. Flag any `AnAction` class that defines mutable instance variables (fields),
as the platform instantiates a single instance of the action for the lifetime of the IDE.
Expand Down
2 changes: 1 addition & 1 deletion src/io/flutter/run/AttachState.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ protected RunContentDescriptor launch(@NotNull ExecutionEnvironment env) throws
// Cache for use in console configuration, and for updating registered extensionRPCs.
FlutterApp.addToEnvironment(env, app);
final ExecutionResult result = setUpConsoleAndActions(app);
return createDebugSession(env, app, result).getRunContentDescriptor();
return createDebugSession(env, app, result);
}
}
84 changes: 84 additions & 0 deletions src/io/flutter/run/FlutterDebugSessionUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2026 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
package io.flutter.run;
Comment thread
helin24 marked this conversation as resolved.

import com.intellij.execution.ExecutionException;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.xdebugger.XDebugProcessStarter;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebuggerManager;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// TODO(helin24): This class is using reflection to find experimental APIs that are only present in platform versions 2025.3+. We should be
// able to use the APIs directly once we are only supporting versions past 2025.3.
Comment thread
helin24 marked this conversation as resolved.
// See https://github.com/flutter/flutter-intellij/issues/8879.
public class FlutterDebugSessionUtils {

private static final Method newSessionBuilderMethod;
private static final Method environmentMethod;
private static final Method startSessionMethod;

static {
Method nsb = null;
Method env = null;
Method ss = null;
try {
nsb = XDebuggerManager.class.getMethod("newSessionBuilder", XDebugProcessStarter.class);
Class<?> builderClass = nsb.getReturnType();
env = builderClass.getMethod("environment", ExecutionEnvironment.class);
ss = builderClass.getMethod("startSession");
} catch (NoSuchMethodException e) {
// Fallback for older platforms
}
newSessionBuilderMethod = nsb;
environmentMethod = env;
startSessionMethod = ss;
}

public static @NotNull RunContentDescriptor startSessionAndGetDescriptor(
@NotNull XDebuggerManager manager,
@NotNull ExecutionEnvironment env,
@NotNull XDebugProcessStarter starter,
boolean muteBreakpoints) throws ExecutionException {
try {
if (newSessionBuilderMethod == null) {
throw new NoSuchMethodException("newSessionBuilder is not available");
}
Object builder = newSessionBuilderMethod.invoke(manager, starter);
builder = environmentMethod.invoke(builder, env);
Object sessionResult = startSessionMethod.invoke(builder);

if (muteBreakpoints) {
Method getSessionMethod = sessionResult.getClass().getMethod("getSession");
XDebugSession session = (XDebugSession) getSessionMethod.invoke(sessionResult);
session.setBreakpointMuted(true);
}

Method getDescriptorMethod = sessionResult.getClass().getMethod("getRunContentDescriptor");
return (RunContentDescriptor) getDescriptorMethod.invoke(sessionResult);

} catch (NoSuchMethodException e) {
// Fallback to old API for 2025.1 and older
XDebugSession session = manager.startSession(env, starter);
if (muteBreakpoints) {
session.setBreakpointMuted(true);
}
return session.getRunContentDescriptor();
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof ExecutionException) {
throw (ExecutionException) cause;
}
throw new ExecutionException("Failed to start debug session via reflection", cause != null ? cause : e);
} catch (Exception e) {
throw new ExecutionException("Failed with unexpected reflection error", e);
}
Comment thread
helin24 marked this conversation as resolved.
}
}
18 changes: 6 additions & 12 deletions src/io/flutter/run/LaunchState.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ protected RunContentDescriptor launch(@NotNull ExecutionEnvironment env) throws
final RunContentDescriptor descriptor;
if (launchMode.supportsDebugConnection()) {
ToolWindowBadgeUpdater.updateBadgedIcon(app, project);
descriptor = createDebugSession(env, app, result).getRunContentDescriptor();
descriptor = createDebugSession(env, app, result);
}
else {
descriptor = new RunContentBuilder(result, env).showRunContent(env.getContentToReuse());
Expand Down Expand Up @@ -224,28 +224,22 @@ protected void showNoDeviceConnectedMessage(Project project) {
}

@NotNull
protected XDebugSession createDebugSession(@NotNull final ExecutionEnvironment env,
@NotNull final FlutterApp app,
@NotNull final ExecutionResult executionResult)
protected RunContentDescriptor createDebugSession(@NotNull final ExecutionEnvironment env,
@NotNull final FlutterApp app,
@NotNull final ExecutionResult executionResult)
throws ExecutionException {

final DartUrlResolver resolver = DartUrlResolver.getInstance(env.getProject(), sourceLocation);
final FlutterPositionMapper mapper = createPositionMapper(env, app, resolver);

final XDebuggerManager manager = XDebuggerManager.getInstance(env.getProject());
final XDebugSession session = manager.startSession(env, new XDebugProcessStarter() {
return FlutterDebugSessionUtils.startSessionAndGetDescriptor(manager, env, new XDebugProcessStarter() {
@Override
@NotNull
public XDebugProcess start(@NotNull final XDebugSession session) {
return new FlutterDebugProcess(app, env, session, executionResult, resolver, mapper);
}
});

if (app.getMode() != RunMode.DEBUG) {
session.setBreakpointMuted(true);
}

return session;
}, app.getMode() != RunMode.DEBUG);
}

@NotNull
Expand Down
7 changes: 3 additions & 4 deletions src/io/flutter/run/bazelTest/BazelTestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import io.flutter.bazel.WorkspaceCache;
import io.flutter.logging.PluginLogger;
import io.flutter.run.FlutterPositionMapper;
import io.flutter.run.FlutterDebugSessionUtils;
import io.flutter.run.common.CommonTestConfigUtils;
import io.flutter.run.test.FlutterTestRunner;
import io.flutter.settings.FlutterSettings;
Expand Down Expand Up @@ -100,15 +101,13 @@ protected RunContentDescriptor runInDebugger(@NotNull BazelTestLaunchState launc

// Create the debug session.
final XDebuggerManager manager = XDebuggerManager.getInstance(env.getProject());
final XDebugSession session = manager.startSession(env, new XDebugProcessStarter() {
return FlutterDebugSessionUtils.startSessionAndGetDescriptor(manager, env, new XDebugProcessStarter() {
@Override
@NotNull
public XDebugProcess start(@NotNull final XDebugSession session) {
return new BazelTestDebugProcess(env, session, executionResult, resolver, connector, mapper);
}
});

return session.getRunContentDescriptor();
}, false);
}

/**
Expand Down
7 changes: 3 additions & 4 deletions src/io/flutter/run/test/FlutterTestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.intellij.xdebugger.XDebuggerManager;
import com.jetbrains.lang.dart.util.DartUrlResolver;
import io.flutter.FlutterUtils;
import io.flutter.run.FlutterDebugSessionUtils;
import io.flutter.ObservatoryConnector;
import io.flutter.logging.PluginLogger;
import io.flutter.run.FlutterPositionMapper;
Expand Down Expand Up @@ -210,15 +211,13 @@ protected RunContentDescriptor runInDebugger(@NotNull TestLaunchState launcher,

// Create the debug session.
final XDebuggerManager manager = XDebuggerManager.getInstance(env.getProject());
final XDebugSession session = manager.startSession(env, new XDebugProcessStarter() {
return FlutterDebugSessionUtils.startSessionAndGetDescriptor(manager, env, new XDebugProcessStarter() {
@Override
@NotNull
public XDebugProcess start(@NotNull final XDebugSession session) {
return new TestDebugProcess(env, session, executionResult, resolver, connector, mapper);
}
});

return session.getRunContentDescriptor();
}, false);
}

/**
Expand Down
Loading