2424import io .quarkus .qute .trace .ResolveEvent ;
2525
2626/**
27- * Represents a single stack frame in the Qute debugging process.
27+ * Represents a single Qute stack frame in the debugging process.
28+ *
2829 * <p>
29- * A {@link RemoteStackFrame} corresponds to the evaluation of a
30- * {@link TemplateNode} at runtime. It stores contextual information such as the
31- * variables in scope, the template being executed, and the current execution
32- * state.
30+ * A {@link RemoteStackFrame} corresponds to the evaluation of a {@link TemplateNode}
31+ * during the rendering of a Qute template. It encapsulates the current execution context,
32+ * including variables, scopes, and the source template being processed.
3333 * </p>
3434 *
3535 * <p>
36- * It extends {@link StackFrame} from the Debug Adapter Protocol (DAP), allowing
37- * integration with remote debugging clients.
36+ * This class integrates with the Debug Adapter Protocol (DAP) through
37+ * {@link org.eclipse.lsp4j.debug.StackFrame}, enabling external debuggers (like VSCode or IntelliJ)
38+ * to display Qute stack frames, inspect variables, and evaluate expressions.
3839 * </p>
3940 */
4041public class RemoteStackFrame extends StackFrame {
4142
42- /**
43- * Represents an empty array of stack frames.
44- */
43+ /** Represents an empty array of stack frames. */
4544 public static final StackFrame [] EMPTY_STACK_FRAMES = new StackFrame [0 ];
4645
47- /**
48- * Counter used to assign a unique ID to each frame.
49- */
46+ /** Counter used to assign a unique ID to each frame. */
5047 private static final AtomicInteger frameIdCounter = new AtomicInteger ();
5148
52- /**
53- * The previous frame in the call stack, or {@code null} if this is the first
54- * frame.
55- */
49+ /** The previous frame in the call stack, or {@code null} if this is the first frame. */
5650 private final transient RemoteStackFrame previousFrame ;
5751
58- /**
59- * The ID of the template currently being executed.
60- */
52+ /** The ID of the template currently being executed. */
6153 private final transient String templateId ;
6254
63- /**
64- * Registry of variables used in this stack frame.
65- */
55+ /** Registry of variables used in this stack frame. */
6656 private final transient VariablesRegistry variablesRegistry ;
6757
68- /**
69- * Lazily created list of available scopes (locals, globals, namespaces).
70- */
58+ /** Lazily created list of available scopes (locals, globals, namespaces). */
7159 private transient Collection <RemoteScope > scopes ;
7260
73- /**
74- * The resolve event associated with this frame, containing runtime context.
75- */
61+ /** The resolve event associated with this frame, containing runtime context. */
7662 private final transient ResolveEvent event ;
7763
64+ /** The remote thread that owns this frame, responsible for executing evaluations. */
65+ private final transient RemoteThread remoteThread ;
66+
7867 /**
7968 * Creates a new {@link RemoteStackFrame}.
8069 *
81- * @param event the resolve event describing the current
82- * execution
70+ * @param event the resolve event describing the current execution
8371 * @param previousFrame the previous stack frame, may be {@code null}
8472 * @param sourceTemplateRegistry registry for mapping templates to debug sources
85- * @param variablesRegistry the registry for managing variables
73+ * @param variablesRegistry registry for managing variables
74+ * @param remoteThread the owning remote thread
8675 */
8776 public RemoteStackFrame (ResolveEvent event , RemoteStackFrame previousFrame ,
88- SourceTemplateRegistry sourceTemplateRegistry , VariablesRegistry variablesRegistry ) {
77+ SourceTemplateRegistry sourceTemplateRegistry , VariablesRegistry variablesRegistry ,
78+ RemoteThread remoteThread ) {
8979 this .event = event ;
9080 this .previousFrame = previousFrame ;
9181 this .variablesRegistry = variablesRegistry ;
82+ this .remoteThread = remoteThread ;
83+
9284 int id = frameIdCounter .incrementAndGet ();
9385 int line = event .getTemplateNode ().getOrigin ().getLine ();
9486 super .setId (id );
9587 super .setName (event .getTemplateNode ().toString ());
9688 super .setLine (line );
89+
9790 this .templateId = event .getTemplateNode ().getOrigin ().getTemplateId ();
9891 super .setSource (
99- sourceTemplateRegistry .getSource (templateId ,
100- previousFrame != null ? previousFrame .getSource () : null ));
92+ sourceTemplateRegistry .getSource (templateId , previousFrame != null ? previousFrame .getSource () : null ));
10193 }
10294
103- /**
104- * Returns the template ID associated with this frame.
105- *
106- * @return the template ID
107- */
95+ /** @return the template ID associated with this frame */
10896 public String getTemplateId () {
10997 return templateId ;
11098 }
11199
112- /**
113- * Returns the template Uri associated with this frame and null otherwise.
114- *
115- * @return the template Uri associated with this frame and null otherwise.
116- */
100+ /** @return the template URI associated with this frame, or {@code null} if unavailable */
117101 public URI getTemplateUri () {
118102 var source = getSource ();
119103 return source != null ? source .getUri () : null ;
@@ -124,11 +108,7 @@ public RemoteSource getSource() {
124108 return (RemoteSource ) super .getSource ();
125109 }
126110
127- /**
128- * Returns the previous stack frame, or {@code null} if none exists.
129- *
130- * @return the previous {@link RemoteStackFrame}
131- */
111+ /** @return the previous stack frame, or {@code null} if none exists */
132112 public RemoteStackFrame getPrevious () {
133113 return previousFrame ;
134114 }
@@ -138,13 +118,12 @@ public RemoteStackFrame getPrevious() {
138118 * <p>
139119 * Scopes include:
140120 * <ul>
141- * <li>Locals ( variables in the current template context) </li>
142- * <li>Globals (global variables accessible in Qute) </li>
143- * <li>Namespace resolvers (custom resolvers for Qute templates) </li>
121+ * <li>Locals — variables specific to the current template</li>
122+ * <li>Globals — shared Qute global variables </li>
123+ * <li>Namespace resolvers — registered resolvers for {@code namespace:expression} </li>
144124 * </ul>
145- * </p>
146125 *
147- * @return the collection of {@link RemoteScope}
126+ * @return a collection of {@link RemoteScope}
148127 */
149128 public Collection <RemoteScope > getScopes () {
150129 if (scopes == null ) {
@@ -153,32 +132,24 @@ public Collection<RemoteScope> getScopes() {
153132 return scopes ;
154133 }
155134
156- /**
157- * Creates the list of scopes for this frame.
158- *
159- * @return a collection of {@link RemoteScope}
160- */
161135 private Collection <RemoteScope > createScopes () {
162136 Collection <RemoteScope > scopes = new ArrayList <>();
163- // Locals scope
164137 scopes .add (new LocalsScope (event .getContext (), this , variablesRegistry ));
165- // Global scope
166138 scopes .add (new GlobalsScope (event .getContext (), this , variablesRegistry ));
167- // Namespace resolvers scope
168139 scopes .add (new NamespaceResolversScope (event .getEngine (), this , variablesRegistry ));
169140 return scopes ;
170141 }
171142
172143 /**
173- * Evaluates an expression in the current frame context.
144+ * Evaluates an arbitrary Qute expression in the current frame context.
174145 * <p>
175- * If the expression contains conditional operators, it is parsed and evaluated
176- * as a conditional expression. Otherwise, it is treated as a simple Qute
177- * expression.
146+ * If the expression looks like a conditional (e.g. {@code user.age > 18}),
147+ * it is parsed and evaluated as a conditional expression. Otherwise, it is
148+ * evaluated as a simple Qute value expression.
178149 * </p>
179150 *
180- * @param expression the expression to evaluate
181- * @return a {@link CompletableFuture} containing the result of the evaluation
151+ * @param expression the Qute expression or condition to evaluate
152+ * @return a {@link CompletableFuture} resolving to the evaluation result
182153 */
183154 public CompletableFuture <Object > evaluate (String expression ) {
184155 if (isConditionExpression (expression )) {
@@ -188,42 +159,81 @@ public CompletableFuture<Object> evaluate(String expression) {
188159 } catch (Exception e ) {
189160 return CompletableFuture .failedFuture (e );
190161 }
191- // Evaluate condition expression without ignoring syntax expression
192- return evaluateCondition (ifNode , false );
162+ // Run the condition evaluation in the render thread
163+ return evaluateConditionInRenderThread (ifNode , false );
193164 }
194- // Evaluate simple expression
195- return event .getContext ().evaluate (expression ).toCompletableFuture ();
165+ return evaluateExpressionInRenderThread (expression );
196166 }
197167
198168 /**
199- * Determines if a given expression should be treated as a conditional
200- * expression.
169+ * Evaluates a Qute expression inside the render thread.
201170 *
202- * @param expression the expression to test
203- * @return {@code true} if the expression contains conditional operators,
204- * {@code false} otherwise
171+ * <p>
172+ * Qute expressions (like {@code uri:Todos.index}) must be evaluated inside the
173+ * original rendering thread to ensure CDI {@code @RequestScoped} contexts are
174+ * active. Evaluating them elsewhere may cause
175+ * {@code ContextNotActiveException}.
176+ * </p>
177+ */
178+ private CompletableFuture <Object > evaluateExpressionInRenderThread (String expression ) {
179+ return remoteThread .evaluateInRenderThread (() -> event .getContext ().evaluate (expression ).toCompletableFuture ());
180+ }
181+
182+ /**
183+ * Checks if an expression contains conditional operators and should be
184+ * interpreted as a condition.
205185 */
206186 private static boolean isConditionExpression (String expression ) {
207- return expression .contains ("!" ) || expression .contains (">" ) || expression .contains ("gt" )
208- || expression .contains (">=" ) || expression .contains (" ge" ) || expression .contains ("<" )
209- || expression .contains (" lt" ) || expression .contains ("<=" ) || expression .contains (" le" )
210- || expression .contains (" eq" ) || expression .contains ("==" ) || expression .contains (" is" )
211- || expression .contains ("!=" ) || expression .contains (" ne" ) || expression .contains ("&&" )
212- || expression .contains (" and" ) || expression .contains ("||" ) || expression .contains (" or" );
187+ return expression .contains ("!" ) || expression .contains (">" ) || expression .contains ("==" )
188+ || expression .contains ("<" ) || expression .contains ("&&" ) || expression .contains ("||" )
189+ || expression .contains (" eq" ) || expression .contains (" ne" )
190+ || expression .contains (" gt" ) || expression .contains (" lt" )
191+ || expression .contains (" ge" ) || expression .contains (" le" )
192+ || expression .contains (" and" ) || expression .contains (" or" )
193+ || expression .contains (" is" );
213194 }
214195
215196 /**
216- * Evaluates a parsed conditional expression.
197+ * Evaluates a parsed conditional expression within the render thread context.
198+ *
199+ * <p>
200+ * This is used for conditional breakpoints: before suspending execution,
201+ * the condition must be evaluated safely inside the render thread.
202+ * </p>
203+ *
204+ * <p>
205+ * Calling {@code evaluateConditionInRenderThread()} from a suspended state
206+ * ensures the evaluation is scheduled on the render thread asynchronously
207+ * (via {@link RemoteThread#evaluateInRenderThread(java.util.concurrent.Callable)}),
208+ * avoiding deadlocks or premature resumption.
209+ * </p>
217210 *
218211 * @param ifNode the parsed {@link TemplateNode} representing the condition
219- * @param ignoreError whether to ignore evaluation errors and return
220- * {@code false}
221- * @return a {@link CompletableFuture} containing {@code true} or {@code false}
212+ * @param ignoreError whether to ignore evaluation errors and return {@code false}
213+ * @return a future resolving to {@code true} or {@code false}
214+ */
215+ public CompletableFuture <Object > evaluateConditionInRenderThread (TemplateNode ifNode , boolean ignoreError ) {
216+ return remoteThread .evaluateInRenderThread (() -> evaluateCondition (ifNode , ignoreError ));
217+ }
218+
219+ /**
220+ * Evaluates the given Qute {@code if} node in the current context.
221+ *
222+ * <p>
223+ * This method converts the Qute {@link TemplateNode} result into a boolean
224+ * value. If evaluation fails and {@code ignoreError} is {@code true}, it
225+ * returns {@code false} instead of throwing an exception.
226+ * </p>
227+ *
228+ * <p>
229+ * This method runs synchronously inside the render thread. It is typically
230+ * called by {@link #evaluateConditionInRenderThread(TemplateNode, boolean)}.
231+ * </p>
222232 */
223233 public CompletableFuture <Object > evaluateCondition (TemplateNode ifNode , boolean ignoreError ) {
224234 try {
225- return ifNode .resolve (event .getContext ())//
226- .toCompletableFuture ()//
235+ return ifNode .resolve (event .getContext ())
236+ .toCompletableFuture ()
227237 .handle ((result , error ) -> {
228238 if (error != null ) {
229239 if (ignoreError ) {
@@ -239,32 +249,18 @@ public CompletableFuture<Object> evaluateCondition(TemplateNode ifNode, boolean
239249 }
240250 }
241251
242- /**
243- * Returns the current Qute engine.
244- *
245- * @return the {@link Engine}
246- */
252+ /** @return the current Qute engine */
247253 public Engine getEngine () {
248254 return event .getEngine ();
249255 }
250256
251- /**
252- * Returns the {@link ResolveEvent} associated with this frame.
253- *
254- * @return the {@link ResolveEvent}
255- */
257+ /** @return the current {@link ResolveEvent} associated with this frame */
256258 ResolveEvent getEvent () {
257259 return event ;
258260 }
259261
260- /**
261- * Creates a new evaluation context for the given base object.
262- *
263- * @param base the base object
264- * @return a new {@link EvalContext}
265- */
262+ /** Creates a new {@link EvalContext} for the given base object. */
266263 public EvalContext createEvalContext (Object base ) {
267264 return new DebuggerEvalContext (base , this );
268265 }
269-
270266}
0 commit comments