2022-11-28 20:53:35 +08:00
|
|
|
import {exec, get_state, open_run_window, reload_run_window} from '../index.js'
|
2022-09-10 02:48:13 +08:00
|
|
|
import {Editor} from './editor.js'
|
|
|
|
|
import {Files} from './files.js'
|
|
|
|
|
import {CallTree} from './calltree.js'
|
2022-10-17 02:49:21 +08:00
|
|
|
import {Logs} from './logs.js'
|
2023-02-13 17:39:34 +08:00
|
|
|
import {IO_Cache} from './io_cache.js'
|
2022-09-10 02:48:13 +08:00
|
|
|
import {Eval} from './eval.js'
|
|
|
|
|
import {el} from './domutils.js'
|
|
|
|
|
import {FLAGS} from '../feature_flags.js'
|
|
|
|
|
|
|
|
|
|
export class UI {
|
|
|
|
|
constructor(container, state){
|
|
|
|
|
this.change_entrypoint = this.change_entrypoint.bind(this)
|
2022-11-28 20:53:35 +08:00
|
|
|
this.change_html_file = this.change_html_file.bind(this)
|
|
|
|
|
this.open_run_window = this.open_run_window.bind(this)
|
2022-09-10 02:48:13 +08:00
|
|
|
|
|
|
|
|
this.files = new Files(this)
|
|
|
|
|
|
2022-10-17 02:49:21 +08:00
|
|
|
this.tabs = {}
|
|
|
|
|
this.debugger = {}
|
|
|
|
|
|
2022-09-10 02:48:13 +08:00
|
|
|
container.appendChild(
|
|
|
|
|
(this.root = el('div',
|
|
|
|
|
'root ' + (FLAGS.embed_value_explorer ? 'embed_value_explorer' : ''),
|
|
|
|
|
this.editor_container = el('div', 'editor_container'),
|
|
|
|
|
FLAGS.embed_value_explorer
|
|
|
|
|
? null
|
|
|
|
|
: (this.eval_container = el('div', {class: 'eval'})),
|
|
|
|
|
el('div', 'bottom',
|
2022-10-17 02:49:21 +08:00
|
|
|
this.debugger_container = el('div', 'debugger',
|
2023-01-15 22:00:49 +08:00
|
|
|
this.debugger_loaded = el('div', 'debugger_wrapper',
|
|
|
|
|
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)')
|
|
|
|
|
),
|
2023-02-13 17:39:34 +08:00
|
|
|
this.tabs.io_cache = el('div', 'tab',
|
|
|
|
|
el('a', {
|
|
|
|
|
click: () => this.set_active_tab('io_cache'),
|
|
|
|
|
href: 'javascript: void(0)',
|
|
|
|
|
}, 'IO cache (F4)')
|
|
|
|
|
),
|
2023-01-15 22:00:49 +08:00
|
|
|
this.entrypoint_select = el('div', 'entrypoint_select')
|
2022-10-17 02:49:21 +08:00
|
|
|
),
|
2023-01-15 22:00:49 +08:00
|
|
|
this.debugger.calltree = el('div', {
|
|
|
|
|
'class': 'tab_content',
|
|
|
|
|
tabindex: 0,
|
|
|
|
|
}),
|
|
|
|
|
this.debugger.logs = el('div', {
|
|
|
|
|
'class': 'tab_content logs',
|
|
|
|
|
tabindex: 0,
|
|
|
|
|
}),
|
2023-02-13 17:39:34 +08:00
|
|
|
this.debugger.io_cache = el('div', {
|
|
|
|
|
'class': 'tab_content io_cache',
|
|
|
|
|
tabindex: 0,
|
|
|
|
|
}),
|
2022-10-17 02:49:21 +08:00
|
|
|
),
|
2023-01-15 22:00:49 +08:00
|
|
|
this.debugger_loading = el('div', 'debugger_wrapper')
|
2022-10-17 02:49:21 +08:00
|
|
|
),
|
2022-09-10 02:48:13 +08:00
|
|
|
this.problems_container = el('div', {"class": 'problems', tabindex: 0}),
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
this.files.el,
|
|
|
|
|
|
|
|
|
|
this.statusbar = el('div', 'statusbar',
|
|
|
|
|
this.status = el('div', 'status'),
|
|
|
|
|
this.current_module = el('div', 'current_module'),
|
|
|
|
|
/*
|
|
|
|
|
// Fullscreen cancelled on escape, TODO
|
|
|
|
|
el('a', {
|
|
|
|
|
"class" : 'request_fullscreen',
|
|
|
|
|
href: 'javascript:void(0)',
|
|
|
|
|
click: e => document.body.requestFullscreen(),
|
|
|
|
|
},
|
|
|
|
|
'Fullscreen'
|
|
|
|
|
),
|
|
|
|
|
*/
|
2022-10-26 01:05:52 +08:00
|
|
|
|
|
|
|
|
el('a', {
|
2023-02-13 17:39:34 +08:00
|
|
|
'class': 'statusbar_action first',
|
|
|
|
|
href: 'javascript: void(0)',
|
|
|
|
|
click: () => exec('clear_io_cache')
|
|
|
|
|
},
|
|
|
|
|
'Clear IO cache (F6)'
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
el('a', {
|
|
|
|
|
'class': 'statusbar_action',
|
2022-10-26 01:05:52 +08:00
|
|
|
href: 'javascript: void(0)',
|
2022-11-28 20:53:35 +08:00
|
|
|
click: this.open_run_window,
|
2022-10-26 01:05:52 +08:00
|
|
|
},
|
2023-02-13 17:39:34 +08:00
|
|
|
'(Re)open run window (F7)'
|
2022-10-26 01:05:52 +08:00
|
|
|
),
|
|
|
|
|
|
2022-09-10 02:48:13 +08:00
|
|
|
this.options = el('div', 'options',
|
|
|
|
|
el('label', {'for': 'standard'},
|
|
|
|
|
el('input', {
|
|
|
|
|
id: 'standard',
|
|
|
|
|
type: 'radio',
|
|
|
|
|
name: 'keyboard',
|
|
|
|
|
checked: localStorage.keyboard == 'standard'
|
|
|
|
|
|| localStorage.keyboard == null,
|
|
|
|
|
change: () => {
|
|
|
|
|
this.editor.set_keyboard_handler('standard')
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
'Standard'
|
|
|
|
|
),
|
|
|
|
|
el('label', {'for': 'vim'},
|
|
|
|
|
el('input', {
|
|
|
|
|
id: 'vim',
|
|
|
|
|
type: 'radio',
|
|
|
|
|
name: 'keyboard',
|
|
|
|
|
checked: localStorage.keyboard == 'vim',
|
|
|
|
|
change: () => {
|
|
|
|
|
this.editor.set_keyboard_handler('vim')
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
'VIM'
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
el('a', {
|
|
|
|
|
'class': 'show_help',
|
|
|
|
|
href: 'javascript: void(0)',
|
|
|
|
|
click: () => this.help_dialog.showModal(),
|
|
|
|
|
},
|
|
|
|
|
'Help',
|
|
|
|
|
),
|
|
|
|
|
el('a', {
|
|
|
|
|
'class': 'github',
|
|
|
|
|
href: 'https://github.com/leporello-js/leporello-js',
|
|
|
|
|
target: '__blank',
|
|
|
|
|
}, 'Github'),
|
|
|
|
|
this.help_dialog = this.render_help(),
|
|
|
|
|
)
|
|
|
|
|
))
|
|
|
|
|
)
|
|
|
|
|
|
2022-10-26 00:39:22 +08:00
|
|
|
window.addEventListener('keydown', () => this.clear_status(), true)
|
|
|
|
|
window.addEventListener('click', () => this.clear_status(), true)
|
2022-09-10 02:48:13 +08:00
|
|
|
|
2022-10-26 00:39:22 +08:00
|
|
|
window.addEventListener('keydown', e => {
|
2022-10-25 04:43:35 +08:00
|
|
|
if(e.key == 'F2') {
|
2022-10-17 02:49:21 +08:00
|
|
|
this.set_active_tab('calltree')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(e.key == 'F3'){
|
|
|
|
|
this.set_active_tab('logs')
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
2023-02-13 17:39:34 +08:00
|
|
|
if(e.key == 'F4'){
|
|
|
|
|
this.set_active_tab('io_cache')
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
2022-10-26 01:05:52 +08:00
|
|
|
|
|
|
|
|
if(e.key == 'F6'){
|
2023-02-13 17:39:34 +08:00
|
|
|
exec('clear_io_cache')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(e.key == 'F7'){
|
2022-11-28 20:53:35 +08:00
|
|
|
this.open_run_window()
|
2022-10-26 01:05:52 +08:00
|
|
|
}
|
2023-02-13 17:39:34 +08:00
|
|
|
|
|
|
|
|
if(e.key == 'F8'){
|
|
|
|
|
this.fullscreen_editor()
|
|
|
|
|
}
|
2022-10-25 04:43:35 +08:00
|
|
|
})
|
2022-09-10 02:48:13 +08:00
|
|
|
|
|
|
|
|
if(!FLAGS.embed_value_explorer) {
|
|
|
|
|
this.eval = new Eval(this, this.eval_container)
|
|
|
|
|
} else {
|
|
|
|
|
// Stub
|
|
|
|
|
this.eval = {
|
|
|
|
|
show_value_or_error(){},
|
|
|
|
|
clear_value_or_error(){},
|
|
|
|
|
focus_value_or_error(){},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.editor = new Editor(this, this.editor_container)
|
|
|
|
|
|
2022-10-17 02:49:21 +08:00
|
|
|
this.calltree = new CallTree(this, this.debugger.calltree)
|
|
|
|
|
this.logs = new Logs(this, this.debugger.logs)
|
2023-02-13 17:39:34 +08:00
|
|
|
this.io_cache = new IO_Cache(this, this.debugger.io_cache)
|
2022-09-10 02:48:13 +08:00
|
|
|
|
|
|
|
|
// TODO jump to another module
|
|
|
|
|
// TODO use exec
|
|
|
|
|
const jump_to_fn_location = (e) => {
|
|
|
|
|
let loc
|
|
|
|
|
if((loc = e.target.dataset.location) != null){
|
|
|
|
|
loc = JSON.parse(loc)
|
2022-12-03 03:18:54 +08:00
|
|
|
this.editor.set_cursor_position(loc.index)
|
2022-09-10 02:48:13 +08:00
|
|
|
this.editor.focus()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO when click in calltree, do not jump to location, navigateCallTree
|
|
|
|
|
// instead
|
2022-10-17 02:49:21 +08:00
|
|
|
this.debugger.calltree.addEventListener('click', jump_to_fn_location)
|
2022-09-10 02:48:13 +08:00
|
|
|
|
|
|
|
|
this.render_entrypoint_select(state)
|
|
|
|
|
this.render_current_module(state.current_module)
|
2022-10-17 02:49:21 +08:00
|
|
|
|
|
|
|
|
this.set_active_tab('calltree', true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_active_tab(tab_id, skip_focus = false) {
|
2023-02-13 17:39:34 +08:00
|
|
|
this.active_tab = tab_id
|
2022-10-17 02:49:21 +08:00
|
|
|
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()
|
|
|
|
|
}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render_entrypoint_select(state) {
|
|
|
|
|
this.entrypoint_select.replaceChildren(
|
2022-11-28 20:53:35 +08:00
|
|
|
el('span', 'entrypoint_title', 'js entrypoint'),
|
2022-09-10 02:48:13 +08:00
|
|
|
el('select', {
|
|
|
|
|
click: e => e.stopPropagation(),
|
|
|
|
|
change: this.change_entrypoint,
|
|
|
|
|
},
|
2022-11-24 16:52:59 +08:00
|
|
|
Object
|
|
|
|
|
.keys(state.files)
|
|
|
|
|
.sort()
|
|
|
|
|
.filter(f => f == '' || f.endsWith('.js') || f.endsWith('.mjs'))
|
|
|
|
|
.map(f =>
|
|
|
|
|
el('option',
|
|
|
|
|
state.entrypoint == f
|
|
|
|
|
? { value: f, selected: true }
|
|
|
|
|
: { value: f},
|
|
|
|
|
f == '' ? "*scratch*" : f
|
|
|
|
|
)
|
2022-09-10 02:48:13 +08:00
|
|
|
)
|
2022-11-28 20:53:35 +08:00
|
|
|
),
|
|
|
|
|
el('span', 'entrypoint_title', 'html page'),
|
|
|
|
|
el('select', {
|
|
|
|
|
click: e => e.stopPropagation(),
|
|
|
|
|
change: this.change_html_file,
|
|
|
|
|
},
|
|
|
|
|
['']
|
|
|
|
|
.concat(
|
|
|
|
|
Object
|
|
|
|
|
.keys(state.files)
|
|
|
|
|
.sort()
|
|
|
|
|
.filter(f => f.endsWith('.htm') || f.endsWith('.html'))
|
|
|
|
|
)
|
|
|
|
|
.map(f =>
|
|
|
|
|
el('option',
|
|
|
|
|
state.html_file == f
|
|
|
|
|
? { value: f, selected: true }
|
|
|
|
|
: { value: f},
|
2022-11-28 23:12:55 +08:00
|
|
|
f == '' ? 'about:blank' : f
|
2022-11-28 20:53:35 +08:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
),
|
2022-09-10 02:48:13 +08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 20:53:35 +08:00
|
|
|
open_run_window() {
|
|
|
|
|
open_run_window(get_state())
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-10 02:48:13 +08:00
|
|
|
change_entrypoint(e) {
|
|
|
|
|
const file = e.target.value
|
2022-12-03 03:17:01 +08:00
|
|
|
exec('change_entrypoint', file)
|
2022-09-10 02:48:13 +08:00
|
|
|
this.editor.focus()
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 20:53:35 +08:00
|
|
|
change_html_file(e) {
|
|
|
|
|
const html_file = e.target.value
|
|
|
|
|
exec('change_html_file', html_file)
|
|
|
|
|
reload_run_window(get_state())
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 11:14:10 +08:00
|
|
|
render_debugger_loading(state) {
|
2022-10-17 02:49:21 +08:00
|
|
|
this.debugger_container.style = ''
|
2022-09-10 02:48:13 +08:00
|
|
|
this.problems_container.style = 'display: none'
|
2023-01-17 11:14:10 +08:00
|
|
|
|
|
|
|
|
this.debugger_loaded.style = 'display: none'
|
|
|
|
|
this.debugger_loading.style = ''
|
|
|
|
|
|
|
|
|
|
this.debugger_loading.innerText =
|
2023-01-15 22:00:49 +08:00
|
|
|
state.loading_external_imports_state != null
|
2023-01-17 11:14:10 +08:00
|
|
|
? 'Loading external modules...'
|
|
|
|
|
: 'Waiting...'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render_debugger(state) {
|
|
|
|
|
this.debugger_container.style = ''
|
|
|
|
|
this.problems_container.style = 'display: none'
|
|
|
|
|
|
|
|
|
|
this.debugger_loading.style = 'display: none'
|
|
|
|
|
this.debugger_loaded.style = ''
|
2023-02-13 17:39:34 +08:00
|
|
|
|
2023-01-17 11:14:10 +08:00
|
|
|
this.calltree.render_calltree(state)
|
|
|
|
|
this.logs.render_logs(null, state.logs)
|
2023-02-13 17:39:34 +08:00
|
|
|
|
|
|
|
|
// render lazily
|
|
|
|
|
// TODO
|
|
|
|
|
//if(this.active_tab == 'io_cache') {
|
|
|
|
|
this.io_cache.render_io_cache(state.io_cache)
|
|
|
|
|
//}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render_problems(problems) {
|
2022-10-17 02:49:21 +08:00
|
|
|
this.debugger_container.style = 'display: none'
|
2022-09-10 02:48:13 +08:00
|
|
|
this.problems_container.style = ''
|
|
|
|
|
this.problems_container.innerHTML = ''
|
|
|
|
|
problems.forEach(p => {
|
|
|
|
|
const s = this.editor.get_session(p.module)
|
|
|
|
|
const pos = s.doc.indexToPosition(p.index)
|
|
|
|
|
const module = p.module == '' ? "*scratch*" : p.module
|
|
|
|
|
this.problems_container.appendChild(
|
|
|
|
|
el('div', 'problem',
|
|
|
|
|
el('a', {
|
|
|
|
|
href: 'javascript:void(0)',
|
|
|
|
|
click: () => exec('goto_problem', p)
|
|
|
|
|
},
|
|
|
|
|
`${module}:${pos.row + 1}:${pos.column} - ${p.message}`
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_status(text){
|
|
|
|
|
this.current_module.style = 'display: none'
|
|
|
|
|
this.status.style = ''
|
|
|
|
|
this.status.innerText = text
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clear_status(){
|
|
|
|
|
this.render_current_module(get_state().current_module)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render_current_module(current_module) {
|
|
|
|
|
this.status.style = 'display: none'
|
|
|
|
|
this.current_module.innerText =
|
|
|
|
|
current_module == ''
|
|
|
|
|
? '*scratch*'
|
|
|
|
|
: current_module
|
|
|
|
|
this.current_module.style = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render_help() {
|
|
|
|
|
const options = [
|
2022-10-17 02:49:21 +08:00
|
|
|
['Focus value explorer', 'F1'],
|
2022-09-10 02:48:13 +08:00
|
|
|
['Navigate value explorer', '← → ↑ ↓ or hjkl'],
|
2022-10-25 04:43:35 +08:00
|
|
|
['Leave value explorer', 'F1 or Esc'],
|
|
|
|
|
['Focus call tree view', 'F2'],
|
2022-10-17 02:49:21 +08:00
|
|
|
['Navigate call tree view', '← → ↑ ↓ or hjkl'],
|
|
|
|
|
['Leave call tree view', 'F2 or Esc'],
|
|
|
|
|
['Focus console logs', 'F3'],
|
|
|
|
|
['Navigate console logs', '↑ ↓ or jk'],
|
2022-10-25 04:43:35 +08:00
|
|
|
['Leave console logs', 'F3 or Esc'],
|
2023-02-13 17:39:34 +08:00
|
|
|
['Focus IO cache', 'F4'],
|
|
|
|
|
['Leave IO cache', 'F4 or Esc'],
|
|
|
|
|
['Jump to definition', 'F5', 'gd'],
|
2022-09-10 02:48:13 +08:00
|
|
|
['Expand selection to eval expression', 'Ctrl-↓ or Ctrl-j'],
|
|
|
|
|
['Collapse selection', 'Ctrl-↑ or Ctrl-k'],
|
|
|
|
|
['Step into call', 'Ctrl-i', '\\i'],
|
|
|
|
|
['Step out of call', 'Ctrl-o', '\\o'],
|
|
|
|
|
['When in call tree view, jump to return statement', 'Enter'],
|
|
|
|
|
['When in call tree view, jump to function arguments', 'a'],
|
2023-02-13 17:39:34 +08:00
|
|
|
['Clear IO cache', 'F6'],
|
|
|
|
|
['(Re)open run window (F7)', 'F7'],
|
|
|
|
|
['Expand/collapse editor to fullscreen', 'F8'],
|
2022-09-10 02:48:13 +08:00
|
|
|
]
|
|
|
|
|
return el('dialog', 'help_dialog',
|
|
|
|
|
el('table', 'help',
|
|
|
|
|
el('thead', '',
|
|
|
|
|
el('th', '', 'Action'),
|
|
|
|
|
el('th', 'key', 'Standard'),
|
|
|
|
|
el('th', 'key', 'VIM'),
|
|
|
|
|
),
|
|
|
|
|
el('tbody', '',
|
|
|
|
|
options.map(([text, standard, vim]) =>
|
|
|
|
|
el('tr', '',
|
|
|
|
|
el('td', '', text),
|
|
|
|
|
el('td',
|
|
|
|
|
vim == null
|
|
|
|
|
? {'class': 'key spanned', colspan: 2}
|
|
|
|
|
: {'class': 'key'},
|
|
|
|
|
standard
|
|
|
|
|
),
|
|
|
|
|
vim == null
|
|
|
|
|
? null
|
|
|
|
|
: el('td', 'key', vim),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
el('form', {method: 'dialog'},
|
|
|
|
|
el('button', null, 'Close'),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-17 16:44:47 +08:00
|
|
|
fullscreen_editor() {
|
|
|
|
|
this.root.classList.toggle('fullscreen_editor')
|
|
|
|
|
this.editor.ace_editor.resize()
|
2022-10-25 04:43:35 +08:00
|
|
|
if(this.root.classList.contains('fullscreen_editor')) {
|
|
|
|
|
this.editor.focus()
|
|
|
|
|
}
|
2022-10-17 16:44:47 +08:00
|
|
|
}
|
|
|
|
|
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|