Skip to content

Commit 94ac646

Browse files
committed
wip
1 parent b5b47b7 commit 94ac646

File tree

3 files changed

+430
-76
lines changed

3 files changed

+430
-76
lines changed

src/frontend/event_helpers.nim

Lines changed: 312 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ import
66
lib/[ jslib ]
77

88
const 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

1033
proc makeNotification*(kind: NotificationKind, text: cstring, isOperationStatus: bool = false): Notification =
1134
Notification(
@@ -68,7 +91,38 @@ proc successMessage*(api: MediatorWithSubscribers, text: cstring) =
6891
proc 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)

src/frontend/middleware.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,6 @@ when defined(ctInExtension):
249249
{.emit: "module.exports.getCurrentTrace = getCurrentTrace".}
250250
{.emit: "module.exports.getFlowList = getFlowList".}
251251
{.emit: "module.exports.computeRenderValueGroups = computeRenderValueGroups".}
252+
{.emit: "module.exports.computeFlowLineValues = computeFlowLineValues".}
253+
{.emit: "module.exports.computeFlowLoopSliders = computeFlowLoopSliders".}
254+
{.emit: "module.exports.computeFlowInsetData = computeFlowInsetData".}

0 commit comments

Comments
 (0)