Skip to content

Commit ae0ff2b

Browse files
authored
Merge pull request quarkusio#48962 from angelozerr/qute-debugger-dap
Provide Qute DAP debugger support
2 parents 5c02a7e + a0415a2 commit ae0ff2b

File tree

67 files changed

+6730
-81
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+6730
-81
lines changed

extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import io.quarkus.deployment.ApplicationArchive;
8181
import io.quarkus.deployment.Feature;
8282
import io.quarkus.deployment.GeneratedClassGizmo2Adaptor;
83+
import io.quarkus.deployment.IsLocalDevelopment;
8384
import io.quarkus.deployment.IsTest;
8485
import io.quarkus.deployment.annotations.BuildProducer;
8586
import io.quarkus.deployment.annotations.BuildStep;
@@ -148,6 +149,7 @@
148149
import io.quarkus.qute.runtime.QuteRecorder;
149150
import io.quarkus.qute.runtime.QuteRecorder.QuteContext;
150151
import io.quarkus.qute.runtime.TemplateProducer;
152+
import io.quarkus.qute.runtime.debug.DebugQuteEngineObserver;
151153
import io.quarkus.qute.runtime.extensions.CollectionTemplateExtensions;
152154
import io.quarkus.qute.runtime.extensions.ConfigTemplateExtensions;
153155
import io.quarkus.qute.runtime.extensions.MapTemplateExtensions;
@@ -289,6 +291,12 @@ AdditionalBeanBuildItem additionalBeans() {
289291
.build();
290292
}
291293

294+
@BuildStep(onlyIf = IsLocalDevelopment.class)
295+
AdditionalBeanBuildItem quteDebuggerBean() {
296+
// Register the Qute debugger when Qute engine is created only in dev-mode
297+
return AdditionalBeanBuildItem.unremovableOf(DebugQuteEngineObserver.class);
298+
}
299+
292300
@BuildStep
293301
List<CheckedTemplateBuildItem> collectCheckedTemplates(BeanArchiveIndexBuildItem index,
294302
BuildProducer<BytecodeTransformerBuildItem> transformers,

extensions/qute/runtime/pom.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
<groupId>io.quarkus.qute</groupId>
2727
<artifactId>qute-core</artifactId>
2828
</dependency>
29+
<dependency>
30+
<groupId>io.quarkus.qute</groupId>
31+
<artifactId>qute-debug</artifactId>
32+
<version>${project.version}</version>
33+
<optional>true</optional>
34+
</dependency>
2935
<dependency>
3036
<groupId>io.quarkus</groupId>
3137
<artifactId>quarkus-cache</artifactId>
@@ -43,6 +49,19 @@
4349
<plugin>
4450
<groupId>io.quarkus</groupId>
4551
<artifactId>quarkus-extension-maven-plugin</artifactId>
52+
<executions>
53+
<execution>
54+
<phase>process-resources</phase>
55+
<goals>
56+
<goal>extension-descriptor</goal>
57+
</goals>
58+
<configuration>
59+
<conditionalDevDependencies>
60+
<artifact>io.quarkus.qute:qute-debug:${project.version}</artifact>
61+
</conditionalDevDependencies>
62+
</configuration>
63+
</execution>
64+
</executions>
4665
</plugin>
4766
<plugin>
4867
<artifactId>maven-compiler-plugin</artifactId>

extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/QuteConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Optional;
77
import java.util.regex.Pattern;
88

9+
import io.quarkus.qute.runtime.debug.QuteDebugConfig;
910
import io.quarkus.runtime.annotations.ConfigDocMapKey;
1011
import io.quarkus.runtime.annotations.ConfigPhase;
1112
import io.quarkus.runtime.annotations.ConfigRoot;
@@ -105,6 +106,11 @@ public interface QuteConfig {
105106
*/
106107
QuteTestModeConfig testMode();
107108

109+
/**
110+
* Qute debugger configuration.
111+
*/
112+
QuteDebugConfig debug();
113+
108114
public enum DuplicitTemplatesStrategy {
109115

110116
/**
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.quarkus.qute.runtime.debug;
2+
3+
import jakarta.annotation.PreDestroy;
4+
import jakarta.enterprise.event.Observes;
5+
import jakarta.inject.Singleton;
6+
7+
import io.quarkus.qute.EngineBuilder;
8+
import io.quarkus.qute.debug.adapter.RegisterDebugServerAdapter;
9+
import io.quarkus.qute.runtime.QuteConfig;
10+
import io.quarkus.runtime.LaunchMode;
11+
import io.smallrye.common.annotation.Experimental;
12+
13+
/**
14+
* Observes the creation of Qute engines and attaches the Qute debugger in development mode.
15+
*
16+
* <p>
17+
* When a new {@link io.quarkus.qute.EngineBuilder} is observed and the application is running in
18+
* {@link io.quarkus.runtime.LaunchMode#DEVELOPMENT development mode} with
19+
* {@code quarkus.qute.debug.enabled=true}, this observer:
20+
* <ul>
21+
* <li>Enables template tracing on the engine (required for the debugger).</li>
22+
* <li>Registers a {@link RegisterDebugServerAdapter} to allow DAP clients to connect.</li>
23+
* </ul>
24+
*
25+
* <p>
26+
* The {@link #cleanup()} method ensures that the debugger is properly reset when the application shuts down.
27+
*/
28+
@Singleton
29+
@Experimental("This observer is experimental and may change in the future")
30+
public class DebugQuteEngineObserver {
31+
32+
private final static RegisterDebugServerAdapter registrar = new RegisterDebugServerAdapter();
33+
34+
/**
35+
* Configures the engine with tracing and debugger support if debugging is enabled.
36+
*
37+
* @param builder the Qute engine builder being observed
38+
* @param config the Qute configuration
39+
*/
40+
void configureEngine(@Observes EngineBuilder builder, QuteConfig config) {
41+
if (LaunchMode.current() == LaunchMode.DEVELOPMENT && config.debug().enabled()) {
42+
builder.enableTracing(true);
43+
builder.addEngineListener(registrar);
44+
}
45+
}
46+
47+
/**
48+
* Cleans up the debugger on shutdown by resetting the registered debug server adapter.
49+
*/
50+
@PreDestroy
51+
void cleanup() {
52+
if (LaunchMode.current() == LaunchMode.DEVELOPMENT) {
53+
registrar.reset();
54+
}
55+
}
56+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.quarkus.qute.runtime.debug;
2+
3+
import io.quarkus.runtime.annotations.ConfigGroup;
4+
import io.smallrye.config.WithDefault;
5+
6+
@ConfigGroup
7+
public interface QuteDebugConfig {
8+
9+
/**
10+
* Enables or disables the Qute debug mode. This feature is experimental.
11+
* <p>
12+
* When enabled, Qute templates can be debugged directly at runtime.
13+
* This includes the ability to:
14+
* </p>
15+
* <ul>
16+
* <li>Set breakpoints inside templates</li>
17+
* <li>Inspect the stack trace of visited template nodes during rendering</li>
18+
* <li>Evaluate expressions in the current Qute context</li>
19+
* </ul>
20+
*
21+
* <p>
22+
* This mode is intended for development and troubleshooting purposes.
23+
* </p>
24+
*
25+
* <p>
26+
* <strong>Default value:</strong> {@code false}
27+
* </p>
28+
*
29+
* <p>
30+
* Example configuration:
31+
* </p>
32+
*
33+
* <pre>
34+
* quarkus.qute.debug.enabled = true
35+
* </pre>
36+
*
37+
* @return {@code true} if Qute debug mode is active, {@code false} otherwise.
38+
*/
39+
@WithDefault("false")
40+
boolean enabled();
41+
}

independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolver.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.quarkus.qute;
22

3+
import java.util.Collections;
4+
import java.util.Set;
5+
36
/**
47
* Value resolvers are used when evaluating expressions.
58
* <p>
@@ -47,6 +50,73 @@ default ValueResolver getCachedResolver(EvalContext context) {
4750
return this;
4851
}
4952

53+
/**
54+
* Returns the set of property names supported by this value resolver for code completion in the Qute debugger.
55+
*
56+
* <p>
57+
* These properties are suggested when evaluating expressions on a base object. For example, if the user invokes
58+
* completion at {@code myList.|}, the evaluation context will be initialized with {@code myList} as the base object,
59+
* and {@link #appliesTo(io.quarkus.qute.EvalContext) appliesTo} will be called with that context. Only if it returns
60+
* {@code true} will the properties from this set be proposed.
61+
*
62+
* <p>
63+
* Completion examples:
64+
* <ul>
65+
* <li>{@code "length"} → inserts as-is: <code>myList.length|</code></li>
66+
* <li>{@code "size"} → inserts as-is: <code>myList.size|</code></li>
67+
* </ul>
68+
*
69+
* <p>
70+
* Example:
71+
*
72+
* <pre>{@code
73+
* @Override
74+
* public Set<String> getSupportedProperties() {
75+
* return Set.of("length", "size");
76+
* }
77+
* }</pre>
78+
*
79+
* @return a set of supported property names to be shown in the debugger's code completion
80+
*/
81+
default Set<String> getSupportedProperties() {
82+
return Collections.emptySet();
83+
}
84+
85+
/**
86+
* Returns the set of method signatures supported by this value resolver for code completion in the Qute debugger.
87+
*
88+
* <p>
89+
* These methods are suggested when evaluating expressions on a base object. For example, if the user invokes
90+
* completion at {@code myList.|}, the evaluation context will be initialized with {@code myList} as the base object,
91+
* and {@link #appliesTo(io.quarkus.qute.EvalContext) appliesTo} will be called with that context. Only if it returns
92+
* {@code true} will the methods from this set be proposed.
93+
*
94+
* <p>
95+
* Completion examples:
96+
* <ul>
97+
* <li>{@code "take(index)"} → inserts as-is: <code>myList.take(index)|</code></li>
98+
* <li>{@code "takeLast(${index})"} → inserts with the parameter selected: <code>myList.takeLast(|[index])</code></li>
99+
* </ul>
100+
*
101+
* <p>
102+
* The {@code ${param}} syntax indicates that the debugger selects the parameter so the user can type it immediately.
103+
*
104+
* <p>
105+
* Example:
106+
*
107+
* <pre>{@code
108+
* @Override
109+
* public Set<String> getSupportedMethods() {
110+
* return Set.of("take(index)", "takeLast(${index})");
111+
* }
112+
* }</pre>
113+
*
114+
* @return a set of supported method signatures to be shown in the debugger's code completion
115+
*/
116+
default Set<String> getSupportedMethods() {
117+
return Collections.emptySet();
118+
}
119+
50120
/**
51121
*
52122
* @return a new builder

0 commit comments

Comments
 (0)