Skip to content

Commit d105cfe

Browse files
committed
Qute debugging support inside Java file
Signed-off-by: azerr <[email protected]>
1 parent 0716372 commit d105cfe

File tree

19 files changed

+685
-60
lines changed

19 files changed

+685
-60
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
import io.quarkus.qute.Expression;
119119
import io.quarkus.qute.Expression.VirtualMethodPart;
120120
import io.quarkus.qute.Identifiers;
121+
import io.quarkus.qute.JavaElementUriBuilder;
121122
import io.quarkus.qute.LoopSectionHelper;
122123
import io.quarkus.qute.NamespaceResolver;
123124
import io.quarkus.qute.ParameterDeclaration;
@@ -129,6 +130,7 @@
129130
import io.quarkus.qute.SectionHelperFactory;
130131
import io.quarkus.qute.SetSectionHelper;
131132
import io.quarkus.qute.Template;
133+
import io.quarkus.qute.TemplateContents;
132134
import io.quarkus.qute.TemplateData;
133135
import io.quarkus.qute.TemplateException;
134136
import io.quarkus.qute.TemplateExtension;
@@ -2627,6 +2629,7 @@ void collecTemplateContents(BeanArchiveIndexBuildItem index, List<CheckedTemplat
26272629
.path(getCheckedTemplatePath(index.getIndex(), checkedTemplateAnnotation, fragmentId, target)
26282630
+ suffix)
26292631
.extensionInfo(target.toString())
2632+
.source(getSource(target, TemplateContents.class))
26302633
.build());
26312634
continue;
26322635
}
@@ -2651,6 +2654,13 @@ void collecTemplateContents(BeanArchiveIndexBuildItem index, List<CheckedTemplat
26512654
}
26522655
}
26532656

2657+
private URI getSource(ClassInfo target, Class<?> annotationClass) {
2658+
return JavaElementUriBuilder
2659+
.builder(target.toString())
2660+
.setAnnotation(annotationClass.getName())
2661+
.build();
2662+
}
2663+
26542664
@BuildStep
26552665
@Record(value = STATIC_INIT)
26562666
void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecorder recorder,

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ private Optional<TemplateLocation> locate(String path) {
380380
LOGGER.debugf("Locate template contents for %s", path);
381381
TemplateInfo template = templates.get(path);
382382
if (template != null && template.hasContent()) {
383-
return getTemplateLocation(template.content, path);
383+
return getTemplateLocation(template, path);
384384
}
385385

386386
// Try path with suffixes
@@ -391,7 +391,7 @@ private Optional<TemplateLocation> locate(String path) {
391391
}
392392
template = templates.get(pathWithSuffix);
393393
if (template != null && template.hasContent()) {
394-
return getTemplateLocation(template.content, pathWithSuffix);
394+
return getTemplateLocation(template, pathWithSuffix);
395395
}
396396
}
397397

@@ -421,8 +421,10 @@ private Optional<TemplateLocation> locate(String path) {
421421
return Optional.empty();
422422
}
423423

424-
private Optional<TemplateLocation> getTemplateLocation(String content, String pathWithSuffix) {
425-
return Optional.of(new StringTemplateLocation(content, Optional.ofNullable(createVariant(pathWithSuffix))));
424+
private Optional<TemplateLocation> getTemplateLocation(TemplateInfo templateInfo, String pathWithSuffix) {
425+
String content = templateInfo.content;
426+
URI source = templateInfo.parseSource();
427+
return Optional.of(new StringTemplateLocation(content, Optional.ofNullable(createVariant(pathWithSuffix)), source));
426428
}
427429

428430
private Optional<TemplateLocation> getTemplateLocation(URL resource, String templatePath, String path) {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package io.quarkus.qute;
2+
3+
import java.net.URI;
4+
5+
/**
6+
* Builder for Qute-specific URIs that reference a Java element
7+
* (class, method, or annotation) from a template.
8+
* <p>
9+
* These URIs have the format:
10+
*
11+
* <pre>
12+
* qute-java://&lt;fully-qualified-class-name&gt;[#method][@annotation]
13+
* </pre>
14+
*
15+
* Examples:
16+
* <ul>
17+
* <li>Class-level annotation: <code>qute-java://[email protected]</code></li>
18+
* <li>Method-level annotation: <code>qute-java://com.acme.Bean#[email protected]</code></li>
19+
* </ul>
20+
* </p>
21+
*
22+
* <p>
23+
* This builder is used to construct such URIs in a type-safe way and to provide
24+
* utility methods to identify and parse them. It is aligned with
25+
* {@link io.quarkus.qute.debug.client.JavaSourceLocationArguments#javaElementUri}.
26+
* </p>
27+
*/
28+
public class JavaElementUriBuilder {
29+
30+
/** Scheme used for Qute Java URIs. */
31+
public static final String QUTE_JAVA_SCHEME = "qute-java";
32+
33+
/** Prefix for Qute Java URIs. */
34+
public static final String QUTE_JAVA_URI_PREFIX = QUTE_JAVA_SCHEME + "://";
35+
36+
private final String typeName;
37+
private String method;
38+
private String annotation;
39+
40+
private JavaElementUriBuilder(String typeName) {
41+
this.typeName = typeName;
42+
}
43+
44+
/**
45+
* Returns the fully qualified Java class name for this URI.
46+
*
47+
* @return the class name
48+
*/
49+
public String getTypeName() {
50+
return typeName;
51+
}
52+
53+
/**
54+
* Returns the Java method name (nullable if not specified).
55+
*
56+
* @return the method name or {@code null}
57+
*/
58+
public String getMethod() {
59+
return method;
60+
}
61+
62+
/**
63+
* Sets the Java method name.
64+
*
65+
* @param method the method name to set
66+
* @return this builder
67+
*/
68+
public JavaElementUriBuilder setMethod(String method) {
69+
this.method = method;
70+
return this;
71+
}
72+
73+
/**
74+
* Returns the fully qualified Java annotation name (nullable if not specified).
75+
*
76+
* @return the annotation name or {@code null}
77+
*/
78+
public String getAnnotation() {
79+
return annotation;
80+
}
81+
82+
/**
83+
* Sets the fully qualified Java annotation name.
84+
*
85+
* @param annotation the annotation name to set
86+
* @return this builder
87+
*/
88+
public JavaElementUriBuilder setAnnotation(String annotation) {
89+
this.annotation = annotation;
90+
return this;
91+
}
92+
93+
/**
94+
* Creates a new builder for the given Java class name.
95+
*
96+
* @param typeName fully qualified Java class name
97+
* @return a new {@link JavaElementUriBuilder}
98+
*/
99+
public static JavaElementUriBuilder builder(String typeName) {
100+
return new JavaElementUriBuilder(typeName);
101+
}
102+
103+
/**
104+
* Builds the Qute Java URI representing the element.
105+
*
106+
* @return a {@link URI} for the Java element
107+
*/
108+
public URI build() {
109+
StringBuilder uri = new StringBuilder(QUTE_JAVA_URI_PREFIX);
110+
uri.append(typeName);
111+
if (method != null) {
112+
uri.append("#").append(method);
113+
}
114+
if (annotation != null) {
115+
uri.append("@").append(annotation);
116+
}
117+
return URI.create(uri.toString());
118+
}
119+
120+
/**
121+
* Returns true if the given URI uses the qute-java scheme.
122+
*
123+
* @param uri the URI to check
124+
* @return {@code true} if this URI is a Qute Java element URI
125+
*/
126+
public static boolean isJavaUri(URI uri) {
127+
return uri != null && QUTE_JAVA_SCHEME.equals(uri.getScheme());
128+
}
129+
130+
/**
131+
* Returns true if the given string starts with the qute-java URI prefix.
132+
*
133+
* @param uri the URI string to check
134+
* @return {@code true} if this string is a Qute Java element URI
135+
*/
136+
public static boolean isJavaUri(String uri) {
137+
return uri != null && uri.startsWith(QUTE_JAVA_URI_PREFIX);
138+
}
139+
}

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

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

33
import java.io.Reader;
4+
import java.net.URI;
45
import java.util.Objects;
56
import java.util.Optional;
67

@@ -11,14 +12,20 @@ public class StringTemplateLocation implements TemplateLocation {
1112

1213
private final String content;
1314
private final Optional<Variant> variant;
15+
private final URI source;
1416

1517
public StringTemplateLocation(String content) {
1618
this(content, Optional.empty());
1719
}
1820

1921
public StringTemplateLocation(String content, Optional<Variant> variant) {
22+
this(content, variant, null);
23+
}
24+
25+
public StringTemplateLocation(String content, Optional<Variant> variant, URI source) {
2026
this.content = Objects.requireNonNull(content);
2127
this.variant = Objects.requireNonNull(variant);
28+
this.source = source;
2229
}
2330

2431
@Override
@@ -31,4 +38,12 @@ public Optional<Variant> getVariant() {
3138
return variant;
3239
}
3340

41+
@Override
42+
public Optional<URI> getSource() {
43+
if (source != null) {
44+
return Optional.of(source);
45+
}
46+
return Optional.empty();
47+
}
48+
3449
}

independent-projects/qute/debug/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Qute Debugger
22

3-
Qute Debugger allows you to **debug Qute templates using breakpoints** in any IDE or editor that supports the [Debug Adapter Protocol (DAP)](https://microsoft.github.io/debug-adapter-protocol/).
3+
Since Quarkus **3.29**, Qute Debugger allows you to **debug Qute templates using breakpoints** in any IDE or editor that supports the [Debug Adapter Protocol (DAP)](https://microsoft.github.io/debug-adapter-protocol/).
44

55
It works seamlessly with:
66

@@ -138,6 +138,12 @@ Pause only when certain conditions are met.
138138

139139
![Conditional Breakpoint](./images/ConditionalBreakpoint.png)
140140

141+
### Hover
142+
143+
Hover
144+
145+
![Hover](./images/Hover.png)
146+
141147
### Expression Evaluation
142148

143149
Evaluate expressions on the fly while debugging templates.
@@ -156,12 +162,6 @@ Inspect the current context and variables in real-time.
156162

157163
![Variables](./images/Variables.png)
158164

159-
## Feature Summary
165+
### Degugging in Java file
160166

161-
| Feature | Supported |
162-
|------------------------|-----------|
163-
| Simple Breakpoints ||
164-
| Conditional Breakpoints||
165-
| Expression Evaluation ||
166-
| Code Completion ||
167-
| Variable Inspection ||
167+
Since Quarkus **3.31** ...

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/Debugger.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
import org.eclipse.lsp4j.debug.Thread;
1616
import org.eclipse.lsp4j.debug.Variable;
1717

18+
import io.quarkus.qute.debug.client.JavaSourceResolver;
19+
1820
/**
1921
* Qute debugger API.
2022
*/
21-
public interface Debugger {
23+
public interface Debugger extends JavaSourceResolver {
2224

2325
/**
2426
* Returns the current state of the remote debugger for a given thread.

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/adapter/DebugServerAdapter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import io.quarkus.qute.debug.StoppedEvent;
4949
import io.quarkus.qute.debug.ThreadEvent;
5050
import io.quarkus.qute.debug.agent.DebuggeeAgent;
51+
import io.quarkus.qute.debug.client.JavaSourceResolver;
5152

5253
/**
5354
* Debug Adapter Protocol (DAP) server implementation for Qute debugging.
@@ -130,6 +131,9 @@ public CompletableFuture<Capabilities> initialize(InitializeRequestArguments arg
130131
public void connect(IDebugProtocolClient client) {
131132
this.client = client;
132133
this.agent.setEnabled(true);
134+
if (client instanceof JavaSourceResolver javaFileInfoProvider) {
135+
((DebuggeeAgent) agent).setJavaSourceResolver(javaFileInfoProvider);
136+
}
133137
}
134138

135139
/**

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/adapter/RegisterDebugServerAdapter.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
import java.util.concurrent.Future;
1111
import java.util.concurrent.TimeUnit;
1212

13-
import org.eclipse.lsp4j.debug.launch.DSPLauncher;
14-
import org.eclipse.lsp4j.debug.services.IDebugProtocolClient;
1513
import org.eclipse.lsp4j.jsonrpc.Launcher;
14+
import org.eclipse.lsp4j.jsonrpc.debug.DebugLauncher;
1615

1716
import io.quarkus.qute.Engine;
1817
import io.quarkus.qute.EngineBuilder.EngineListener;
1918
import io.quarkus.qute.debug.agent.DebuggeeAgent;
19+
import io.quarkus.qute.debug.client.QuteDebugProtocolClient;
2020
import io.quarkus.qute.trace.TemplateEvent;
2121
import io.quarkus.qute.trace.TraceListener;
2222

@@ -279,8 +279,8 @@ private void setupLauncher(Socket client, boolean suspend) throws IOException {
279279
launcherFuture.cancel(true);
280280
}
281281

282-
Launcher<IDebugProtocolClient> launcher = DSPLauncher.createServerLauncher(server, client.getInputStream(),
283-
client.getOutputStream(), executor, null);
282+
Launcher<QuteDebugProtocolClient> launcher = DebugLauncher.createLauncher(server, QuteDebugProtocolClient.class,
283+
client.getInputStream(), client.getOutputStream(), executor, null);
284284

285285
var clientProxy = launcher.getRemoteProxy();
286286
server.connect(clientProxy);

independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/DebuggeeAgent.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
import io.quarkus.qute.debug.agent.source.SourceReferenceRegistry;
4242
import io.quarkus.qute.debug.agent.source.SourceTemplateRegistry;
4343
import io.quarkus.qute.debug.agent.variables.VariablesRegistry;
44+
import io.quarkus.qute.debug.client.JavaSourceLocationArguments;
45+
import io.quarkus.qute.debug.client.JavaSourceLocationResponse;
46+
import io.quarkus.qute.debug.client.JavaSourceResolver;
4447
import io.quarkus.qute.trace.ResolveEvent;
4548
import io.quarkus.qute.trace.TemplateEvent;
4649

@@ -103,6 +106,8 @@ public class DebuggeeAgent implements Debugger {
103106
/** Indicates whether the debugging agent is enabled. */
104107
private boolean enabled;
105108

109+
private JavaSourceResolver javaSourceResolver;
110+
106111
/**
107112
* Creates a new {@link DebuggeeAgent} instance.
108113
*/
@@ -472,7 +477,11 @@ public VariablesRegistry getVariablesRegistry() {
472477

473478
public SourceTemplateRegistry getSourceTemplateRegistry(Engine engine) {
474479
return this.sourceTemplateRegistry.computeIfAbsent(engine,
475-
k -> new SourceTemplateRegistry(breakpointsRegistry, sourceReferenceRegistry, engine));
480+
k -> new SourceTemplateRegistry(breakpointsRegistry, sourceReferenceRegistry, this, engine));
481+
}
482+
483+
public void setJavaSourceResolver(JavaSourceResolver javaSourceResolver) {
484+
this.javaSourceResolver = javaSourceResolver;
476485
}
477486

478487
@Override
@@ -516,4 +525,12 @@ public void reset() {
516525
this.sourceTemplateRegistry.clear();
517526
this.sourceReferenceRegistry.reset();
518527
}
528+
529+
@Override
530+
public CompletableFuture<JavaSourceLocationResponse> resolveJavaSource(JavaSourceLocationArguments params) {
531+
if (javaSourceResolver != null) {
532+
return javaSourceResolver.resolveJavaSource(params);
533+
}
534+
return CompletableFuture.completedFuture(null);
535+
}
519536
}

0 commit comments

Comments
 (0)