Skip to content

Commit 14a689f

Browse files
committed
feat: Integrate Qute Debugger
Fixes #1465 Signed-off-by: azerr <azerr@redhat.com>
1 parent 1a41388 commit 14a689f

15 files changed

+447
-22
lines changed

src/main/java/com/redhat/devtools/intellij/quarkus/buildtool/BuildToolDelegate.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,8 @@
2626
import org.jetbrains.idea.maven.model.MavenId;
2727

2828
import java.io.*;
29-
import java.util.ArrayList;
30-
import java.util.Arrays;
31-
import java.util.List;
32-
import java.util.Properties;
29+
import java.nio.charset.StandardCharsets;
30+
import java.util.*;
3331
import java.util.jar.JarEntry;
3432
import java.util.jar.JarFile;
3533

@@ -63,18 +61,19 @@ static String getDeploymentJarId(File file) {
6361
try (Reader r = new FileReader(quarkusFile)) {
6462
result = getQuarkusExtension(r);
6563
} catch (IOException e) {
64+
// Do nothing
6665
}
6766
}
6867
} else {
69-
try {
70-
JarFile jarFile = new JarFile(file);
68+
try(JarFile jarFile = new JarFile(file)) {
7169
JarEntry entry = jarFile.getJarEntry(QUARKUS_EXTENSION_PROPERTIES);
7270
if (entry != null) {
73-
try (Reader r = new InputStreamReader(jarFile.getInputStream(entry), "UTF-8")) {
71+
try (Reader r = new InputStreamReader(jarFile.getInputStream(entry), StandardCharsets.UTF_8)) {
7472
result = getQuarkusExtension(r);
7573
}
7674
}
7775
} catch (IOException e) {
76+
// Do nothing
7877
}
7978
}
8079
return result;
@@ -90,7 +89,7 @@ static String getQuarkusExtension(Reader r) throws IOException {
9089
return p.getProperty(QUARKUS_DEPLOYMENT_PROPERTY_NAME);
9190
}
9291

93-
public static BuildToolDelegate getDelegate(Module module) {
92+
static BuildToolDelegate getDelegate(Module module) {
9493
for (BuildToolDelegate toolDelegate : getDelegates()) {
9594
if (toolDelegate.isValid(module)) {
9695
return toolDelegate;
@@ -107,9 +106,9 @@ public static BuildToolDelegate getDelegate(Module module) {
107106
*/
108107
boolean isValid(Module module);
109108

110-
public static final int BINARY = 0;
109+
int BINARY = 0;
111110

112-
public static final int SOURCES = 1;
111+
int SOURCES = 1;
113112

114113
/**
115114
* Return the list of additional deployment JARs for the module. The array should have 2 elements, the first
@@ -163,7 +162,7 @@ default VirtualFile getJarFile(File file) {
163162
return virtualFile != null ? JarFileSystem.getInstance().getJarRootForLocalFile(virtualFile) : null;
164163
}
165164

166-
static final ExtensionPointName<BuildToolDelegate> EP_NAME = ExtensionPointName.create("com.redhat.devtools.intellij.quarkus.toolDelegate");
165+
ExtensionPointName<BuildToolDelegate> EP_NAME = ExtensionPointName.create("com.redhat.devtools.intellij.quarkus.toolDelegate");
167166

168167
@NotNull
169168
static List<VirtualFile>[] initDeploymentFiles() {
@@ -173,9 +172,9 @@ static List<VirtualFile>[] initDeploymentFiles() {
173172
return result;
174173
}
175174

176-
public static BuildToolDelegate[] getDelegates() {
175+
static BuildToolDelegate[] getDelegates() {
177176
BuildToolDelegate[] delegates = EP_NAME.getExtensions();
178-
Arrays.sort(delegates, (a, b) -> a.getOrder() - b.getOrder());
177+
Arrays.sort(delegates, Comparator.comparingInt(BuildToolDelegate::getOrder));
179178
return delegates;
180179
}
181180

@@ -185,13 +184,15 @@ public static BuildToolDelegate[] getDelegates() {
185184
*
186185
* @param module the module.
187186
* @param configuration the quarkus configuration.
188-
* @param debugPort the debug port tu use if Quarkus application must be debugged and null otherwise.
187+
* @param debugPort the debug port to use if Quarkus application must be debugged and null otherwise.
188+
* @param quteDebugPort the debug port to use if Qute templates must be debugged and null otherwise.
189189
* @return the configuration delegate (Gradle,Maven).
190190
*/
191191
@Nullable
192192
RunnerAndConfigurationSettings getConfigurationDelegate(@NotNull Module module,
193193
@NotNull QuarkusRunConfiguration configuration,
194-
@Nullable Integer debugPort);
194+
@Nullable Integer debugPort,
195+
@Nullable Integer quteDebugPort);
195196

196197
/**
197198
* Add project import listener.

src/main/java/com/redhat/devtools/intellij/quarkus/buildtool/gradle/AbstractGradleToolDelegate.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,8 @@ private ProjectImportProvider getGradleProjectImportProvider() {
372372
@Override
373373
public RunnerAndConfigurationSettings getConfigurationDelegate(@NotNull Module module,
374374
@NotNull QuarkusRunConfiguration configuration,
375-
@Nullable Integer debugPort) {
375+
@Nullable Integer debugPort,
376+
@Nullable Integer quteDebugPort) {
376377
RunnerAndConfigurationSettings settings = RunManager.getInstance(module.getProject()).createConfiguration(module.getName() + " Quarkus (Gradle)", GradleExternalTaskConfigurationType.class);
377378
GradleRunConfiguration gradleConfiguration = (GradleRunConfiguration) settings.getConfiguration();
378379
gradleConfiguration.getSettings().getTaskNames().add("quarkusDev");
@@ -381,6 +382,9 @@ public RunnerAndConfigurationSettings getConfigurationDelegate(@NotNull Module m
381382
if (StringUtils.isNotBlank(configuration.getProfile())) {
382383
parameters += " -Dquarkus.profile=" + configuration.getProfile();
383384
}
385+
if (quteDebugPort != null) {
386+
parameters += " -Dqute.debug.port=" + Integer.toString(quteDebugPort);
387+
}
384388
gradleConfiguration.getSettings().setScriptParameters(parameters);
385389
gradleConfiguration.setBeforeRunTasks(configuration.getBeforeRunTasks());
386390
return settings;

src/main/java/com/redhat/devtools/intellij/quarkus/buildtool/maven/MavenToolDelegate.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ private static void addDeploymentArtifact(MavenArtifact resolvedArtifact, Set<Ma
192192
}
193193

194194
@Override
195-
public RunnerAndConfigurationSettings getConfigurationDelegate(Module module, QuarkusRunConfiguration configuration, @Nullable Integer debugPort) {
195+
public RunnerAndConfigurationSettings getConfigurationDelegate(@NotNull Module module, @NotNull QuarkusRunConfiguration configuration,
196+
@Nullable Integer debugPort,
197+
@Nullable Integer quteDebugPort) {
196198
RunnerAndConfigurationSettings settings = RunManager.getInstance(module.getProject()).createConfiguration(module.getName() + " Quarkus (Maven)", MavenRunConfigurationType.class);
197199
MavenRunConfiguration mavenConfiguration = (MavenRunConfiguration) settings.getConfiguration();
198200
mavenConfiguration.getRunnerParameters().setResolveToWorkspace(true);
@@ -206,6 +208,9 @@ public RunnerAndConfigurationSettings getConfigurationDelegate(Module module, Qu
206208
if (debugPort != null) {
207209
mavenConfiguration.getRunnerSettings().getMavenProperties().put("debug", Integer.toString(debugPort));
208210
}
211+
if (quteDebugPort != null) {
212+
mavenConfiguration.getRunnerSettings().getMavenProperties().put("qute.debug.port", Integer.toString(quteDebugPort));
213+
}
209214
mavenConfiguration.setBeforeRunTasks(configuration.getBeforeRunTasks());
210215
return settings;
211216
}

src/main/java/com/redhat/devtools/intellij/quarkus/run/AttachDebuggerProcessListener.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import com.intellij.openapi.project.Project;
3131
import com.intellij.openapi.ui.Messages;
3232
import com.intellij.openapi.util.Key;
33+
import com.redhat.devtools.intellij.qute.run.QuteConfigurationType;
34+
import com.redhat.devtools.intellij.qute.run.QuteRunConfiguration;
3335
import org.jetbrains.annotations.NotNull;
3436
import org.jetbrains.annotations.Nullable;
3537
import org.slf4j.Logger;
@@ -51,12 +53,14 @@ class AttachDebuggerProcessListener implements ProcessListener {
5153
private final static Logger LOGGER = LoggerFactory.getLogger(AttachDebuggerProcessListener.class);
5254

5355
private static final String LISTENING_FOR_TRANSPORT_DT_SOCKET_AT_ADDRESS = "Listening for transport dt_socket at address: ";
54-
5556
private static final String JWDP_HANDSHAKE = "JDWP-Handshake";
5657

58+
private static final String QUTE_LISTENING_ON_PORT = "DebugServerAdapter listening on port ";
59+
5760
private final Project project;
5861
private final ExecutionEnvironment env;
5962
private boolean connected; // to prevent from several messages like 'Listening for transport dt_socket at address:'
63+
private boolean quteConnected; // to prevent from several messages like 'Listening for transport dt_socket at address:'
6064

6165
AttachDebuggerProcessListener(@NotNull Project project,
6266
@NotNull ExecutionEnvironment env) {
@@ -71,7 +75,7 @@ public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType
7175
connected = true;
7276
Integer debugPort = getDebugPort(message);
7377
if (debugPort == null) {
74-
LOGGER.error("Cannot extract port from the given message: " + message);
78+
LOGGER.error("Cannot extract port from the given message: {}", message);
7579
return;
7680
}
7781
ProgressManager.getInstance().run(new Task.Backgroundable(project, QUARKUS_CONFIGURATION, false) {
@@ -81,13 +85,37 @@ public void run(@NotNull ProgressIndicator indicator) {
8185
createRemoteConfiguration(indicator, debugPort, name);
8286
}
8387
});
88+
} else if (!quteConnected && message.startsWith(QUTE_LISTENING_ON_PORT)) {
89+
quteConnected = true;
90+
Integer quteDebugPort = getQuteDebugPort(message);
91+
if (quteDebugPort == null) {
92+
LOGGER.error("Cannot extract Qute debug port from the given message: {}", message);
93+
return;
94+
}
95+
ProgressManager.getInstance().run(new Task.Backgroundable(project, QUARKUS_CONFIGURATION, false) {
96+
@Override
97+
public void run(@NotNull ProgressIndicator indicator) {
98+
String name = env.getRunProfile().getName();
99+
createQuteConfiguration(indicator, quteDebugPort, name);
100+
}
101+
});
84102
}
85103
}
86104

87105
@Nullable
88106
private static Integer getDebugPort(String message) {
89107
try {
90-
String port = message.substring(LISTENING_FOR_TRANSPORT_DT_SOCKET_AT_ADDRESS.length(), message.length()).trim();
108+
String port = message.substring(LISTENING_FOR_TRANSPORT_DT_SOCKET_AT_ADDRESS.length()).trim();
109+
return Integer.valueOf(port);
110+
} catch (Exception e) {
111+
return null;
112+
}
113+
}
114+
115+
@Nullable
116+
private static Integer getQuteDebugPort(String message) {
117+
try {
118+
String port = message.substring(QUTE_LISTENING_ON_PORT.length()).trim();
91119
return Integer.valueOf(port);
92120
} catch (Exception e) {
93121
return null;
@@ -99,7 +127,7 @@ public void processTerminated(@NotNull ProcessEvent event) {
99127
event.getProcessHandler().removeProcessListener(this);
100128
}
101129

102-
private void createRemoteConfiguration(ProgressIndicator indicator, int port, String name) {
130+
private void createRemoteConfiguration(@NotNull ProgressIndicator indicator, int port, String name) {
103131
indicator.setText("Connecting Java debugger to port " + port);
104132
try {
105133
waitForPortAvailable(port, indicator);
@@ -114,6 +142,21 @@ private void createRemoteConfiguration(ProgressIndicator indicator, int port, St
114142
}
115143
}
116144

145+
private void createQuteConfiguration(@NotNull ProgressIndicator indicator, int port, String name) {
146+
indicator.setText("Connecting Qute debugger to port " + port);
147+
try {
148+
waitForPortAvailable(port, indicator);
149+
RunnerAndConfigurationSettings settings = RunManager.getInstance(project).createConfiguration(name + " (Qute)", QuteConfigurationType.class);
150+
QuteRunConfiguration quteConfiguration = (QuteRunConfiguration) settings.getConfiguration();
151+
quteConfiguration.setAttachPort(Integer.toString(port));
152+
long groupId = ExecutionEnvironment.getNextUnusedExecutionId();
153+
ExecutionUtil.runConfiguration(settings, DefaultDebugExecutor.getDebugExecutorInstance(), DefaultExecutionTarget.INSTANCE, groupId);
154+
} catch (IOException e) {
155+
ApplicationManager.getApplication()
156+
.invokeLater(() -> Messages.showErrorDialog("Can' t connector to port " + port, "Quarkus"));
157+
}
158+
}
159+
117160
private void waitForPortAvailable(int port, ProgressIndicator monitor) throws IOException {
118161
long start = System.currentTimeMillis();
119162
while (System.currentTimeMillis() - start < 120_000 && !monitor.isCanceled()) {

src/main/java/com/redhat/devtools/intellij/quarkus/run/QuarkusRunConfiguration.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
2323
import com.intellij.execution.runners.ExecutionUtil;
2424
import com.intellij.execution.runners.RunConfigurationWithSuppressedDefaultRunAction;
25+
import com.intellij.java.library.JavaLibraryUtil;
2526
import com.intellij.openapi.actionSystem.DataContext;
2627
import com.intellij.openapi.application.ApplicationManager;
2728
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunnableState;
@@ -34,11 +35,15 @@
3435
import com.intellij.openapi.ui.Messages;
3536
import com.intellij.openapi.util.Key;
3637
import com.intellij.openapi.wm.ToolWindowId;
38+
import com.intellij.psi.util.PsiClassUtil;
3739
import com.intellij.util.net.NetUtils;
40+
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.PsiUtils;
3841
import com.redhat.devtools.intellij.quarkus.QuarkusModuleUtil;
3942
import com.redhat.devtools.intellij.quarkus.buildtool.BuildToolDelegate;
4043
import com.redhat.devtools.intellij.quarkus.telemetry.TelemetryEventName;
4144
import com.redhat.devtools.intellij.quarkus.telemetry.TelemetryManager;
45+
import com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants;
46+
import com.redhat.devtools.intellij.qute.psi.utils.PsiTypeUtils;
4247
import org.jetbrains.annotations.NotNull;
4348
import org.jetbrains.annotations.Nullable;
4449
import org.slf4j.Logger;
@@ -142,14 +147,15 @@ public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEn
142147
telemetryData.put("tool", toolDelegate.getDisplay());
143148
boolean debug = DefaultDebugExecutor.EXECUTOR_ID.equals(executor.getId());
144149
Integer debugPort = debug ? allocateLocalPort() : null;
150+
Integer quteDebugPort = debug && isQuteDebuggerInstalled(module) ? allocateLocalPort() : null;
145151
// The parameter (run/debug) executor is filled according the run/debug action
146152
// but in the case of Gradle, the executor must be only the run executor
147153
// otherwise for some reason, the stop button will stop the task without stopping the Quarkus application process.
148154
// Here we need to override the executor if Gradle is started in debug mode.
149155
Executor overridedExecutor = toolDelegate.getOverridedExecutor();
150156
executor = overridedExecutor != null ? overridedExecutor : executor;
151157
// Create a Gradle or Maven run configuration in memory
152-
RunnerAndConfigurationSettings settings = toolDelegate.getConfigurationDelegate(module, this, debugPort);
158+
RunnerAndConfigurationSettings settings = toolDelegate.getConfigurationDelegate(module, this, debugPort, quteDebugPort);
153159
if (settings != null) {
154160
QuarkusRunConfigurationManager.getInstance(module.getProject()); // to be sure that Quarkus execution listener is registered
155161
long groupId = ExecutionEnvironment.getNextUnusedExecutionId();
@@ -163,6 +169,11 @@ public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEn
163169
return state;
164170
}
165171

172+
173+
private boolean isQuteDebuggerInstalled(@NotNull Module module) {
174+
return PsiTypeUtils.findType("io.quarkus.qute.debug.adapter.RegisterDebugServerAdapter", module, null) != null;
175+
}
176+
166177
public String getProfile() {
167178
return getOptions().getProfile();
168179
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at https://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.qute.run;
12+
13+
import com.intellij.openapi.project.Project;
14+
import com.intellij.xdebugger.XDebugSession;
15+
import com.intellij.xdebugger.breakpoints.XBreakpointProperties;
16+
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
17+
import com.redhat.devtools.lsp4ij.dap.breakpoints.DAPBreakpointHandlerBase;
18+
import org.jetbrains.annotations.NotNull;
19+
20+
/**
21+
* Qute breakpoint handler.
22+
*/
23+
public class QuteBreakpointHandler extends DAPBreakpointHandlerBase<XLineBreakpoint<XBreakpointProperties<?>>> {
24+
public QuteBreakpointHandler(@NotNull XDebugSession session,
25+
@NotNull QuteDebugAdapterDescriptor quteDebugAdapterDescriptor,
26+
@NotNull Project project) {
27+
super(QuteBreakpointType.class, session, quteDebugAdapterDescriptor, project);
28+
}
29+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at https://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.qute.run;
12+
13+
import com.intellij.openapi.vfs.VirtualFile;
14+
import com.intellij.xdebugger.breakpoints.XBreakpointProperties;
15+
import com.redhat.devtools.lsp4ij.dap.breakpoints.DAPBreakpointTypeBase;
16+
import org.jetbrains.annotations.NotNull;
17+
import org.jetbrains.annotations.Nullable;
18+
19+
/**
20+
* Qute breakpoint type.
21+
*/
22+
public class QuteBreakpointType extends DAPBreakpointTypeBase<XBreakpointProperties<?>> {
23+
24+
private static final String BREAKPOINT_ID = "qute-breakpoint";
25+
26+
public QuteBreakpointType() {
27+
super(BREAKPOINT_ID, "Qute Breakpoint");
28+
}
29+
30+
@Override
31+
public @Nullable XBreakpointProperties<?> createBreakpointProperties(@NotNull VirtualFile virtualFile, int line) {
32+
return null;
33+
}
34+
35+
}

0 commit comments

Comments
 (0)