66 lib/ [ jslib ]
77
88const HISTORY_JUMP_VALUE * : string = " history-jump"
9+ const
10+ FLOW_DECORATION_MAX_VALUES_PER_LINE * = 10
11+
12+ type
13+ FlowLineRenderValue * = object
14+ line* : int
15+ text* : cstring
16+ FlowLoopSliderData * = object
17+ line* : int
18+ loopId* : int
19+ firstLine* : int
20+ lastLine* : int
21+ baseLoopId* : int
22+ baseIteration* : int
23+ iterationCount* : int
24+ minIteration* : int
25+ maxIteration* : int
26+ activeIteration* : int
27+ locationInside* : bool
28+ rrTicksForIterations* : seq [int ]
29+ FlowInsetData * = object
30+ lineValues* : seq [FlowLineRenderValue ]
31+ loopSliders* : seq [FlowLoopSliderData ]
932
1033proc makeNotification * (kind: NotificationKind , text: cstring , isOperationStatus: bool = false ): Notification =
1134 Notification (
@@ -68,7 +91,38 @@ proc successMessage*(api: MediatorWithSubscribers, text: cstring) =
6891proc openValueInScratchpad * (api: MediatorWithSubscribers , arg: ValueWithExpression ) =
6992 api.emit (InternalAddToScratchpad , arg)
7093
71- proc findExpressionColumn (sourceLine: cstring , expression: cstring ): int =
94+ proc getStepExpressions * (step: FlowStep ): seq [cstring ] {.exportc .} =
95+ # # Resolve expressions in a stable order:
96+ # # 1) exprOrder from backend, 2) missing before-values keys, 3) missing after-values keys.
97+ var expressions: seq [cstring ] = @ []
98+ for expression in step.exprOrder:
99+ if expression.len > 0 and not expressions.contains (expression):
100+ expressions.add (expression)
101+ for expression, _ in step.beforeValues:
102+ if expression.len > 0 and not expressions.contains (expression):
103+ expressions.add (expression)
104+ for expression, _ in step.afterValues:
105+ if expression.len > 0 and not expressions.contains (expression):
106+ expressions.add (expression)
107+ result = expressions
108+
109+ proc getStepValuePair * (
110+ step: FlowStep ,
111+ expression: cstring
112+ ): tuple [beforeValue: Value , afterValue: Value ] {.exportc .} =
113+ let beforeValue =
114+ if step.beforeValues.hasKey (expression):
115+ step.beforeValues[expression]
116+ else :
117+ nil
118+ let afterValue =
119+ if step.afterValues.hasKey (expression):
120+ step.afterValues[expression]
121+ else :
122+ nil
123+ (beforeValue: beforeValue, afterValue: afterValue)
124+
125+ proc findExpressionColumn * (sourceLine: cstring , expression: cstring ): int {.exportc .} =
72126 # # Find a 1-based column for a whole-word expression match within a source line.
73127 if sourceLine.len == 0 or expression.len == 0 :
74128 return - 1
@@ -95,32 +149,274 @@ proc formatRenderText(expression: cstring, beforeValue: Value, afterValue: Value
95149 return cstring (fmt" { expression} : { afterValue.textRepr (compact = true )} " )
96150 cstring (fmt" { expression} : { beforeValue.textRepr (compact = true )} " )
97151
98- proc computeRenderValueGroups * (update: FlowUpdate , sourceLines: seq [cstring ]): seq [FlowRenderValue ] {.exportc .} =
99- # # Build render values from ViewSource steps only (exprOrder + before/after values).
152+ proc computeRenderValueGroups * (update: FlowUpdate , sourceLines: seq [cstring ]): seq [FlowRenderValue ] {.exportc .}
153+
154+ proc resolveFlowViewUpdate (update: FlowUpdate ): FlowViewUpdate =
100155 if update.isNil:
101- return @ []
102- var viewUpdate = update.viewUpdates[ViewSource ]
103- if viewUpdate .isNil:
156+ return nil
157+ result = update.viewUpdates[ViewSource ]
158+ if result .isNil:
104159 for candidate in update.viewUpdates:
105160 if not candidate.isNil:
106- viewUpdate = candidate
161+ return candidate
162+
163+ proc findStepCountForPositionAtRRTicks (
164+ viewUpdate: FlowViewUpdate ,
165+ position: int ,
166+ rrTicks: int
167+ ): int =
168+ # # Equivalent to flow.nim positionRRTicksToStepCount for active step resolution.
169+ if viewUpdate.isNil:
170+ return - 1
171+ if not viewUpdate.positionStepCounts.hasKey (position):
172+ return - 1
173+ let stepCounts = viewUpdate.positionStepCounts[position]
174+ if stepCounts.len == 0 :
175+ return - 1
176+
177+ let firstStepCount = stepCounts[0 ]
178+ let lastStepCount = stepCounts[^ 1 ]
179+
180+ if rrTicks < viewUpdate.steps[firstStepCount].rrTicks:
181+ return firstStepCount
182+ if rrTicks > viewUpdate.steps[lastStepCount].rrTicks:
183+ return lastStepCount
184+
185+ for i, stepCount in stepCounts:
186+ let nextStepCount = stepCounts[min (i + 1 , stepCounts.len - 1 )]
187+ if rrTicks >= viewUpdate.steps[stepCount].rrTicks and rrTicks <= viewUpdate.steps[nextStepCount].rrTicks:
188+ return stepCount
189+
190+ lastStepCount
191+
192+ proc loopDepth (viewUpdate: FlowViewUpdate , loopId: int ): int =
193+ var depth = 0
194+ var current = loopId
195+ while current > 0 and current < viewUpdate.loops.len:
196+ let baseLoop = viewUpdate.loops[current].base
197+ if baseLoop < 0 :
198+ break
199+ depth += 1
200+ current = baseLoop
201+ depth
202+
203+ proc activeLoopIterationForTicks * (
204+ rrTicksForIterations: seq [int ],
205+ debuggerLocationRRTicks: int
206+ ): int {.exportc .} =
207+ # # Resolve active loop iteration from rrTicks using nearest-left semantics.
208+ if rrTicksForIterations.len == 0 :
209+ return 0
210+ let firstLoopIterationRRTicks = rrTicksForIterations[0 ]
211+ let lastLoopIterationRRTicks = rrTicksForIterations[rrTicksForIterations.len - 1 ]
212+ if debuggerLocationRRTicks <= firstLoopIterationRRTicks:
213+ return 0
214+ if debuggerLocationRRTicks >= lastLoopIterationRRTicks:
215+ return rrTicksForIterations.len - 1
216+ for index in countdown (rrTicksForIterations.len - 1 , 0 ):
217+ if debuggerLocationRRTicks >= rrTicksForIterations[index]:
218+ return index
219+ 0
220+
221+ proc computeFlowLineValuesFromRenderValues * (
222+ renderValues: seq [FlowRenderValue ],
223+ maxValuesPerLine: int = FLOW_DECORATION_MAX_VALUES_PER_LINE
224+ ): seq [FlowLineRenderValue ] {.exportc .} =
225+ var lineToValues = JsAssoc [int , seq [FlowRenderValue ]]{}
226+ var lines: seq [int ] = @ []
227+
228+ for value in renderValues:
229+ if value.line <= 0 or value.text.len == 0 :
230+ continue
231+ if not lineToValues.hasKey (value.line):
232+ lineToValues[value.line] = @ []
233+ lines.add (value.line)
234+ var lineValues = lineToValues[value.line]
235+ lineValues.add (value)
236+ lineToValues[value.line] = lineValues
237+
238+ lines.sort (proc (a, b: int ): int = a - b)
239+
240+ for line in lines:
241+ var lineValues = lineToValues[line]
242+ lineValues.sort (proc (a, b: FlowRenderValue ): int =
243+ if a.column != b.column:
244+ return a.column - b.column
245+ cmp (a.text, b.text)
246+ )
247+ var texts: seq [cstring ] = @ []
248+ for i, lineValue in lineValues:
249+ if i >= maxValuesPerLine:
107250 break
251+ texts.add (lineValue.text)
252+ if texts.len > 0 :
253+ var lineText = cstring " "
254+ for i, text in texts:
255+ if i > 0 :
256+ lineText.add (cstring " , " )
257+ lineText.add (text)
258+ result .add (FlowLineRenderValue (line: line, text: lineText))
259+
260+ proc computeFlowLineValues * (
261+ update: FlowUpdate ,
262+ sourceLines: seq [cstring ],
263+ maxValuesPerLine: int = FLOW_DECORATION_MAX_VALUES_PER_LINE
264+ ): seq [FlowLineRenderValue ] {.exportc .} =
265+ let renderValues = computeRenderValueGroups (update, sourceLines)
266+ result = computeFlowLineValuesFromRenderValues (renderValues, maxValuesPerLine)
267+
268+ proc computeActiveRenderValues * (
269+ update: FlowUpdate ,
270+ sourceLines: seq [cstring ]
271+ ): seq [FlowRenderValue ] {.exportc .} =
272+ # # Build render values from active iteration steps at the current debugger rrTicks.
273+ let viewUpdate = resolveFlowViewUpdate (update)
274+ if viewUpdate.isNil:
275+ return @ []
276+
277+ let currentRRTicks = update.location.rrTicks
278+ var stepCountByLine = JsAssoc [int , int ]{}
279+
280+ # Baseline active step per line at current rrTicks.
281+ for position, _ in viewUpdate.positionStepCounts:
282+ let stepCount = findStepCountForPositionAtRRTicks (viewUpdate, position, currentRRTicks)
283+ if stepCount >= 0 and stepCount < viewUpdate.steps.len:
284+ stepCountByLine[position] = stepCount
285+
286+ # Override by active iteration of each loop so all lines in active iterations are rendered.
287+ var loopIds: seq [int ] = @ []
288+ for loopId, loop in viewUpdate.loops:
289+ if loopId <= 0 :
290+ continue
291+ if loop.iteration <= 0 :
292+ continue
293+ loopIds.add (loopId)
294+ loopIds.sort (proc (a, b: int ): int =
295+ let depthA = loopDepth (viewUpdate, a)
296+ let depthB = loopDepth (viewUpdate, b)
297+ if depthA != depthB:
298+ return depthA - depthB
299+ a - b
300+ )
301+
302+ for loopId in loopIds:
303+ let loop = viewUpdate.loops[loopId]
304+ let activeIteration =
305+ activeLoopIterationForTicks (loop.rrTicksForIterations, currentRRTicks)
306+ if loopId >= viewUpdate.loopIterationSteps.len:
307+ continue
308+ if activeIteration < 0 or activeIteration >= viewUpdate.loopIterationSteps[loopId].len:
309+ continue
310+ let iterationTable = viewUpdate.loopIterationSteps[loopId][activeIteration].table
311+ for line, stepCount in iterationTable:
312+ if stepCount >= 0 and stepCount < viewUpdate.steps.len:
313+ stepCountByLine[line] = stepCount
314+
315+ var valuesByKey = JsAssoc [cstring , FlowRenderValue ]{}
316+ for position, stepCount in stepCountByLine:
317+ let step = viewUpdate.steps[stepCount]
318+ let lineIndex = step.position - 1
319+ if lineIndex < 0 or lineIndex >= sourceLines.len:
320+ continue
321+
322+ for expression in getStepExpressions (step):
323+ let (beforeValue, afterValue) = getStepValuePair (step, expression)
324+ if beforeValue.isNil and afterValue.isNil:
325+ continue
326+ let column = findExpressionColumn (sourceLines[lineIndex], expression)
327+ if column < 0 :
328+ continue
329+ let text = formatRenderText (expression, beforeValue, afterValue)
330+ if text.len == 0 :
331+ continue
332+ let key = cstring (& " { step.position} :{ expression} " )
333+ valuesByKey[key] = FlowRenderValue (
334+ line: step.position,
335+ column: column,
336+ loopId: step.loop,
337+ iteration: step.iteration,
338+ rrTicks: step.rrTicks,
339+ text: text
340+ )
341+
342+ for _, value in valuesByKey:
343+ result .add (value)
344+
345+ result .sort (proc (a, b: FlowRenderValue ): int =
346+ if a.line != b.line:
347+ return a.line - b.line
348+ if a.column != b.column:
349+ return a.column - b.column
350+ cmp (a.text, b.text)
351+ )
352+
353+ proc computeActiveFlowLineValues * (
354+ update: FlowUpdate ,
355+ sourceLines: seq [cstring ],
356+ maxValuesPerLine: int = FLOW_DECORATION_MAX_VALUES_PER_LINE
357+ ): seq [FlowLineRenderValue ] {.exportc .} =
358+ let renderValues = computeActiveRenderValues (update, sourceLines)
359+ result = computeFlowLineValuesFromRenderValues (renderValues, maxValuesPerLine)
360+
361+ proc computeFlowLoopSliders * (update: FlowUpdate ): seq [FlowLoopSliderData ] {.exportc .} =
362+ if update.isNil:
363+ return @ []
364+ let viewUpdate = resolveFlowViewUpdate (update)
365+ if viewUpdate.isNil:
366+ return @ []
367+
368+ let currentLine = update.location.line
369+ let currentRRTicks = update.location.rrTicks
370+ for loopId, loop in viewUpdate.loops:
371+ if loop.iteration <= 0 :
372+ continue
373+ let rrTicksForIterations = loop.rrTicksForIterations
374+ let activeIteration = activeLoopIterationForTicks (rrTicksForIterations, currentRRTicks)
375+ result .add (FlowLoopSliderData (
376+ line: loop.first,
377+ loopId: loopId,
378+ firstLine: loop.first,
379+ lastLine: loop.last,
380+ baseLoopId: loop.base,
381+ baseIteration: loop.baseIteration,
382+ iterationCount: loop.iteration,
383+ minIteration: 0 ,
384+ maxIteration: loop.iteration,
385+ activeIteration: activeIteration,
386+ locationInside: currentLine >= loop.first and currentLine <= loop.last,
387+ rrTicksForIterations: rrTicksForIterations
388+ ))
389+
390+ result .sort (proc (a, b: FlowLoopSliderData ): int =
391+ if a.locationInside != b.locationInside:
392+ return (if a.locationInside: - 1 else : 1 )
393+ if a.firstLine != b.firstLine:
394+ return a.firstLine - b.firstLine
395+ a.loopId - b.loopId
396+ )
397+
398+ proc computeFlowInsetData * (
399+ update: FlowUpdate ,
400+ sourceLines: seq [cstring ],
401+ maxValuesPerLine: int = FLOW_DECORATION_MAX_VALUES_PER_LINE
402+ ): FlowInsetData {.exportc .} =
403+ result = FlowInsetData (
404+ lineValues: computeActiveFlowLineValues (update, sourceLines, maxValuesPerLine),
405+ loopSliders: computeFlowLoopSliders (update)
406+ )
407+
408+ proc computeRenderValueGroups * (update: FlowUpdate , sourceLines: seq [cstring ]): seq [FlowRenderValue ] {.exportc .} =
409+ # # Build render values from ViewSource steps only (exprOrder + before/after values).
410+ let viewUpdate = resolveFlowViewUpdate (update)
108411 if viewUpdate.isNil:
109412 return @ []
110413
111414 var valuesByKey = JsAssoc [cstring , FlowRenderValue ]{}
112415 for step in viewUpdate.steps:
113- var expressions = step.exprOrder
114- if expressions.len == 0 :
115- for key, _ in step.beforeValues:
116- expressions.add (key)
117- for key, _ in step.afterValues:
118- if not expressions.contains (key):
119- expressions.add (key)
416+ let expressions = getStepExpressions (step)
120417
121418 for expression in expressions:
122- let beforeValue = if step.beforeValues.hasKey (expression): step.beforeValues[expression] else : nil
123- let afterValue = if step.afterValues.hasKey (expression): step.afterValues[expression] else : nil
419+ let (beforeValue, afterValue) = getStepValuePair (step, expression)
124420 if beforeValue.isNil and afterValue.isNil:
125421 continue
126422 let text = formatRenderText (expression, beforeValue, afterValue)
0 commit comments