mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 21:14:28 -08:00
Collect console log and allow time-travel to log invocation
This commit is contained in:
@@ -24,11 +24,15 @@ export class CallTree {
|
||||
e.preventDefault()
|
||||
|
||||
if(e.key == 'F1') {
|
||||
this.ui.editor.focus()
|
||||
this.ui.editor.focus_value_explorer(this.container)
|
||||
}
|
||||
|
||||
if(e.key == 'F2') {
|
||||
this.ui.editor.focus_value_explorer(this.container)
|
||||
this.ui.editor.focus()
|
||||
}
|
||||
|
||||
if(e.key == 'F3') {
|
||||
this.ui.set_active_tab('logs')
|
||||
}
|
||||
|
||||
if(e.key == 'a') {
|
||||
@@ -80,12 +84,12 @@ export class CallTree {
|
||||
this.state = null
|
||||
}
|
||||
|
||||
render_node(n, current_node){
|
||||
render_node(n){
|
||||
const is_expanded = this.state.calltree_node_is_expanded[n.id]
|
||||
|
||||
const result = el('div', 'callnode',
|
||||
el('div', {
|
||||
'class': (n == current_node ? 'call_el active' : 'call_el'),
|
||||
'class': 'call_el',
|
||||
click: () => this.on_click_node(n.id),
|
||||
},
|
||||
!is_expandable(n)
|
||||
@@ -104,9 +108,7 @@ export class CallTree {
|
||||
+ (n.fn.__location == null ? ' native' : '')
|
||||
,
|
||||
// TODO show `this` argument
|
||||
n.fn.__location == null
|
||||
? fn_link(n.fn)
|
||||
: n.fn.name
|
||||
n.fn.name
|
||||
,
|
||||
'(' ,
|
||||
...join(
|
||||
@@ -123,7 +125,7 @@ export class CallTree {
|
||||
),
|
||||
(n.children == null || !is_expanded)
|
||||
? null
|
||||
: n.children.map(c => this.render_node(c, current_node))
|
||||
: n.children.map(c => this.render_node(c))
|
||||
)
|
||||
|
||||
this.node_to_el.set(n.id, result)
|
||||
@@ -142,8 +144,10 @@ export class CallTree {
|
||||
}
|
||||
}
|
||||
|
||||
render_select_node(state) {
|
||||
this.render_active(this.state.current_calltree_node, false)
|
||||
render_select_node(prev, state) {
|
||||
if(prev != null) {
|
||||
this.render_active(prev.current_calltree_node, false)
|
||||
}
|
||||
this.state = state
|
||||
this.render_active(this.state.current_calltree_node, true)
|
||||
scrollIntoViewIfNeeded(
|
||||
@@ -152,12 +156,38 @@ export class CallTree {
|
||||
)
|
||||
}
|
||||
|
||||
render_expand_node(state) {
|
||||
render_expand_node(prev_state, state) {
|
||||
this.state = state
|
||||
const current_node = this.state.current_calltree_node
|
||||
const prev_dom_node = this.node_to_el.get(current_node.id)
|
||||
const next = this.render_node(current_node, current_node)
|
||||
prev_dom_node.parentNode.replaceChild(next, prev_dom_node)
|
||||
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),
|
||||
)
|
||||
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],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO on hover highlight line where function defined/
|
||||
@@ -166,8 +196,7 @@ export class CallTree {
|
||||
this.clear_calltree()
|
||||
this.state = state
|
||||
const root = root_calltree_node(this.state)
|
||||
const current_node = state.current_calltree_node
|
||||
this.container.appendChild(this.render_node(root, current_node))
|
||||
this.render_select_node(state, root, current_node)
|
||||
this.container.appendChild(this.render_node(root))
|
||||
this.render_select_node(null, state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ export class Editor {
|
||||
const VimApi = require("ace/keyboard/vim").CodeMirror.Vim
|
||||
|
||||
|
||||
this.ace_editor.commands.bindKey("F1", "switch_window");
|
||||
this.ace_editor.commands.bindKey("F2", "switch_window");
|
||||
VimApi._mapCommand({
|
||||
keys: '<C-w>',
|
||||
type: 'action',
|
||||
@@ -302,12 +302,20 @@ export class Editor {
|
||||
this.ace_editor.commands.addCommand({
|
||||
name: 'switch_window',
|
||||
exec: (editor) => {
|
||||
this.ui.calltree_container.focus()
|
||||
this.ui.set_active_tab('calltree')
|
||||
}
|
||||
})
|
||||
|
||||
this.ace_editor.commands.bindKey("F3", "focus_logs");
|
||||
this.ace_editor.commands.addCommand({
|
||||
name: 'focus_logs',
|
||||
exec: (editor) => {
|
||||
this.ui.set_active_tab('logs')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
this.ace_editor.commands.bindKey("F3", "goto_definition");
|
||||
this.ace_editor.commands.bindKey("F4", "goto_definition");
|
||||
VimApi._mapCommand({
|
||||
keys: 'gd',
|
||||
type: 'action',
|
||||
@@ -322,7 +330,7 @@ export class Editor {
|
||||
})
|
||||
|
||||
|
||||
this.ace_editor.commands.bindKey("F2", "focus_value_explorer");
|
||||
this.ace_editor.commands.bindKey("F1", "focus_value_explorer");
|
||||
this.ace_editor.commands.addCommand({
|
||||
name: 'focus_value_explorer',
|
||||
exec: (editor) => {
|
||||
|
||||
87
src/editor/logs.js
Normal file
87
src/editor/logs.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import {el, scrollIntoViewIfNeeded} from './domutils.js'
|
||||
import {exec} from '../index.js'
|
||||
import {header} from './value_explorer.js'
|
||||
|
||||
export class Logs {
|
||||
constructor(ui, el) {
|
||||
this.el = el
|
||||
this.ui = ui
|
||||
this.el.addEventListener('keydown', (e) => {
|
||||
|
||||
if(e.key == 'Enter') {
|
||||
// TODO reselect call node that was selected previously by calling
|
||||
// 'calltree.navigate_logs_position'
|
||||
this.ui.editor.focus()
|
||||
}
|
||||
|
||||
if(e.key == 'F1') {
|
||||
this.ui.editor.focus_value_explorer(this.el)
|
||||
}
|
||||
|
||||
if(e.key == 'F2') {
|
||||
this.ui.set_active_tab('calltree')
|
||||
}
|
||||
|
||||
if(e.key == 'F3') {
|
||||
this.ui.editor.focus()
|
||||
}
|
||||
|
||||
if(e.key == 'ArrowDown' || e.key == 'j'){
|
||||
exec('calltree.navigate_logs_increment', 1)
|
||||
}
|
||||
|
||||
if(e.key == 'ArrowUp' || e.key == 'k'){
|
||||
exec('calltree.navigate_logs_increment', -1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render_logs(prev_logs, logs) {
|
||||
|
||||
if(prev_logs?.logs != logs.logs) {
|
||||
|
||||
this.el.innerHTML = ''
|
||||
for(let i = 0; i < logs.logs.length; i++) {
|
||||
const log = logs.logs[i]
|
||||
this.el.appendChild(
|
||||
el('div',
|
||||
'log call_header '
|
||||
+ (log.log_fn_name == 'error' ? 'error' : '')
|
||||
// Currently console.log calls from native fns (like Array::map)
|
||||
// are not recorded, so next line is dead code
|
||||
+ (log.module == null ? ' native' : '')
|
||||
,
|
||||
el('a', {
|
||||
href: 'javascript: void(0)',
|
||||
click: () => exec('calltree.navigate_logs_position', i),
|
||||
},
|
||||
(log.module == '' ? '*scratch*' : log.module)
|
||||
+ ': '
|
||||
+ (
|
||||
log.toplevel
|
||||
? 'toplevel'
|
||||
: 'fn ' + (log.parent_name == '' ? 'anonymous' : log.parent_name)
|
||||
)
|
||||
+ ':'
|
||||
),
|
||||
' ',
|
||||
log.args.map(a => header(a)).join(', ')
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(prev_logs?.log_position != logs.log_position) {
|
||||
if(prev_logs?.log_position != null) {
|
||||
this.el.children[prev_logs.log_position].classList.remove('active')
|
||||
}
|
||||
if(logs.log_position != null) {
|
||||
const active_child = this.el.children[logs.log_position]
|
||||
active_child.classList.add('active')
|
||||
scrollIntoViewIfNeeded(this.el, active_child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import {exec, get_state} from '../index.js'
|
||||
import {Editor} from './editor.js'
|
||||
import {Files} from './files.js'
|
||||
import {CallTree} from './calltree.js'
|
||||
import {Logs} from './logs.js'
|
||||
import {Eval} from './eval.js'
|
||||
import {el} from './domutils.js'
|
||||
import {FLAGS} from '../feature_flags.js'
|
||||
@@ -12,6 +13,9 @@ export class UI {
|
||||
|
||||
this.files = new Files(this)
|
||||
|
||||
this.tabs = {}
|
||||
this.debugger = {}
|
||||
|
||||
container.appendChild(
|
||||
(this.root = el('div',
|
||||
'root ' + (FLAGS.embed_value_explorer ? 'embed_value_explorer' : ''),
|
||||
@@ -20,9 +24,32 @@ export class UI {
|
||||
? null
|
||||
: (this.eval_container = el('div', {class: 'eval'})),
|
||||
el('div', 'bottom',
|
||||
this.calltree_container = el('div', {"class": 'calltree', tabindex: 0}),
|
||||
this.debugger_container = el('div', 'debugger',
|
||||
el('div', 'tabs',
|
||||
this.tabs.calltree = el('div', 'tab',
|
||||
el('a', {
|
||||
click: () => this.set_active_tab('calltree'),
|
||||
href: 'javascript: void(0)',
|
||||
}, 'Call tree (F2)')
|
||||
),
|
||||
this.tabs.logs = el('div', 'tab',
|
||||
el('a', {
|
||||
click: () => this.set_active_tab('logs'),
|
||||
href: 'javascript: void(0)',
|
||||
}, 'Logs (F3)')
|
||||
),
|
||||
this.entrypoint_select = el('div', 'entrypoint_select')
|
||||
),
|
||||
this.debugger.calltree = el('div', {
|
||||
'class': 'tab_content',
|
||||
tabindex: 0,
|
||||
}),
|
||||
this.debugger.logs = el('div', {
|
||||
'class': 'tab_content logs',
|
||||
tabindex: 0,
|
||||
}),
|
||||
),
|
||||
this.problems_container = el('div', {"class": 'problems', tabindex: 0}),
|
||||
this.entrypoint_select = el('div', 'entrypoint_select')
|
||||
),
|
||||
|
||||
this.files.el,
|
||||
@@ -88,17 +115,21 @@ export class UI {
|
||||
this.root.addEventListener('click', () => this.clear_status(), true)
|
||||
|
||||
this.editor_container.addEventListener('keydown', e => {
|
||||
// Bind F2 and F3 for embed_value_explorer
|
||||
if(
|
||||
e.key.toLowerCase() == 'w' && e.ctrlKey == true
|
||||
||
|
||||
// We bind F1 later, this one to work from embed_value_explorer
|
||||
e.key == 'F1'
|
||||
e.key == 'F2'
|
||||
){
|
||||
this.calltree_container.focus()
|
||||
this.set_active_tab('calltree')
|
||||
}
|
||||
|
||||
if(e.key == 'F3'){
|
||||
this.set_active_tab('logs')
|
||||
}
|
||||
})
|
||||
|
||||
this.calltree_container.addEventListener('keydown', e => {
|
||||
const escape = e => {
|
||||
if(
|
||||
(e.key.toLowerCase() == 'w' && e.ctrlKey == true)
|
||||
||
|
||||
@@ -106,7 +137,10 @@ export class UI {
|
||||
){
|
||||
this.editor.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.debugger.calltree.addEventListener('keydown', escape)
|
||||
this.debugger.logs.addEventListener('keydown', escape)
|
||||
|
||||
|
||||
if(!FLAGS.embed_value_explorer) {
|
||||
@@ -122,7 +156,8 @@ export class UI {
|
||||
|
||||
this.editor = new Editor(this, this.editor_container)
|
||||
|
||||
this.calltree = new CallTree(this, this.calltree_container)
|
||||
this.calltree = new CallTree(this, this.debugger.calltree)
|
||||
this.logs = new Logs(this, this.debugger.logs)
|
||||
|
||||
// TODO jump to another module
|
||||
// TODO use exec
|
||||
@@ -137,10 +172,22 @@ export class UI {
|
||||
|
||||
// TODO when click in calltree, do not jump to location, navigateCallTree
|
||||
// instead
|
||||
this.calltree_container.addEventListener('click', jump_to_fn_location)
|
||||
this.debugger.calltree.addEventListener('click', jump_to_fn_location)
|
||||
|
||||
this.render_entrypoint_select(state)
|
||||
this.render_current_module(state.current_module)
|
||||
|
||||
this.set_active_tab('calltree', true)
|
||||
}
|
||||
|
||||
set_active_tab(tab_id, skip_focus = false) {
|
||||
Object.values(this.tabs).forEach(el => el.classList.remove('active'))
|
||||
this.tabs[tab_id].classList.add('active')
|
||||
Object.values(this.debugger).forEach(el => el.style.display = 'none')
|
||||
this.debugger[tab_id].style.display = 'block'
|
||||
if(!skip_focus) {
|
||||
this.debugger[tab_id].focus()
|
||||
}
|
||||
}
|
||||
|
||||
render_entrypoint_select(state) {
|
||||
@@ -172,14 +219,15 @@ export class UI {
|
||||
this.editor.focus()
|
||||
}
|
||||
|
||||
render_calltree(state) {
|
||||
this.calltree_container.style = ''
|
||||
render_debugger(state) {
|
||||
this.debugger_container.style = ''
|
||||
this.problems_container.style = 'display: none'
|
||||
this.calltree.render_calltree(state)
|
||||
this.logs.render_logs(null, state.logs)
|
||||
}
|
||||
|
||||
render_problems(problems) {
|
||||
this.calltree_container.style = 'display: none'
|
||||
this.debugger_container.style = 'display: none'
|
||||
this.problems_container.style = ''
|
||||
this.problems_container.innerHTML = ''
|
||||
problems.forEach(p => {
|
||||
@@ -220,15 +268,17 @@ export class UI {
|
||||
|
||||
render_help() {
|
||||
const options = [
|
||||
['Switch between editor and call tree', 'F1 or Ctrl-w'],
|
||||
['Go from call tree to editor', 'F1 or Esc'],
|
||||
['Focus value explorer', 'F2'],
|
||||
['Focus value explorer', 'F1'],
|
||||
['Navigate value explorer', '← → ↑ ↓ or hjkl'],
|
||||
['Leave value explorer', 'Esc'],
|
||||
['Jump to definition', 'F3', 'gd'],
|
||||
['Switch between editor and call tree view', 'F2 or Ctrl-w'],
|
||||
['Navigate call tree view', '← → ↑ ↓ or hjkl'],
|
||||
['Leave call tree view', 'F2 or Esc'],
|
||||
['Focus console logs', 'F3'],
|
||||
['Navigate console logs', '↑ ↓ or jk'],
|
||||
['Jump to definition', 'F4', 'gd'],
|
||||
['Expand selection to eval expression', 'Ctrl-↓ or Ctrl-j'],
|
||||
['Collapse selection', 'Ctrl-↑ or Ctrl-k'],
|
||||
['Navigate call tree view', '← → ↑ ↓ or hjkl'],
|
||||
['Step into call', 'Ctrl-i', '\\i'],
|
||||
['Step out of call', 'Ctrl-o', '\\o'],
|
||||
['When in call tree view, jump to return statement', 'Enter'],
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// TODO large arrays/objects
|
||||
// TODO maps, sets
|
||||
// TODO show Errors in red
|
||||
// TODO fns as clickable links (jump to definition), both for header and for
|
||||
// content
|
||||
|
||||
import {el, stringify, scrollIntoViewIfNeeded} from './domutils.js'
|
||||
|
||||
@@ -53,7 +55,7 @@ export const stringify_for_header = v => {
|
||||
}
|
||||
}
|
||||
|
||||
const header = object => {
|
||||
export const header = object => {
|
||||
if(typeof(object) == 'undefined') {
|
||||
return 'undefined'
|
||||
} else if(object == null) {
|
||||
|
||||
Reference in New Issue
Block a user