Files
leporello-js/src/editor/calltree.js

241 lines
6.5 KiB
JavaScript
Raw Normal View History

2022-09-10 02:48:13 +08:00
import {exec} from '../index.js'
import {el, stringify, fn_link, scrollIntoViewIfNeeded} from './domutils.js'
import {FLAGS} from '../feature_flags.js'
import {stringify_for_header} from './value_explorer.js'
import {find_node} from '../ast_utils.js'
2023-02-13 17:39:34 +08:00
import {is_expandable, root_calltree_node, get_deferred_calls, has_error}
from '../calltree.js'
2022-09-10 02:48:13 +08:00
// TODO perf - quadratic difficulty
const join = arr => arr.reduce(
(acc, el) => acc.length == 0
? [el]
: [...acc, ',', el],
[],
)
export class CallTree {
constructor(ui, container) {
this.ui = ui
this.container = container
this.container.addEventListener('keydown', (e) => {
// Do not scroll
e.preventDefault()
2022-10-25 04:43:35 +08:00
if(e.key == 'Escape') {
this.ui.editor.focus()
}
2022-09-10 02:48:13 +08:00
if(e.key == 'F1') {
this.ui.editor.focus_value_explorer(this.container)
2022-09-10 02:48:13 +08:00
}
if(e.key == 'F2') {
this.ui.editor.focus()
}
2022-09-10 02:48:13 +08:00
if(e.key == 'a') {
if(FLAGS.embed_value_explorer) {
exec('calltree.select_arguments')
} else {
// TODO make clear that arguments are shown
this.ui.eval.show_value(this.state.current_calltree_node.args)
this.ui.eval.focus_value_or_error(this.container)
}
}
if(e.key == 'r' || e.key == 'Enter') {
if(FLAGS.embed_value_explorer) {
exec('calltree.select_return_value')
} else {
// TODO make clear that return value is shown
this.ui.eval.show_value_or_error(this.state.current_calltree_node)
this.ui.eval.focus_value_or_error(this.container)
}
}
if(e.key == 'ArrowDown' || e.key == 'j'){
exec('calltree.arrow_down')
}
if(e.key == 'ArrowUp' || e.key == 'k'){
exec('calltree.arrow_up')
}
if(e.key == 'ArrowLeft' || e.key == 'h'){
exec('calltree.arrow_left')
}
if(e.key == 'ArrowRight' || e.key == 'l'){
exec('calltree.arrow_right')
}
})
}
on_click_node(id) {
exec('calltree.click', id)
}
clear_calltree(){
this.container.innerHTML = ''
this.node_to_el = new Map()
this.state = null
}
render_node(n){
2022-09-10 02:48:13 +08:00
const is_expanded = this.state.calltree_node_is_expanded[n.id]
const result = el('div', 'callnode',
el('div', {
'class': 'call_el',
2022-09-10 02:48:13 +08:00
click: () => this.on_click_node(n.id),
},
!is_expandable(n)
? '\xa0'
: is_expanded ? '▼' : '▶',
n.toplevel
? el('span', '',
el('i', '',
'toplevel: ' + (n.module == '' ? '*scratch*' : n.module),
),
2023-02-08 04:09:53 +08:00
n.ok ? '' : el('span', 'call_header error', '\xa0', stringify_for_header(n.error)),
2022-09-10 02:48:13 +08:00
)
: el('span',
'call_header '
2023-02-13 17:39:34 +08:00
+ (has_error(n) ? 'error' : '')
2022-09-10 02:48:13 +08:00
+ (n.fn.__location == null ? ' native' : '')
,
// TODO show `this` argument
2022-12-16 18:23:55 +08:00
(n.is_new ? 'new ' : ''),
n.fn.name,
2022-09-10 02:48:13 +08:00
'(' ,
...join(
n.args.map(
a => typeof(a) == 'function'
? fn_link(a)
: stringify_for_header(a)
)
),
')' ,
// TODO: show error message only where it was thrown, not every frame?
2023-02-08 04:09:53 +08:00
': ', (n.ok ? stringify_for_header(n.value) : stringify_for_header(n.error))
2022-09-10 02:48:13 +08:00
),
),
(n.children == null || !is_expanded)
? null
: n.children.map(c => this.render_node(c))
2022-09-10 02:48:13 +08:00
)
this.node_to_el.set(n.id, result)
result.is_expanded = is_expanded
return result
}
render_active(node, is_active) {
const dom = this.node_to_el.get(node.id).getElementsByClassName('call_el')[0]
if(is_active) {
dom.classList.add('active')
} else {
dom.classList.remove('active')
}
}
render_select_node(prev, state) {
if(prev != null) {
this.render_active(prev.current_calltree_node, false)
}
2022-09-10 02:48:13 +08:00
this.state = state
this.render_active(this.state.current_calltree_node, true)
scrollIntoViewIfNeeded(
this.container,
this.node_to_el.get(this.state.current_calltree_node.id).getElementsByClassName('call_el')[0]
)
}
render_expand_node(prev_state, state) {
2022-09-10 02:48:13 +08:00
this.state = state
this.do_render_expand_node(
prev_state.calltree_node_is_expanded,
state.calltree_node_is_expanded,
root_calltree_node(prev_state),
root_calltree_node(state),
)
2022-12-02 04:31:16 +08:00
const prev_deferred_calls = get_deferred_calls(prev_state)
const deferred_calls = get_deferred_calls(state)
2022-12-02 04:31:16 +08:00
if(prev_deferred_calls != null) {
// Expand already existing deferred calls
for(let i = 0; i < prev_deferred_calls.length; i++) {
this.do_render_expand_node(
prev_state.calltree_node_is_expanded,
state.calltree_node_is_expanded,
2022-12-02 04:31:16 +08:00
prev_deferred_calls[i],
deferred_calls[i],
)
}
2022-12-02 04:31:16 +08:00
// Add new deferred calls
for(let i = prev_deferred_calls.length; i < deferred_calls.length; i++) {
this.deferred_calls_root.appendChild(
this.render_node(deferred_calls[i])
)
}
}
this.render_select_node(prev_state, state)
}
do_render_expand_node(prev_exp, next_exp, prev_node, next_node) {
if(prev_node.id != next_node.id) {
throw new Error()
}
if(!!prev_exp[prev_node.id] != !!next_exp[next_node.id]) {
const prev_dom_node = this.node_to_el.get(prev_node.id)
const next = this.render_node(next_node)
prev_dom_node.parentNode.replaceChild(next, prev_dom_node)
} else {
if(prev_node.children == null) {
return
}
for(let i = 0; i < prev_node.children.length; i++) {
this.do_render_expand_node(
prev_exp,
next_exp,
prev_node.children[i],
next_node.children[i],
)
}
}
2022-09-10 02:48:13 +08:00
}
// TODO on hover highlight line where function defined
2022-09-10 02:48:13 +08:00
// TODO hover ?
render_calltree(state){
this.clear_calltree()
this.state = state
const root = root_calltree_node(this.state)
this.container.appendChild(this.render_node(root))
this.render_select_node(null, state)
2022-09-10 02:48:13 +08:00
}
2022-12-02 04:31:16 +08:00
render_deferred_calls(state) {
this.state = state
this.container.appendChild(
el('div', 'callnode',
el('div', 'call_el',
2022-12-02 04:31:16 +08:00
el('i', '', 'deferred calls'),
this.deferred_calls_root = el('div', 'callnode',
get_deferred_calls(state).map(call => this.render_node(call))
)
)
)
)
}
2022-09-10 02:48:13 +08:00
}