mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 21:14:28 -08:00
async calls WIP: render async calls and expand path
This commit is contained in:
@@ -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 expand_calltree_node = (state, node) => {
|
const replace_calltree_node_in_state = (state, node, replacement) => {
|
||||||
if(node.has_more_children) {
|
|
||||||
const root = root_calltree_module(state)
|
const root = root_calltree_module(state)
|
||||||
const next_node = state.calltree_actions.expand_calltree_node(node)
|
|
||||||
// Update calltree, replacing node with expanded node
|
// Update calltree, replacing node with expanded node
|
||||||
const {exports, calls} = state.calltree[root]
|
const {exports, calls} = state.calltree[root]
|
||||||
|
|
||||||
|
const replaced = replace_calltree_node(
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
calls,
|
||||||
|
{children: state.async_calls},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
node,
|
||||||
|
replacement
|
||||||
|
)
|
||||||
|
|
||||||
const calltree = {...state.calltree,
|
const calltree = {...state.calltree,
|
||||||
[root]: {
|
[root]: {
|
||||||
exports,
|
exports,
|
||||||
calls: replace_calltree_node(calls, node, next_node),
|
calls: replaced.children[0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {state: {...state, calltree}, node: next_node}
|
return {...state, calltree, async_calls: replaced.children[1].children}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expand_calltree_node = (state, node) => {
|
||||||
|
if(node.has_more_children) {
|
||||||
|
const next_node = state.calltree_actions.expand_calltree_node(node)
|
||||||
|
return {
|
||||||
|
state: replace_calltree_node_in_state(state, node, next_node),
|
||||||
|
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(
|
|
||||||
|
const [_parent] = path_to_root(
|
||||||
|
{
|
||||||
|
children: [
|
||||||
root_calltree_node(state),
|
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
|
||||||
|
|||||||
@@ -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
7
src/effects.js
vendored
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/index.js
18
src/index.js
@@ -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({
|
||||||
|
|||||||
@@ -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')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user