2022-09-10 02:48:13 +08:00
|
|
|
import {exec, get_state} from '../index.js'
|
2023-02-08 04:09:53 +08:00
|
|
|
import {ValueExplorer, stringify_for_header} from './value_explorer.js'
|
2022-09-10 02:48:13 +08:00
|
|
|
import {el, stringify, fn_link} from './domutils.js'
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
normalize events 'change' and 'changeSelection':
|
|
|
|
|
- change is debounced
|
|
|
|
|
- changeSelection must not fire if 'change' is fired. So for every keystroke,
|
|
|
|
|
either change or changeSelection should be fired, not both
|
|
|
|
|
- changeSelection fired only once (ace fires it multiple times for single
|
|
|
|
|
keystroke)
|
|
|
|
|
*/
|
|
|
|
|
const normalize_events = (ace_editor, {
|
|
|
|
|
on_change,
|
|
|
|
|
on_change_selection,
|
|
|
|
|
is_change_selection_supressed,
|
|
|
|
|
on_change_immediate,
|
|
|
|
|
}) => {
|
|
|
|
|
const TIMEOUT = 1000
|
|
|
|
|
|
|
|
|
|
let state
|
|
|
|
|
|
|
|
|
|
const set_initial_state = () => {
|
|
|
|
|
state = {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_initial_state()
|
|
|
|
|
|
|
|
|
|
const flush = () => {
|
|
|
|
|
if(state.change_args != null) {
|
|
|
|
|
on_change(...state.change_args)
|
|
|
|
|
} else if(state.change_selection_args != null) {
|
|
|
|
|
on_change_selection(...state.change_selection_args)
|
|
|
|
|
}
|
|
|
|
|
set_initial_state()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ace_editor.on('change', (...args) => {
|
|
|
|
|
on_change_immediate()
|
|
|
|
|
|
|
|
|
|
if(state.tid != null) {
|
|
|
|
|
clearTimeout(state.tid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.change_args = args
|
|
|
|
|
|
|
|
|
|
state.tid = setTimeout(() => {
|
|
|
|
|
state.tid = null
|
|
|
|
|
flush()
|
|
|
|
|
}, TIMEOUT)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
ace_editor.on('changeSelection', (...args) => {
|
|
|
|
|
if(is_change_selection_supressed()) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if(state.tid != null) {
|
|
|
|
|
// flush is already by `change`, skip `changeSelection`
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
state.change_selection_args = args
|
|
|
|
|
if(!state.is_flush_set) {
|
|
|
|
|
state.is_flush_set = true
|
|
|
|
|
Promise.resolve().then(() => {
|
|
|
|
|
if(state.tid == null) {
|
|
|
|
|
flush()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class Editor {
|
|
|
|
|
|
|
|
|
|
constructor(ui, editor_container){
|
|
|
|
|
this.ui = ui
|
|
|
|
|
this.editor_container = editor_container
|
|
|
|
|
|
2023-07-04 20:24:42 +03:00
|
|
|
this.make_resizable()
|
|
|
|
|
|
2022-09-10 02:48:13 +08:00
|
|
|
this.markers = {}
|
|
|
|
|
this.sessions = {}
|
|
|
|
|
|
2023-02-07 22:25:05 +08:00
|
|
|
this.ace_editor = globalThis.ace.edit(this.editor_container)
|
2022-09-10 02:48:13 +08:00
|
|
|
|
|
|
|
|
this.ace_editor.setOptions({
|
|
|
|
|
behavioursEnabled: false,
|
|
|
|
|
// Scroll past end for value explorer
|
|
|
|
|
scrollPastEnd: 100 /* Allows to scroll 100*<screen size> */,
|
2023-06-26 15:12:49 +03:00
|
|
|
|
|
|
|
|
enableLiveAutocompletion: false,
|
|
|
|
|
enableBasicAutocompletion: true,
|
2022-09-10 02:48:13 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
normalize_events(this.ace_editor, {
|
|
|
|
|
on_change: () => {
|
|
|
|
|
try {
|
2022-12-03 03:18:54 +08:00
|
|
|
exec('input', this.ace_editor.getValue(), this.get_cursor_position())
|
2022-09-10 02:48:13 +08:00
|
|
|
} catch(e) {
|
|
|
|
|
// Do not throw Error to ACE because it breaks typing
|
|
|
|
|
console.error(e)
|
|
|
|
|
this.ui.set_status(e.message)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
on_change_immediate: () => {
|
2023-07-05 00:18:22 +03:00
|
|
|
this.unembed_value_explorer()
|
2022-09-10 02:48:13 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
on_change_selection: () => {
|
|
|
|
|
try {
|
|
|
|
|
if(!this.is_change_selection_supressed) {
|
2022-12-03 03:18:54 +08:00
|
|
|
exec('move_cursor', this.get_cursor_position())
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
} catch(e) {
|
|
|
|
|
// Do not throw Error to ACE because it breaks typing
|
|
|
|
|
console.error(e)
|
|
|
|
|
this.ui.set_status(e.message)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
is_change_selection_supressed: () => {
|
|
|
|
|
return this.is_change_selection_supressed
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.focus()
|
|
|
|
|
|
|
|
|
|
this.init_keyboard()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
focus() {
|
|
|
|
|
this.ace_editor.focus()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
supress_change_selection(action) {
|
|
|
|
|
try {
|
|
|
|
|
this.is_change_selection_supressed = true
|
|
|
|
|
action()
|
|
|
|
|
} finally {
|
|
|
|
|
this.is_change_selection_supressed = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensure_session(file, code) {
|
|
|
|
|
let session = this.sessions[file]
|
|
|
|
|
if(session == null) {
|
2023-02-07 22:25:05 +08:00
|
|
|
session = globalThis.ace.createEditSession(code)
|
2022-09-10 02:48:13 +08:00
|
|
|
this.sessions[file] = session
|
|
|
|
|
session.setUseWorker(false)
|
|
|
|
|
session.setOptions({
|
|
|
|
|
mode: "ace/mode/javascript",
|
|
|
|
|
tabSize: 2,
|
|
|
|
|
useSoftTabs: true,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return session
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get_session(file) {
|
|
|
|
|
return this.sessions[file]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch_session(file) {
|
|
|
|
|
// Supress selection change triggered by switching sessions
|
|
|
|
|
this.supress_change_selection(() => {
|
|
|
|
|
this.ace_editor.setSession(this.get_session(file))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unembed_value_explorer() {
|
|
|
|
|
if(this.widget != null) {
|
|
|
|
|
this.ace_editor.getSession().widgetManager.removeLineWidget(this.widget)
|
|
|
|
|
this.widget = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update_value_explorer_margin() {
|
|
|
|
|
if(this.widget != null) {
|
2023-07-05 00:18:22 +03:00
|
|
|
const session = this.ace_editor.getSession()
|
|
|
|
|
|
|
|
|
|
// Calculate left margin in such way that value explorer does not cover
|
|
|
|
|
// code. It has sufficient left margin so all visible code is to the left
|
|
|
|
|
// of it
|
|
|
|
|
const lines_count = session.getLength()
|
|
|
|
|
let margin = 0
|
|
|
|
|
for(
|
|
|
|
|
let i = this.widget.row;
|
2023-07-05 03:35:53 +03:00
|
|
|
i <= this.ace_editor.renderer.getLastVisibleRow();
|
2023-07-05 00:18:22 +03:00
|
|
|
i++
|
|
|
|
|
) {
|
|
|
|
|
margin = Math.max(margin, session.getLine(i).length)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Next line sets margin based on whole file
|
|
|
|
|
//const margin = this.ace_editor.getSession().getScreenWidth()
|
|
|
|
|
|
|
|
|
|
this.widget.content.style.marginLeft = (margin + 1) + 'ch'
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
embed_value_explorer({index, result: {ok, value, error}}) {
|
|
|
|
|
this.unembed_value_explorer()
|
|
|
|
|
|
|
|
|
|
const session = this.ace_editor.getSession()
|
|
|
|
|
const pos = session.doc.indexToPosition(index)
|
|
|
|
|
const row = pos.row
|
|
|
|
|
|
|
|
|
|
const line_height = this.ace_editor.renderer.lineHeight
|
|
|
|
|
|
|
|
|
|
let content
|
|
|
|
|
const container = el('div', {'class': 'embed_value_explorer_container'},
|
|
|
|
|
el('div', {'class': 'embed_value_explorer_wrapper'},
|
|
|
|
|
content = el('div', {
|
|
|
|
|
// Ace editor cannot render widget before the first line. So we
|
|
|
|
|
// render in on the next line and apply translate
|
|
|
|
|
'style': `transform: translate(0px, -${line_height}px)`,
|
|
|
|
|
'class': 'embed_value_explorer_content',
|
|
|
|
|
tabindex: 0
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let initial_scroll_top
|
|
|
|
|
|
|
|
|
|
const escape = () => {
|
|
|
|
|
if(initial_scroll_top != null) {
|
|
|
|
|
// restore scroll
|
|
|
|
|
session.setScrollTop(initial_scroll_top)
|
|
|
|
|
}
|
|
|
|
|
if(this.widget.return_to == null) {
|
|
|
|
|
this.focus()
|
|
|
|
|
} else {
|
|
|
|
|
this.widget.return_to.focus()
|
|
|
|
|
}
|
|
|
|
|
// TODO select root in value explorer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
container.addEventListener('keydown', e => {
|
|
|
|
|
if(e.key == 'Escape') {
|
|
|
|
|
escape()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if(ok) {
|
|
|
|
|
const exp = new ValueExplorer({
|
|
|
|
|
container: content,
|
|
|
|
|
event_target: container,
|
|
|
|
|
on_escape: escape,
|
|
|
|
|
scroll_to_element: t => {
|
|
|
|
|
if(initial_scroll_top == null) {
|
|
|
|
|
initial_scroll_top = session.getScrollTop()
|
|
|
|
|
}
|
|
|
|
|
let scroll
|
|
|
|
|
const out_of_bottom = t.getBoundingClientRect().bottom - this.editor_container.getBoundingClientRect().bottom
|
|
|
|
|
if(out_of_bottom > 0) {
|
|
|
|
|
session.setScrollTop(session.getScrollTop() + out_of_bottom)
|
|
|
|
|
}
|
|
|
|
|
const out_of_top = this.editor_container.getBoundingClientRect().top - t.getBoundingClientRect().top
|
|
|
|
|
if(out_of_top > 0) {
|
|
|
|
|
session.setScrollTop(session.getScrollTop() - out_of_top)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
exp.render(value)
|
|
|
|
|
} else {
|
2023-02-08 04:09:53 +08:00
|
|
|
content.appendChild(el('span', 'eval_error', stringify_for_header(error)))
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.widget = {
|
|
|
|
|
row,
|
|
|
|
|
fixedWidth: true,
|
|
|
|
|
el: container,
|
|
|
|
|
content,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const LineWidgets = require("ace/line_widgets").LineWidgets;
|
|
|
|
|
if (!session.widgetManager) {
|
|
|
|
|
session.widgetManager = new LineWidgets(session);
|
|
|
|
|
session.widgetManager.attach(this.ace_editor);
|
|
|
|
|
}
|
2023-07-05 03:35:53 +03:00
|
|
|
|
|
|
|
|
// update_value_explorer_margin relies on getLastVisibleRow which can be
|
|
|
|
|
// incorrect because it may be executed right after set_cursor_position
|
|
|
|
|
// which is async in ace_editor. Use setTimeout
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.update_value_explorer_margin()
|
|
|
|
|
session.widgetManager.addLineWidget(this.widget)
|
|
|
|
|
}, 0)
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
focus_value_explorer(return_to) {
|
2023-05-19 15:59:15 +03:00
|
|
|
if(this.widget != null) {
|
|
|
|
|
this.widget.return_to = return_to
|
|
|
|
|
this.widget.content.focus({preventScroll: true})
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set_keyboard_handler(type) {
|
|
|
|
|
if(type != null) {
|
|
|
|
|
localStorage.keyboard = type
|
|
|
|
|
}
|
|
|
|
|
this.ace_editor.setKeyboardHandler(
|
|
|
|
|
type == 'vim' ? "ace/keyboard/vim" : null
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init_keyboard(){
|
|
|
|
|
this.set_keyboard_handler(localStorage.keyboard)
|
|
|
|
|
|
|
|
|
|
const VimApi = require("ace/keyboard/vim").CodeMirror.Vim
|
|
|
|
|
|
2022-10-25 04:43:35 +08:00
|
|
|
// Remove commands binded to function keys that we are going to redefine
|
|
|
|
|
this.ace_editor.commands.removeCommand('openCommandPallete')
|
|
|
|
|
this.ace_editor.commands.removeCommand('toggleFoldWidget')
|
|
|
|
|
this.ace_editor.commands.removeCommand('goToNextError')
|
2022-09-10 02:48:13 +08:00
|
|
|
|
|
|
|
|
|
2023-02-13 17:39:34 +08:00
|
|
|
this.ace_editor.commands.bindKey("F5", "goto_definition");
|
2022-09-10 02:48:13 +08:00
|
|
|
VimApi._mapCommand({
|
|
|
|
|
keys: 'gd',
|
|
|
|
|
type: 'action',
|
|
|
|
|
action: 'aceCommand',
|
|
|
|
|
actionArgs: { name: "goto_definition" }
|
|
|
|
|
})
|
|
|
|
|
this.ace_editor.commands.addCommand({
|
|
|
|
|
name: 'goto_definition',
|
|
|
|
|
exec: (editor) => {
|
|
|
|
|
this.goto_definition()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
2022-10-17 02:49:21 +08:00
|
|
|
this.ace_editor.commands.bindKey("F1", "focus_value_explorer");
|
2022-09-10 02:48:13 +08:00
|
|
|
this.ace_editor.commands.addCommand({
|
|
|
|
|
name: 'focus_value_explorer',
|
|
|
|
|
exec: (editor) => {
|
|
|
|
|
this.focus_value_explorer()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.ace_editor.commands.bindKey("ctrl-i", 'step_into')
|
|
|
|
|
VimApi._mapCommand({
|
|
|
|
|
keys: '\\i',
|
|
|
|
|
type: 'action',
|
|
|
|
|
action: 'aceCommand',
|
|
|
|
|
actionArgs: { name: "step_into" }
|
|
|
|
|
})
|
|
|
|
|
this.ace_editor.commands.addCommand({
|
|
|
|
|
name: 'step_into',
|
|
|
|
|
exec: (editor) => {
|
2022-12-03 03:18:54 +08:00
|
|
|
exec('step_into', this.get_cursor_position())
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.ace_editor.commands.bindKey("ctrl-o", 'step_out')
|
|
|
|
|
VimApi._mapCommand({
|
|
|
|
|
keys: '\\o',
|
|
|
|
|
type: 'action',
|
|
|
|
|
action: 'aceCommand',
|
|
|
|
|
actionArgs: { name: "step_out" }
|
|
|
|
|
})
|
|
|
|
|
this.ace_editor.commands.addCommand({
|
|
|
|
|
name: 'step_out',
|
|
|
|
|
exec: (editor) => {
|
|
|
|
|
exec('calltree.arrow_left')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.ace_editor.commands.addCommand({
|
|
|
|
|
name: 'expand_selection',
|
|
|
|
|
exec: () => {
|
2022-12-03 03:18:54 +08:00
|
|
|
exec('eval_selection', this.get_cursor_position(), true)
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.ace_editor.commands.addCommand({
|
|
|
|
|
name: 'collapse_selection',
|
|
|
|
|
exec: () => {
|
2022-12-03 03:18:54 +08:00
|
|
|
exec('eval_selection', this.get_cursor_position(), false)
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.ace_editor.commands.bindKey("ctrl-j", 'expand_selection')
|
|
|
|
|
this.ace_editor.commands.bindKey("ctrl-down", 'expand_selection')
|
|
|
|
|
this.ace_editor.commands.bindKey("ctrl-k", 'collapse_selection')
|
|
|
|
|
this.ace_editor.commands.bindKey("ctrl-up", 'collapse_selection')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.ace_editor.commands.addCommand({
|
|
|
|
|
name: 'edit',
|
|
|
|
|
exec: (editor, input) => {
|
|
|
|
|
const module = input.args == null ? '' : input.args[0]
|
|
|
|
|
exec('change_current_module', module)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
VimApi.defineEx("edit", "e", function(cm, input) {
|
|
|
|
|
cm.ace.execCommand("edit", input)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// TODO remove my custom binding
|
|
|
|
|
VimApi.map('jj', '<Esc>', 'insert')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add_marker(file, className, from, to){
|
|
|
|
|
const session = this.get_session(file)
|
|
|
|
|
const from_pos = session.doc.indexToPosition(from)
|
|
|
|
|
const to_pos = session.doc.indexToPosition(to)
|
|
|
|
|
const markerId = session.addMarker(
|
2023-02-07 22:25:05 +08:00
|
|
|
new globalThis.ace.Range(from_pos.row,from_pos.column,to_pos.row,to_pos.column),
|
2022-09-10 02:48:13 +08:00
|
|
|
className
|
|
|
|
|
)
|
|
|
|
|
if(this.markers[file] == null){
|
|
|
|
|
this.markers[file] = []
|
|
|
|
|
}
|
|
|
|
|
this.markers[file].push({className, from, to, markerId})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remove_markers_of_type(file, type){
|
|
|
|
|
if(this.markers[file] == null){
|
|
|
|
|
this.markers[file] = []
|
|
|
|
|
}
|
|
|
|
|
const for_removal = this.markers[file].filter(h => h.className == type)
|
|
|
|
|
const session = this.get_session(file)
|
|
|
|
|
for(let marker of for_removal){
|
|
|
|
|
session.removeMarker(marker.markerId)
|
|
|
|
|
}
|
|
|
|
|
this.markers[file] = this.markers[file].filter(h => h.className != type)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-12-03 03:18:54 +08:00
|
|
|
get_cursor_position(file){
|
2022-09-10 02:48:13 +08:00
|
|
|
const session = file == null
|
|
|
|
|
? this.ace_editor.getSession()
|
|
|
|
|
: this.get_session(file)
|
|
|
|
|
|
|
|
|
|
if(session == null) {
|
2022-12-03 03:17:01 +08:00
|
|
|
// Session was not created for file
|
|
|
|
|
throw new Error('illegal state')
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return session.doc.positionToIndex(session.selection.getCursor())
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-03 03:18:54 +08:00
|
|
|
set_cursor_position(index){
|
2022-09-10 02:48:13 +08:00
|
|
|
if(index == null) {
|
|
|
|
|
throw new Error('illegal state')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pos = this.ace_editor.session.doc.indexToPosition(index)
|
|
|
|
|
|
|
|
|
|
this.supress_change_selection(() => {
|
|
|
|
|
const pos = this.ace_editor.session.doc.indexToPosition(index)
|
|
|
|
|
this.ace_editor.moveCursorToPosition(pos)
|
|
|
|
|
// Moving cursor performs selection, clear it
|
|
|
|
|
this.ace_editor.clearSelection()
|
|
|
|
|
const first = this.ace_editor.renderer.getFirstVisibleRow()
|
|
|
|
|
const last = this.ace_editor.renderer.getLastVisibleRow()
|
|
|
|
|
if(pos.row < first || pos.row > last) {
|
|
|
|
|
this.ace_editor.scrollToLine(pos.row)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goto_definition(){
|
2022-12-03 03:18:54 +08:00
|
|
|
const index = this.get_cursor_position()
|
2022-09-10 02:48:13 +08:00
|
|
|
exec('goto_definition', index)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for_each_session(cb) {
|
|
|
|
|
for(let file in this.sessions) {
|
|
|
|
|
cb(file, this.sessions[file])
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-04 20:24:42 +03:00
|
|
|
|
|
|
|
|
make_resizable() {
|
|
|
|
|
|
|
|
|
|
const apply_height = () => {
|
|
|
|
|
this.editor_container.style.height = localStorage.editor_height + 'vh'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let last_resize_time = new Date().getTime()
|
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
last_resize_time = new Date().getTime()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Save editor_height on resize and restore it on reopen
|
|
|
|
|
if(localStorage.editor_height != null) {
|
|
|
|
|
apply_height()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let is_first_run = true
|
|
|
|
|
|
|
|
|
|
new ResizeObserver((e) => {
|
|
|
|
|
if(is_first_run) {
|
|
|
|
|
// Resize observer callback seems to fire immediately on create
|
|
|
|
|
is_first_run = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if(new Date().getTime() - last_resize_time < 100) {
|
|
|
|
|
// Resize observer triggered by window resize, skip
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// See https://stackoverflow.com/a/57166828/795038
|
|
|
|
|
// ace editor must be updated based on container size change
|
|
|
|
|
this.ace_editor.resize()
|
|
|
|
|
|
|
|
|
|
const height = this.editor_container.offsetHeight / window.innerHeight * 100
|
|
|
|
|
localStorage.editor_height = height
|
|
|
|
|
// resize applies height in pixels. Wait for it and apply height in vh
|
|
|
|
|
setTimeout(apply_height, 0)
|
|
|
|
|
|
|
|
|
|
}).observe(this.editor_container)
|
|
|
|
|
}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|