make cursor position controlled property of state

This commit is contained in:
Dmitry Vasilev
2022-12-03 03:17:01 +08:00
parent e235704455
commit e3a742ae77
6 changed files with 150 additions and 180 deletions

View File

@@ -13,6 +13,25 @@ export const pp_calltree = tree => ({
children: tree.children && tree.children.map(pp_calltree)
})
export const current_caret_position = state =>
state.caret_position_by_file[state.current_module]
// When we open file for the first time, caret set to the beginning
?? 0
export const set_caret_position = (state, caret_position) => (
{
...state,
caret_position_by_file: {
...state.caret_position_by_file, [state.current_module]: caret_position
}
}
)
export const set_location = (state, location) => set_caret_position(
{...state, current_module: location.module},
location.index
)
const is_stackoverflow = node =>
// Chrome
node.error.message == 'Maximum call stack size exceeded'
@@ -214,35 +233,31 @@ const jump_calltree_node = (_state, _current_calltree_node) => {
: find_callsite(next.modules, active_calltree_node, current_calltree_node)
return {
state: {...next, current_module: loc.module},
state: next.current_calltree_node.toplevel
? {...next, current_module: loc.module}
// TODO: better jump not start of function (arguments), but start
// of body?
: set_location(next, loc),
effects: next.current_calltree_node.toplevel
? {type: 'unembed_value_explorer'}
: [
{
type: 'set_caret_position',
// TODO: better jump not start of function (arguments), but start
// of body?
args: [loc.index],
},
{
type: 'embed_value_explorer',
args: [{
index: loc.index,
result: {
ok: true,
value: current_calltree_node.ok
? {
'*arguments*': current_calltree_node.args,
'*return*': current_calltree_node.value,
}
: {
'*arguments*': current_calltree_node.args,
'*throws*': current_calltree_node.error,
}
}
}],
},
]
: {
type: 'embed_value_explorer',
args: [{
index: loc.index,
result: {
ok: true,
value: current_calltree_node.ok
? {
'*arguments*': current_calltree_node.args,
'*return*': current_calltree_node.value,
}
: {
'*arguments*': current_calltree_node.args,
'*throws*': current_calltree_node.error,
}
}
}],
}
}
}
@@ -716,8 +731,8 @@ const select_return_value = state => {
if(return_statement == null) {
// Fn has no return statement
return {
state: {...state, current_module: loc.module},
effects: {type: 'set_caret_position', args: [code.body.index, true]}
state: set_location(state, {module: loc.module, index: code.body.index}),
effects: {type: 'set_focus'}
}
} else {
result_node = return_statement.children[0]
@@ -743,15 +758,15 @@ const select_return_value = state => {
}
return {
state: {...state,
current_module: loc.module,
state: {
...set_location(state, {module: loc.module, index: node.index}),
selection_state: {
node,
initial_is_expand: true,
result: result_node.result,
}
},
effects: {type: 'set_caret_position', args: [node.index, true]}
effects: {type: 'set_focus'}
}
}
@@ -785,15 +800,17 @@ const select_arguments = (state, with_focus = true) => {
}
return {
state: {...state,
current_module: loc.module,
state: {
...set_location(state, {module: loc.module, index: node.index}),
selection_state: {
node,
initial_is_expand: true,
result,
}
},
effects: {type: 'set_caret_position', args: [node.index, with_focus]}
effects: with_focus
? {type: 'set_focus'}
: null,
}
}

View File

@@ -14,7 +14,8 @@ import {
calltree_commands,
add_frame, calltree_node_loc, expand_path,
initial_calltree_node, default_expand_path, toggle_expanded, active_frame,
find_call, find_call_node, set_active_calltree_node
find_call, find_call_node, set_active_calltree_node,
set_caret_position, current_caret_position, set_location
} from './calltree.js'
const collect_logs = call =>
@@ -46,7 +47,7 @@ const apply_eval_result = (state, eval_result) => {
}
}
const run_code = (s, index, dirty_files) => {
const run_code = (s, dirty_files) => {
const parse_result = load_modules(s.entrypoint, module => {
if(dirty_files != null && dirty_files.includes(module)) {
@@ -107,7 +108,6 @@ const run_code = (s, index, dirty_files) => {
// Trigger loading of external modules
return {...state,
loading_external_imports_state: {
index,
external_imports,
}
}
@@ -122,25 +122,29 @@ const run_code = (s, index, dirty_files) => {
state.external_imports_cache,
(module_name, module) => external_imports.includes(module_name)
),
index
)
}
}
const do_external_imports_loaded = (
state,
const external_imports_loaded = (
s,
prev_state,
external_imports,
index,
) => {
if(
state.loading_external_imports_state
s.loading_external_imports_state
!=
prev_state.loading_external_imports_state
) {
// code was modified after loading started, discard
return state
return s
}
const state = {
...s,
external_imports_cache: external_imports,
loading_external_imports_state: null
}
if(external_imports != null) {
@@ -168,7 +172,7 @@ const do_external_imports_loaded = (
}
}
const node = find_call_node(state, index)
const node = find_call_node(state, current_caret_position(state))
let active_calltree_node, next
@@ -239,35 +243,12 @@ const do_external_imports_loaded = (
}
}
const external_imports_loaded = (
state,
prev_state,
external_imports,
maybe_index,
) => {
// index saved in loading_external_imports_state maybe stale, if cursor was
// moved after
// TODO refactor, make index controlled property saved in state?
const index = maybe_index ?? state.loading_external_imports_state.index
// TODO after edit we should fire embed_value_explorer, but we dont do it
// here because we dont have cursor position (index) here (see comment
// above). Currently it is fixed by having `external_imports_cache`, so code
// goes async path only on first external imports load
return {
...do_external_imports_loaded(state, prev_state, external_imports, index),
external_imports_cache: external_imports,
loading_external_imports_state: null
}
}
// TODO refactor, make index controlled property and get it from state instead
// of setting to zero
const rerun_code = state => run_code(state, 0)
const input = (state, code, index) => {
const files = {...state.files, [state.current_module]: code}
const next = run_code({...state, files}, index, [state.current_module])
const next = run_code(
set_caret_position({...state, files}, index),
[state.current_module]
)
const effect_save = next.current_module == ''
? {type: 'save_to_localstorage', args: ['code', code]}
: {type: 'write', args: [
@@ -505,13 +486,12 @@ const change_current_module = (state, current_module) => {
}
}
const change_entrypoint = (state, entrypoint, index) => {
const change_entrypoint = (state, entrypoint) => {
return run_code(
{...state,
entrypoint,
current_module: entrypoint,
},
index
}
)
}
@@ -543,8 +523,10 @@ const goto_definition = (state, index) => {
loc = {module: state.current_module, index: d.index}
}
return {
state: {...state, current_module: loc.module},
effects: {type: 'set_caret_position', args: [loc.index]}
state: set_caret_position(
{...state, current_module: loc.module},
loc.index,
)
}
}
}
@@ -553,9 +535,8 @@ const goto_definition = (state, index) => {
const goto_problem = (state, p) => {
return {
state: {...state, current_module: p.module},
// TODO set focus after jump
effects: {type: 'set_caret_position', args: [p.index]}
state: set_location(state, p),
effects: {type: 'set_focus'}
}
}
@@ -728,17 +709,20 @@ const do_move_cursor = (state, index) => {
}
const move_cursor = (s, index) => {
const with_cursor = set_caret_position(s, index)
if(!s.parse_result.ok){
return {state: s}
return {state: with_cursor}
}
if(s.loading_external_imports_state != null) {
// TODO: save index in loading_external_imports_state
return {state: s}
// Code will be executed when imports will load, do not do it right now
return {state: with_cursor}
}
// Remove selection on move cursor
const state_sel_removed = {...s, selection_state: null}
const state_sel_removed = {...with_cursor, selection_state: null}
const state = find_call(state_sel_removed, index)
@@ -784,7 +768,7 @@ const do_load_dir = (state, dir) => {
const load_dir = (state, dir) => {
// Clear parse cache and rerun code
return rerun_code({
return run_code({
...do_load_dir(state, dir),
// remove cache. We have to clear cache because imports of modules that are
// not available because project_dir is not available have errors and the
@@ -800,7 +784,7 @@ const create_file = (state, dir, current_module) => {
const open_run_window = state => {
// After we reopen run window, we should reload external modules in the
// context of new window. Clear external_imports_cache
return rerun_code({
return run_code({
...state,
external_imports_cache: null,
})
@@ -811,23 +795,21 @@ const get_initial_state = state => {
? state
: do_load_dir(state, state.project_dir)
const entrypoint = with_files.entrypoint
const current_module = with_files.current_module
const html_file = with_files.html_file
const blank_if_not_exists = key =>
with_files.files[with_files[key]] == null
? ''
: with_files[key]
const entrypoint = blank_if_not_exists('entrypoint')
const current_module = blank_if_not_exists('current_module')
const html_file = blank_if_not_exists('html_file')
return {
...with_files,
// If module for entrypoint or current_module does not exist, use *scratch*
entrypoint:
with_files.files[entrypoint] == null
? ''
: entrypoint,
current_module: with_files.files[current_module] == null
? ''
: current_module,
html_file: with_files.files[html_file] == null
? ''
: html_file,
entrypoint,
current_module,
html_file,
caret_position_by_file: {[current_module]: 0},
}
}

View File

@@ -416,9 +416,9 @@ export class Editor {
? this.ace_editor.getSession()
: this.get_session(file)
// Session was not created for file
if(session == null) {
return null
// Session was not created for file
throw new Error('illegal state')
}
return session.doc.positionToIndex(session.selection.getCursor())

View File

@@ -240,11 +240,7 @@ export class UI {
change_entrypoint(e) {
const file = e.target.value
const index = this.editor.get_caret_position(file)
// if index is null, session was not created, and index after session
// creation will be 0
?? 0
exec('change_entrypoint', file, index)
exec('change_entrypoint', file)
this.editor.focus()
}

12
src/effects.js vendored
View File

@@ -5,6 +5,7 @@ import {
calltree_node_loc,
get_deferred_calls
} from './calltree.js'
import {current_caret_position} from './calltree.js'
import {FLAGS} from './feature_flags.js'
import {exec, FILES_ROOT} from './index.js'
@@ -165,6 +166,10 @@ export const render_common_side_effects = (prev, next, command, ui) => {
ui.editor.switch_session(next.current_module)
}
if(current_caret_position(next) != ui.editor.get_caret_position()) {
ui.editor.set_caret_position(current_caret_position(next))
}
if(prev.loading_external_imports_state != next.loading_external_imports_state) {
load_external_imports(next)
}
@@ -263,11 +268,8 @@ export const render_common_side_effects = (prev, next, command, ui) => {
export const EFFECTS = {
set_caret_position: (state, [index, with_focus], ui) => {
ui.editor.set_caret_position(index)
if(with_focus) {
ui.editor.focus()
}
set_focus: (_state, _args, ui) => {
ui.editor.focus()
},
set_status: (state, [msg], ui) => {