async calls WIP: render async calls and expand path

This commit is contained in:
Dmitry Vasilev
2022-11-08 16:22:45 +08:00
parent bcc9e7a382
commit b4bca92fca
5 changed files with 123 additions and 22 deletions

View File

@@ -18,6 +18,18 @@ export const do_pp_calltree = tree => ({
children: tree.children && tree.children.map(do_pp_calltree) children: tree.children && tree.children.map(do_pp_calltree)
}) })
const find_calltree_node = (state, id) => {
return find_node(
{
children: [
root_calltree_node(state),
{children: state.async_calls},
]
},
n => n.id == id
)
}
const is_stackoverflow = node => const is_stackoverflow = node =>
// Chrome // Chrome
node.error.message == 'Maximum call stack size exceeded' node.error.message == 'Maximum call stack size exceeded'
@@ -115,7 +127,6 @@ export const add_frame = (
return set_active_calltree_node(result, active_calltree_node, current_calltree_node) return set_active_calltree_node(result, active_calltree_node, current_calltree_node)
} }
const replace_calltree_node = (root, node, replacement) => { const replace_calltree_node = (root, node, replacement) => {
const do_replace = root => { const do_replace = root => {
if(root.id == node.id) { if(root.id == node.id) {
@@ -151,19 +162,39 @@ const replace_calltree_node = (root, node, replacement) => {
return result return result
} }
const replace_calltree_node_in_state = (state, node, replacement) => {
const root = root_calltree_module(state)
// Update calltree, replacing node with expanded node
const {exports, calls} = state.calltree[root]
const replaced = replace_calltree_node(
{
children: [
calls,
{children: state.async_calls},
]
},
node,
replacement
)
const calltree = {...state.calltree,
[root]: {
exports,
calls: replaced.children[0],
}
}
return {...state, calltree, async_calls: replaced.children[1].children}
}
const expand_calltree_node = (state, node) => { const expand_calltree_node = (state, node) => {
if(node.has_more_children) { if(node.has_more_children) {
const root = root_calltree_module(state)
const next_node = state.calltree_actions.expand_calltree_node(node) const next_node = state.calltree_actions.expand_calltree_node(node)
// Update calltree, replacing node with expanded node return {
const {exports, calls} = state.calltree[root] state: replace_calltree_node_in_state(state, node, next_node),
const calltree = {...state.calltree, node: next_node
[root]: {
exports,
calls: replace_calltree_node(calls, node, next_node),
}
} }
return {state: {...state, calltree}, node: next_node}
} else { } else {
return {state, node} return {state, node}
} }
@@ -187,11 +218,27 @@ const jump_calltree_node = (_state, _current_calltree_node) => {
/* Whether to show fn body (true) or callsite (false) */ /* Whether to show fn body (true) or callsite (false) */
let show_body let show_body
const [parent] = path_to_root(
root_calltree_node(state), const [_parent] = path_to_root(
{
children: [
root_calltree_node(state),
{children: state.async_calls},
]
},
current_calltree_node current_calltree_node
) )
if(current_calltree_node.toplevel) {
const parent = _parent.id == null
? _parent.children[0]
: _parent
if(
current_calltree_node.toplevel
||
/* async call */
parent == current_calltree_node
) {
show_body = true show_body = true
} else if(is_native_fn(current_calltree_node)) { } else if(is_native_fn(current_calltree_node)) {
show_body = false show_body = false
@@ -429,7 +476,7 @@ export const toggle_expanded = (state, is_exp) => {
} }
const click = (state, id) => { const click = (state, id) => {
const node = find_node(root_calltree_node(state), n => n.id == id) const node = find_calltree_node(state, id)
const {state: nextstate, effects} = jump_calltree_node(state, node) const {state: nextstate, effects} = jump_calltree_node(state, node)
if(is_expandable(node)) { if(is_expandable(node)) {
// `effects` are intentionally discarded, correct `set_caret_position` will // `effects` are intentionally discarded, correct `set_caret_position` will

View File

@@ -162,12 +162,32 @@ export class CallTree {
render_expand_node(prev_state, state) { render_expand_node(prev_state, state) {
this.state = state this.state = state
this.do_render_expand_node( this.do_render_expand_node(
prev_state.calltree_node_is_expanded, prev_state.calltree_node_is_expanded,
state.calltree_node_is_expanded, state.calltree_node_is_expanded,
root_calltree_node(prev_state), root_calltree_node(prev_state),
root_calltree_node(state), root_calltree_node(state),
) )
if(prev_state.async_calls != null) {
// Expand already existing async calls
for(let i = 0; i < prev_state.async_calls.length; i++) {
this.do_render_expand_node(
prev_state.calltree_node_is_expanded,
state.calltree_node_is_expanded,
prev_state.async_calls[i],
state.async_calls[i],
)
}
// Add new async calls
for(let i = prev_state.async_calls.length; i < state.async_calls.length; i++) {
this.async_calls_root.appendChild(
this.render_node(state.async_calls[i])
)
}
}
this.render_select_node(prev_state, state) this.render_select_node(prev_state, state)
} }
@@ -194,7 +214,7 @@ export class CallTree {
} }
} }
// TODO on hover highlight line where function defined/ // TODO on hover highlight line where function defined
// TODO hover ? // TODO hover ?
render_calltree(state){ render_calltree(state){
this.clear_calltree() this.clear_calltree()
@@ -203,4 +223,18 @@ export class CallTree {
this.container.appendChild(this.render_node(root)) this.container.appendChild(this.render_node(root))
this.render_select_node(null, state) this.render_select_node(null, state)
} }
render_async_calls(state) {
this.state = state
this.container.appendChild(
el('div', 'callnode',
el('div', 'call_el',
el('i', '', 'async calls'),
this.async_calls_root = el('div', 'callnode',
state.async_calls.map(call => this.render_node(call))
)
)
)
)
}
} }

7
src/effects.js vendored
View File

@@ -172,10 +172,17 @@ export const render_common_side_effects = (prev, next, command, ui) => {
render_coloring(ui, next) render_coloring(ui, next)
ui.editor.unembed_value_explorer() ui.editor.unembed_value_explorer()
} else { } else {
if(prev.async_calls == null && next.async_calls != null) {
ui.calltree.render_async_calls(next)
}
if( if(
prev.calltree != next.calltree prev.calltree != next.calltree
|| ||
prev.calltree_node_is_expanded != next.calltree_node_is_expanded prev.calltree_node_is_expanded != next.calltree_node_is_expanded
||
prev.async_calls != next.async_calls
) { ) {
ui.calltree.render_expand_node(prev, next) ui.calltree.render_expand_node(prev, next)
} }

View File

@@ -10,6 +10,15 @@ const EXAMPLE = `const fib = n =>
: fib(n - 1) + fib(n - 2) : fib(n - 1) + fib(n - 2)
fib(6)` fib(6)`
const set_error_handler = w => {
// TODO err.message
w.onerror = (msg, src, lineNum, colNum, err) => {
ui.set_status(msg)
}
w.addEventListener('unhandledrejection', (event) => {
ui.set_status(event.reason)
})
}
// By default run code in hidden iframe, until user explicitly opens visible // By default run code in hidden iframe, until user explicitly opens visible
// window // window
@@ -18,6 +27,7 @@ globalThis.run_window = (() => {
iframe.src = 'about:blank' iframe.src = 'about:blank'
iframe.setAttribute('hidden', '') iframe.setAttribute('hidden', '')
document.body.appendChild(iframe) document.body.appendChild(iframe)
set_error_handler(iframe.contentWindow)
return iframe.contentWindow return iframe.contentWindow
})() })()
@@ -56,13 +66,7 @@ let ui
let state let state
export const init = (container) => { export const init = (container) => {
// TODO err.message set_error_handler(window)
window.onerror = (msg, src, lineNum, colNum, err) => {
ui.set_status(msg)
}
window.addEventListener('unhandledrejection', (event) => {
ui.set_status(event.reason)
})
read_modules().then(initial_state => { read_modules().then(initial_state => {
state = get_initial_state({ state = get_initial_state({

View File

@@ -2302,7 +2302,12 @@ const y = x()`
test('async calls', () => { test('async calls', () => {
const code = ` const code = `
const fn = () => { const fn = () => {
fn2()
} }
const fn2 = () => {
}
// Use Function constructor to exec impure code for testing // Use Function constructor to exec impure code for testing
new Function('fn', 'globalThis.__run_async_call = fn')(fn) new Function('fn', 'globalThis.__run_async_call = fn')(fn)
` `
@@ -2327,6 +2332,10 @@ const y = x()`
assert_equal(call.args, [10]) assert_equal(call.args, [10])
const state = COMMANDS.on_async_call(i, call) const state = COMMANDS.on_async_call(i, call)
assert_equal(state.async_calls, [call]) assert_equal(state.async_calls, [call])
// Expand call
const {state: expanded} = COMMANDS.calltree.click(state, call.id)
assert_equal(expanded.async_calls[0].children[0].fn.name, 'fn2')
}), }),
] ]