Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import io.quarkus.qute.Expression;
import io.quarkus.qute.Expression.VirtualMethodPart;
import io.quarkus.qute.Identifiers;
import io.quarkus.qute.JavaElementUriBuilder;
import io.quarkus.qute.LoopSectionHelper;
import io.quarkus.qute.NamespaceResolver;
import io.quarkus.qute.ParameterDeclaration;
Expand All @@ -129,6 +130,7 @@
import io.quarkus.qute.SectionHelperFactory;
import io.quarkus.qute.SetSectionHelper;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateContents;
import io.quarkus.qute.TemplateData;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateExtension;
Expand Down Expand Up @@ -2627,6 +2629,7 @@ void collecTemplateContents(BeanArchiveIndexBuildItem index, List<CheckedTemplat
.path(getCheckedTemplatePath(index.getIndex(), checkedTemplateAnnotation, fragmentId, target)
+ suffix)
.extensionInfo(target.toString())
.source(getSource(target, TemplateContents.class))
.build());
continue;
}
Expand All @@ -2651,6 +2654,13 @@ void collecTemplateContents(BeanArchiveIndexBuildItem index, List<CheckedTemplat
}
}

private URI getSource(ClassInfo target, Class<?> annotationClass) {
return JavaElementUriBuilder
.builder(target.toString())
.setAnnotation(annotationClass.getName())
.build();
}

@BuildStep
@Record(value = STATIC_INIT)
void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecorder recorder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ private Optional<TemplateLocation> locate(String path) {
LOGGER.debugf("Locate template contents for %s", path);
TemplateInfo template = templates.get(path);
if (template != null && template.hasContent()) {
return getTemplateLocation(template.content, path);
return getTemplateLocation(template, path);
}

// Try path with suffixes
Expand All @@ -391,7 +391,7 @@ private Optional<TemplateLocation> locate(String path) {
}
template = templates.get(pathWithSuffix);
if (template != null && template.hasContent()) {
return getTemplateLocation(template.content, pathWithSuffix);
return getTemplateLocation(template, pathWithSuffix);
}
}

Expand Down Expand Up @@ -421,8 +421,10 @@ private Optional<TemplateLocation> locate(String path) {
return Optional.empty();
}

private Optional<TemplateLocation> getTemplateLocation(String content, String pathWithSuffix) {
return Optional.of(new StringTemplateLocation(content, Optional.ofNullable(createVariant(pathWithSuffix))));
private Optional<TemplateLocation> getTemplateLocation(TemplateInfo templateInfo, String pathWithSuffix) {
String content = templateInfo.content;
URI source = templateInfo.parseSource();
return Optional.of(new StringTemplateLocation(content, Optional.ofNullable(createVariant(pathWithSuffix)), source));
}

private Optional<TemplateLocation> getTemplateLocation(URL resource, String templatePath, String path) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package io.quarkus.qute;

import java.net.URI;

/**
* Builder for Qute-specific URIs that reference a Java element
* (class, method, or annotation) from a template.
* <p>
* These URIs have the format:
*
* <pre>
* qute-java://&lt;fully-qualified-class-name&gt;[#method][@annotation]
* </pre>
*
* Examples:
* <ul>
* <li>Class-level annotation: <code>qute-java://[email protected]</code></li>
* <li>Method-level annotation: <code>qute-java://com.acme.Bean#[email protected]</code></li>
* </ul>
* </p>
*
* <p>
* This builder is used to construct such URIs in a type-safe way and to provide
* utility methods to identify and parse them. It is aligned with
* {@link io.quarkus.qute.debug.client.JavaSourceLocationArguments#javaElementUri}.
* </p>
*/
public class JavaElementUriBuilder {

/** Scheme used for Qute Java URIs. */
public static final String QUTE_JAVA_SCHEME = "qute-java";

/** Prefix for Qute Java URIs. */
public static final String QUTE_JAVA_URI_PREFIX = QUTE_JAVA_SCHEME + "://";

private final String typeName;
private String method;
private String annotation;

private JavaElementUriBuilder(String typeName) {
this.typeName = typeName;
}

/**
* Returns the fully qualified Java class name for this URI.
*
* @return the class name
*/
public String getTypeName() {
return typeName;
}

/**
* Returns the Java method name (nullable if not specified).
*
* @return the method name or {@code null}
*/
public String getMethod() {
return method;
}

/**
* Sets the Java method name.
*
* @param method the method name to set
* @return this builder
*/
public JavaElementUriBuilder setMethod(String method) {
this.method = method;
return this;
}

/**
* Returns the fully qualified Java annotation name (nullable if not specified).
*
* @return the annotation name or {@code null}
*/
public String getAnnotation() {
return annotation;
}

/**
* Sets the fully qualified Java annotation name.
*
* @param annotation the annotation name to set
* @return this builder
*/
public JavaElementUriBuilder setAnnotation(String annotation) {
this.annotation = annotation;
return this;
}

/**
* Creates a new builder for the given Java class name.
*
* @param typeName fully qualified Java class name
* @return a new {@link JavaElementUriBuilder}
*/
public static JavaElementUriBuilder builder(String typeName) {
return new JavaElementUriBuilder(typeName);
}

/**
* Builds the Qute Java URI representing the element.
*
* @return a {@link URI} for the Java element
*/
public URI build() {
StringBuilder uri = new StringBuilder(QUTE_JAVA_URI_PREFIX);
uri.append(typeName);
if (method != null) {
uri.append("#").append(method);
}
if (annotation != null) {
uri.append("@").append(annotation);
}
return URI.create(uri.toString());
}

/**
* Returns true if the given URI uses the qute-java scheme.
*
* @param uri the URI to check
* @return {@code true} if this URI is a Qute Java element URI
*/
public static boolean isJavaUri(URI uri) {
return uri != null && QUTE_JAVA_SCHEME.equals(uri.getScheme());
}

/**
* Returns true if the given string starts with the qute-java URI prefix.
*
* @param uri the URI string to check
* @return {@code true} if this string is a Qute Java element URI
*/
public static boolean isJavaUri(String uri) {
return uri != null && uri.startsWith(QUTE_JAVA_URI_PREFIX);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.qute;

import java.io.Reader;
import java.net.URI;
import java.util.Objects;
import java.util.Optional;

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

private final String content;
private final Optional<Variant> variant;
private final URI source;

public StringTemplateLocation(String content) {
this(content, Optional.empty());
}

public StringTemplateLocation(String content, Optional<Variant> variant) {
this(content, variant, null);
}

public StringTemplateLocation(String content, Optional<Variant> variant, URI source) {
this.content = Objects.requireNonNull(content);
this.variant = Objects.requireNonNull(variant);
this.source = source;
}

@Override
Expand All @@ -31,4 +38,12 @@ public Optional<Variant> getVariant() {
return variant;
}

@Override
public Optional<URI> getSource() {
if (source != null) {
return Optional.of(source);
}
return Optional.empty();
}

}
18 changes: 9 additions & 9 deletions independent-projects/qute/debug/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Qute Debugger

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/).
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/).

It works seamlessly with:

Expand Down Expand Up @@ -138,6 +138,12 @@ Pause only when certain conditions are met.

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

### Hover

Hover

![Hover](./images/Hover.png)

### Expression Evaluation

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

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

## Feature Summary
### Degugging in Java file

| Feature | Supported |
|------------------------|-----------|
| Simple Breakpoints | ✅ |
| Conditional Breakpoints| ✅ |
| Expression Evaluation | ✅ |
| Code Completion | ✅ |
| Variable Inspection | ✅ |
Since Quarkus **3.31** ...
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
import org.eclipse.lsp4j.debug.Thread;
import org.eclipse.lsp4j.debug.Variable;

import io.quarkus.qute.debug.client.JavaSourceResolver;

/**
* Qute debugger API.
*/
public interface Debugger {
public interface Debugger extends JavaSourceResolver {

/**
* Returns the current state of the remote debugger for a given thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import io.quarkus.qute.debug.StoppedEvent;
import io.quarkus.qute.debug.ThreadEvent;
import io.quarkus.qute.debug.agent.DebuggeeAgent;
import io.quarkus.qute.debug.client.JavaSourceResolver;

/**
* Debug Adapter Protocol (DAP) server implementation for Qute debugging.
Expand Down Expand Up @@ -130,6 +131,9 @@ public CompletableFuture<Capabilities> initialize(InitializeRequestArguments arg
public void connect(IDebugProtocolClient client) {
this.client = client;
this.agent.setEnabled(true);
if (client instanceof JavaSourceResolver javaFileInfoProvider) {
((DebuggeeAgent) agent).setJavaSourceResolver(javaFileInfoProvider);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.eclipse.lsp4j.debug.launch.DSPLauncher;
import org.eclipse.lsp4j.debug.services.IDebugProtocolClient;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.debug.DebugLauncher;

import io.quarkus.qute.Engine;
import io.quarkus.qute.EngineBuilder.EngineListener;
import io.quarkus.qute.debug.agent.DebuggeeAgent;
import io.quarkus.qute.debug.client.QuteDebugProtocolClient;
import io.quarkus.qute.trace.TemplateEvent;
import io.quarkus.qute.trace.TraceListener;

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

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

var clientProxy = launcher.getRemoteProxy();
server.connect(clientProxy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
import io.quarkus.qute.debug.agent.source.SourceReferenceRegistry;
import io.quarkus.qute.debug.agent.source.SourceTemplateRegistry;
import io.quarkus.qute.debug.agent.variables.VariablesRegistry;
import io.quarkus.qute.debug.client.JavaSourceLocationArguments;
import io.quarkus.qute.debug.client.JavaSourceLocationResponse;
import io.quarkus.qute.debug.client.JavaSourceResolver;
import io.quarkus.qute.trace.ResolveEvent;
import io.quarkus.qute.trace.TemplateEvent;

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

private JavaSourceResolver javaSourceResolver;

/**
* Creates a new {@link DebuggeeAgent} instance.
*/
Expand Down Expand Up @@ -472,7 +477,11 @@ public VariablesRegistry getVariablesRegistry() {

public SourceTemplateRegistry getSourceTemplateRegistry(Engine engine) {
return this.sourceTemplateRegistry.computeIfAbsent(engine,
k -> new SourceTemplateRegistry(breakpointsRegistry, sourceReferenceRegistry, engine));
k -> new SourceTemplateRegistry(breakpointsRegistry, sourceReferenceRegistry, this, engine));
}

public void setJavaSourceResolver(JavaSourceResolver javaSourceResolver) {
this.javaSourceResolver = javaSourceResolver;
}

@Override
Expand Down Expand Up @@ -516,4 +525,12 @@ public void reset() {
this.sourceTemplateRegistry.clear();
this.sourceReferenceRegistry.reset();
}

@Override
public CompletableFuture<JavaSourceLocationResponse> resolveJavaSource(JavaSourceLocationArguments params) {
if (javaSourceResolver != null) {
return javaSourceResolver.resolveJavaSource(params);
}
return CompletableFuture.completedFuture(null);
}
}
Loading