From a5079541472579f732ba7c16f102f6d38f10abe7 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 2 Jan 2020 13:25:18 -0500 Subject: [PATCH 01/60] Implement undo and redo --- src/hazelweb/JSUtil.re | 20 ++++++++--- src/hazelweb/Model.re | 75 ++++++++++++++++++++++++++++++++-------- src/hazelweb/Update.re | 8 ++++- src/hazelweb/gui/Cell.re | 11 ++++++ 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/hazelweb/JSUtil.re b/src/hazelweb/JSUtil.re index 8db9409ea1..df50f1c51a 100644 --- a/src/hazelweb/JSUtil.re +++ b/src/hazelweb/JSUtil.re @@ -339,10 +339,11 @@ module ModKeys = { }; let not_held = {c: NotHeld, s: NotHeld, a: NotHeld, m: NotHeld}; - let ctrl = {c: Held, s: Any, a: NotHeld, m: NotHeld}; + let ctrl = {c: Held, s: NotHeld, a: NotHeld, m: NotHeld}; let shift = {c: NotHeld, s: Held, a: NotHeld, m: NotHeld}; let alt = {c: NotHeld, s: Any, a: Held, m: NotHeld}; let no_ctrl_alt_meta = {c: NotHeld, s: Any, a: NotHeld, m: NotHeld}; + let ctrl_shift = {c: Held, s: Held, a: NotHeld, m: NotHeld}; let req_matches = (req, mk, evt) => switch (req) { @@ -427,6 +428,7 @@ module KeyCombo = { let shift = key => {mod_keys: ModKeys.shift, key}; let ctrl = key => {mod_keys: ModKeys.ctrl, key}; let alt = key => {mod_keys: ModKeys.alt, key}; + let ctrl_shift = key => {mod_keys: ModKeys.ctrl_shift, key}; let matches = (kc, evt: Js.t(Dom_html.keyboardEvent)) => ModKeys.matches(kc.mod_keys, evt) && Key.matches(kc.key, evt); @@ -473,6 +475,8 @@ module KeyCombo = { let key_B = no_ctrl_alt_meta(Key.the_key("B")); let key_N = no_ctrl_alt_meta(Key.the_key("N")); let key_L = no_ctrl_alt_meta(Key.the_key("L")); + let ctrl_z = ctrl(Key.the_letter_code("Z")); + let ctrl_shift_z = ctrl_shift(Key.the_letter_code("Z")); }; type t = @@ -502,7 +506,9 @@ module KeyCombo = { | Semicolon | Alt_L | Alt_R - | Alt_C; + | Alt_C + | Ctrl_Z + | Ctrl_Shift_Z; let get_details = fun @@ -532,11 +538,17 @@ module KeyCombo = { | Semicolon => Details.semicolon | Alt_L => Details.alt_L | Alt_R => Details.alt_R - | Alt_C => Details.alt_C; + | Alt_C => Details.alt_C + | Ctrl_Z => Details.ctrl_z + | Ctrl_Shift_Z => Details.ctrl_shift_z; let of_evt = (evt: Js.t(Dom_html.keyboardEvent)): option(t) => { let evt_matches = details => Details.matches(details, evt); - if (evt_matches(Details.escape)) { + if (evt_matches(Details.ctrl_z)) { + Some(Ctrl_Z); + } else if (evt_matches(Details.ctrl_shift_z)) { + Some(Ctrl_Shift_Z); + } else if (evt_matches(Details.escape)) { Some(Escape); } else if (evt_matches(Details.backspace)) { Some(Backspace); diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index eb6164ab25..5a50b0b4f1 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -84,22 +84,35 @@ type has_result_state = { type result_state = | ResultsDisabled - | Result(has_result_state); + | Result(has_result_state) /* type checkpoint = (Statics.edit_state, ZList.t(unit, Action.t)); type history = ZList.t(checkpoint, checkpoint); */; + +[@deriving sexp] +type history = ZList.t(Statics.edit_state, Statics.edit_state); type t = { cardstacks, - cardstacks_state, - /* these are derived from the cardstack state: */ + cardstacks_state /* these are derived from the cardstack state: */, cursor_info: CursorInfo.t, compute_results_flag: bool, - result_state, - /* UI state */ + result_state /* UI state */, user_newlines, selected_example: option(UHExp.block), is_cell_focused: bool, left_sidebar_open: bool, right_sidebar_open: bool, + history, +}; + +let add_history = (history: history, edit_state: Statics.edit_state): history => { + let add_new = ( + ZList.prj_prefix(history), + ZList.prj_z(history), + [edit_state], + ); + ZList.shift_next(add_new); }; +let undo_edit_state = history => ZList.shift_prev(history); +let redo_edit_state = history => ZList.shift_next(history); let cardstack_state_of = model => ZList.prj_z(model.cardstacks_state); @@ -140,14 +153,14 @@ let cursor_info_of_edit_state = ((zblock, _, _): edit_state): CursorInfo.t => ) { | None => raise(MissingCursorInfo) | Some(ci) => -/* uncomment to see where variable is used - switch (ci.node) { - | Pat(VarPat(_, uses)) => - JSUtil.log_sexp(UsageAnalysis.sexp_of_uses_list(uses)) - | _ => JSUtil.log("not varpat") - }; -*/ - ci; + /* uncomment to see where variable is used + switch (ci.node) { + | Pat(VarPat(_, uses)) => + JSUtil.log_sexp(UsageAnalysis.sexp_of_uses_list(uses)) + | _ => JSUtil.log("not varpat") + }; + */ + ci }; exception InvalidInput; @@ -373,6 +386,7 @@ let init = (): t => { selected_example: None, is_cell_focused: false, user_newlines: CursorPath.StepsMap.empty, + history: ([], edit_state, []), }; }; @@ -391,7 +405,27 @@ let perform_edit_action = (model: t, a: Action.t): t => { | Failed => raise(FailedAction) | CursorEscaped(_) => raise(CursorEscaped) | CantShift => raise(CantShift) - | Succeeded(new_edit_state) => model |> update_edit_state(new_edit_state) + | Succeeded(new_edit_state) => + let new_model = model |> update_edit_state(new_edit_state); + let new_history = { + switch (a) { + | UpdateApPalette(_) + | Delete + | Backspace + | Construct(_) => add_history(model.history, new_edit_state) + | MoveTo(_) + | MoveToBefore(_) + | MoveLeft + | MoveRight + | MoveToNextHole + | MoveToPrevHole + | ShiftLeft + | ShiftRight + | ShiftUp + | ShiftDown => model.history + }; + }; + {...new_model, history: new_history}; }; }; @@ -410,6 +444,19 @@ let move_to_hole = (model: t, u: MetaVar.t): t => { }; }; +let undo = (model: t): t => { + let new_history = undo_edit_state(model.history); + let new_edit_state = ZList.prj_z(new_history); + let new_model = model |> update_edit_state(new_edit_state); + {...new_model, history: new_history}; +}; + +let redo = (model: t): t => { + let new_history = redo_edit_state(model.history); + let new_edit_state = ZList.prj_z(new_history); + let new_model = model |> update_edit_state(new_edit_state); + {...new_model, history: new_history}; +}; let select_hole_instance = (model, (u, i) as inst) => { switch (model.result_state) { | ResultsDisabled => model diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 5acda89003..1554883573 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -26,7 +26,9 @@ module Action = { | BlurCell | FocusWindow | AddUserNewline(CursorPath.steps) - | RemoveUserNewline(CursorPath.steps); + | RemoveUserNewline(CursorPath.steps) + | Redo + | Undo; }; [@deriving sexp] @@ -79,6 +81,8 @@ let log_action = (action: Action.t, _: State.t): unit => { | FocusWindow | AddUserNewline(_) | RemoveUserNewline(_) + | Redo + | Undo | MoveToHole(_) => Logger.append( Sexp.to_string( @@ -94,6 +98,8 @@ let apply_action = : Model.t => { log_action(action, state); switch (action) { + | Redo => Model.redo(model) + | Undo => Model.undo(model) | EditAction(a) => switch (Model.perform_edit_action(model, a)) { | new_model => new_model diff --git a/src/hazelweb/gui/Cell.re b/src/hazelweb/gui/Cell.re index ab083ab659..983fdbd127 100644 --- a/src/hazelweb/gui/Cell.re +++ b/src/hazelweb/gui/Cell.re @@ -359,6 +359,17 @@ let view = ci.node |> Hashtbl.find(kc_actions, Enter), ), ) + | (_, _, _, Some((Ctrl_Z | Ctrl_Shift_Z) as kc)) => + switch (kc) { + | Ctrl_Z => prevent_stop_inject(Update.Action.Undo) + | Ctrl_Shift_Z => prevent_stop_inject(Update.Action.Redo) + | _ => + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, kc), + ), + ) + } | (_, _, _, Some(kc)) => prevent_stop_inject( Update.Action.EditAction( From 627d26cb798c28131ced33ecc9d2a921edee9c8b Mon Sep 17 00:00:00 2001 From: "Michael D. Adams" Date: Thu, 2 Jan 2020 13:57:38 -0500 Subject: [PATCH 02/60] Fix duplicate `user_newlines` --- src/hazelweb/Model.re | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 149ca05b68..fcb6da06ec 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -387,7 +387,6 @@ let init = (): t => { user_newlines: CursorPath.StepsMap.empty, selected_example: None, is_cell_focused: false, - user_newlines: CursorPath.StepsMap.empty, history: ([], edit_state, []), left_sidebar_open: false, right_sidebar_open: true, From 24dee2c094360cea119dff181711136c67148098 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 2 Jan 2020 15:23:37 -0500 Subject: [PATCH 03/60] modify redo and undo --- src/hazelweb/JSUtil.re | 16 +- src/hazelweb/Model.re | 30 +- src/hazelweb/Update.re | 8 +- src/hazelweb/gui/Cell.re | 808 +++++++++++++++++++-------------------- 4 files changed, 438 insertions(+), 424 deletions(-) diff --git a/src/hazelweb/JSUtil.re b/src/hazelweb/JSUtil.re index df50f1c51a..da6473f3e5 100644 --- a/src/hazelweb/JSUtil.re +++ b/src/hazelweb/JSUtil.re @@ -406,7 +406,7 @@ module Key = { String.equal(code, c); | Key(k) => let key = get_key(evt); - String.equal(key, k); + String.equal(String.uppercase_ascii(key), String.uppercase_ascii(k)); }; let matches = (k, evt: Js.t(Dom_html.keyboardEvent)) => { @@ -465,18 +465,18 @@ module KeyCombo = { let ampersand = no_ctrl_alt_meta(Key.the_key("&")); let dollar = no_ctrl_alt_meta(Key.the_key("$")); let amp = no_ctrl_alt_meta(Key.the_key("&")); - let alt_L = alt(Key.the_letter_code("l")); - let alt_R = alt(Key.the_letter_code("r")); - let alt_C = alt(Key.the_letter_code("c")); + let alt_L = alt(Key.the_key("l")); + let alt_R = alt(Key.the_key("r")); + let alt_C = alt(Key.the_key("c")); let alt_PageUp = alt(Key.the_key("PageUp")); let alt_PageDown = alt(Key.the_key("PageDown")); - let alt_T = alt(Key.the_letter_code("T")); - let alt_F = alt(Key.the_letter_code("F")); + let alt_T = alt(Key.the_key("T")); + let alt_F = alt(Key.the_key("F")); let key_B = no_ctrl_alt_meta(Key.the_key("B")); let key_N = no_ctrl_alt_meta(Key.the_key("N")); let key_L = no_ctrl_alt_meta(Key.the_key("L")); - let ctrl_z = ctrl(Key.the_letter_code("Z")); - let ctrl_shift_z = ctrl_shift(Key.the_letter_code("Z")); + let ctrl_z = ctrl(Key.the_key("z")); + let ctrl_shift_z = ctrl_shift(Key.the_key("Z")); }; type t = diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 5a50b0b4f1..8bb9239a04 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -84,7 +84,7 @@ type has_result_state = { type result_state = | ResultsDisabled - | Result(has_result_state) /* type checkpoint = (Statics.edit_state, ZList.t(unit, Action.t)); type history = ZList.t(checkpoint, checkpoint); */; + | Result(has_result_state); [@deriving sexp] type history = ZList.t(Statics.edit_state, Statics.edit_state); @@ -104,6 +104,7 @@ type t = { }; let add_history = (history: history, edit_state: Statics.edit_state): history => { + /* first add new edit state to the end, then shift_next */ let add_new = ( ZList.prj_prefix(history), ZList.prj_z(history), @@ -111,8 +112,18 @@ let add_history = (history: history, edit_state: Statics.edit_state): history => ); ZList.shift_next(add_new); }; -let undo_edit_state = history => ZList.shift_prev(history); -let redo_edit_state = history => ZList.shift_next(history); +let undo_edit_state = (history): option(history) => { + switch (ZList.prj_prefix(history)) { + | [] => None + | _ => Some(ZList.shift_prev(history)) + }; +}; +let redo_edit_state = (history): option(history) => { + switch (ZList.prj_suffix(history)) { + | [] => None + | _ => Some(ZList.shift_next(history)) + }; +}; let cardstack_state_of = model => ZList.prj_z(model.cardstacks_state); @@ -445,14 +456,23 @@ let move_to_hole = (model: t, u: MetaVar.t): t => { }; let undo = (model: t): t => { - let new_history = undo_edit_state(model.history); + let new_history = + switch (undo_edit_state(model.history)) { + | Some(his) => his + | None => model.history + }; + let new_edit_state = ZList.prj_z(new_history); let new_model = model |> update_edit_state(new_edit_state); {...new_model, history: new_history}; }; let redo = (model: t): t => { - let new_history = redo_edit_state(model.history); + let new_history = + switch (redo_edit_state(model.history)) { + | Some(his) => his + | None => model.history + }; let new_edit_state = ZList.prj_z(new_history); let new_model = model |> update_edit_state(new_edit_state); {...new_model, history: new_history}; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 1554883573..6352ab9a6b 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -81,9 +81,9 @@ let log_action = (action: Action.t, _: State.t): unit => { | FocusWindow | AddUserNewline(_) | RemoveUserNewline(_) + | MoveToHole(_) | Redo - | Undo - | MoveToHole(_) => + | Undo => Logger.append( Sexp.to_string( sexp_of_timestamped_action(mk_timestamped_action(action)), @@ -98,8 +98,6 @@ let apply_action = : Model.t => { log_action(action, state); switch (action) { - | Redo => Model.redo(model) - | Undo => Model.undo(model) | EditAction(a) => switch (Model.perform_edit_action(model, a)) { | new_model => new_model @@ -289,5 +287,7 @@ let apply_action = }; }; model; + | Redo => Model.redo(model) + | Undo => Model.undo(model) }; }; diff --git a/src/hazelweb/gui/Cell.re b/src/hazelweb/gui/Cell.re index 983fdbd127..e334ae6f8f 100644 --- a/src/hazelweb/gui/Cell.re +++ b/src/hazelweb/gui/Cell.re @@ -1,407 +1,401 @@ -module Vdom = Virtual_dom.Vdom; -module Dom = Js_of_ocaml.Dom; -module Dom_html = Js_of_ocaml.Dom_html; -module Js = Js_of_ocaml.Js; -module Sexp = Sexplib.Sexp; -module KeyCombo = JSUtil.KeyCombo; -open GeneralUtil; -open ViewUtil; -open Sexplib.Std; - -let string_insert = (s1, offset, s2) => { - let prefix = String.sub(s1, 0, offset); - let length = String.length(s1); - let suffix = String.sub(s1, offset, length - offset); - prefix ++ s2 ++ suffix; -}; - -let string_backspace = (s, offset, ctrlKey) => { - let prefix = ctrlKey ? "" : String.sub(s, 0, offset - 1); - let length = String.length(s); - let suffix = String.sub(s, offset, length - offset); - let offset' = ctrlKey ? 0 : offset - 1; - (prefix ++ suffix, offset'); -}; - -let string_delete = (s, offset, ctrlKey) => { - let prefix = String.sub(s, 0, offset); - let length = String.length(s); - let suffix = ctrlKey ? "" : String.sub(s, offset + 1, length - offset - 1); - (prefix ++ suffix, offset); -}; - -let kc_actions: Hashtbl.t(KeyCombo.t, CursorInfo.node => Action.t) = - Hashtbl.of_seq( - [ - (KeyCombo.Backspace, _ => Action.Backspace), - (KeyCombo.Delete, _ => Action.Delete), - (KeyCombo.ShiftTab, _ => Action.MoveToPrevHole), - (KeyCombo.Tab, _ => Action.MoveToNextHole), - (KeyCombo.Key_N, _ => Action.Construct(SNum)), - (KeyCombo.Key_B, _ => Action.Construct(SBool)), - ( - KeyCombo.GT, - fun - | CursorInfo.Typ(_) => Action.Construct(SOp(SArrow)) - | _ => Action.Construct(SOp(SGreaterThan)), - ), - (KeyCombo.Ampersand, _ => Action.Construct(SOp(SAnd))), - (KeyCombo.VBar, _ => Action.Construct(SOp(SOr))), - (KeyCombo.Key_L, _ => Action.Construct(SList)), - (KeyCombo.LeftParen, _ => Action.Construct(SParenthesized)), - (KeyCombo.Colon, _ => Action.Construct(SAsc)), - (KeyCombo.Equals, _ => Action.Construct(SOp(SEquals))), - (KeyCombo.Enter, _ => Action.Construct(SLine)), - (KeyCombo.Backslash, _ => Action.Construct(SLam)), - (KeyCombo.Plus, _ => Action.Construct(SOp(SPlus))), - (KeyCombo.Minus, _ => Action.Construct(SOp(SMinus))), - (KeyCombo.Asterisk, _ => Action.Construct(SOp(STimes))), - (KeyCombo.LT, _ => Action.Construct(SOp(SLessThan))), - (KeyCombo.Space, _ => Action.Construct(SOp(SSpace))), - (KeyCombo.Comma, _ => Action.Construct(SOp(SComma))), - (KeyCombo.LeftBracket, _ => Action.Construct(SListNil)), - (KeyCombo.Semicolon, _ => Action.Construct(SOp(SCons))), - (KeyCombo.Alt_L, _ => Action.Construct(SInj(L))), - (KeyCombo.Alt_R, _ => Action.Construct(SInj(R))), - (KeyCombo.Alt_C, _ => Action.Construct(SCase)), - ] - |> List.to_seq, - ); -let entered_single_key = - ( - ~prevent_stop_inject, - ci: CursorInfo.t, - single_key: JSUtil.single_key, - opt_kc: option(KeyCombo.t), - ) - : option(Vdom.Event.t) => - switch (ci.node, opt_kc) { - | (Typ(_), Some((Key_B | Key_L | Key_N) as kc)) => - Some( - prevent_stop_inject( - Update.Action.EditAction(ci.node |> Hashtbl.find(kc_actions, kc)), - ), - ) - | (Pat(OtherPat(EmptyHole(_))), _) => - let shape = - switch (single_key) { - | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) - | Letter(x) => Action.SVar(x, OnText(Var.length(x))) - | Underscore => Action.SWild - }; - Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); - | (Pat(OtherPat(Wild(_))), _) => - let shape = - switch (single_key) { - | Number(n) => - Action.SVar("_" ++ string_of_int(n), OnText(num_digits(n) + 1)) - | Letter(x) => Action.SVar("_" ++ x, OnText(Var.length(x) + 1)) - | Underscore => Action.SVar("__", OnText(2)) - }; - Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); - | (Line(EmptyLine | ExpLine(EmptyHole(_))) | Exp(EmptyHole(_)), _) => - let shape = - switch (single_key) { - | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) - | Letter(x) => Action.SVar(x, OnText(Var.length(x))) - | Underscore => Action.SVar("_", OnText(1)) - }; - Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); - | ( - Exp(NumLit(_, _) | BoolLit(_, _) | Var(_, _, _)) | - Pat(OtherPat(NumLit(_, _) | BoolLit(_, _)) | VarPat(_, _)), - _, - ) => - let (nodeValue, anchorOffset) = - switch (ci.node, ci.position) { - | (Exp(NumLit(_, n)) | Pat(OtherPat(NumLit(_, n))), OnText(j)) => ( - string_of_int(n), - j, - ) - | (Exp(BoolLit(_, b)) | Pat(OtherPat(BoolLit(_, b))), OnText(j)) => ( - b ? "true" : "false", - j, - ) - | (Exp(Var(_, _, x)) | Pat(VarPat(x, _)), OnText(j)) => (x, j) - | (_, _) => assert(false) - }; - let key_string = JSUtil.single_key_string(single_key); - let newNodeValue = string_insert(nodeValue, anchorOffset, key_string); - switch (int_of_string_opt(newNodeValue), single_key) { - | (Some(_), Underscore) => - // OCaml accepts and ignores underscores - // when parsing ints from strings, we don't - Some(Vdom.Event.Ignore) - | (Some(new_n), _) => - Some( - // defensive check in case OCaml is - // doing any other weird things - num_digits(new_n) != String.length(newNodeValue) - ? Vdom.Event.Ignore - : prevent_stop_inject( - Update.Action.EditAction( - Action.Construct( - Action.SNumLit(new_n, OnText(anchorOffset + 1)), - ), - ), - ), - ) - | (None, _) => - Some( - Var.is_valid(newNodeValue) - ? prevent_stop_inject( - Update.Action.EditAction( - Action.Construct( - Action.SVar(newNodeValue, OnText(anchorOffset + 1)), - ), - ), - ) - : prevent_stop_inject(Update.Action.InvalidVar(newNodeValue)), - ) - }; - | (Line(_) | Exp(_) | Rule(_) | Pat(_) | Typ(_), _) => None - }; - -let view = - (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t => { - Vdom.( - Node.div( - [ - Attr.id("pp_view"), - Attr.classes(["ModelExp"]), - Attr.create( - "style", - "font-size: " - ++ (font_size |> JSUtil.px) - ++ "; line-height: " - ++ string_of_float(line_height) - ++ "; padding: " - ++ (cell_padding |> JSUtil.px) - ++ "; border: " - ++ (cell_border |> JSUtil.px) - ++ " solid #CCC;", - ), - ], - [ - Node.div( - [ - Attr.id(cell_id), - Attr.create("contenteditable", "true"), - Attr.on("drop", _ => Event.Prevent_default), - Attr.on_focus(_ => inject(FocusCell)), - Attr.on_blur(_ => inject(BlurCell)), - Attr.on_keypress(evt => - switch ( - model.cursor_info.position, - JSUtil.is_movement_key(evt), - ) { - | (Staging(_), _) => Event.Prevent_default - | (OnText(_) | OnDelim(_, _), true) => Event.Many([]) - | (OnText(_) | OnDelim(_, _), false) => Event.Prevent_default - } - ), - Attr.on_keydown(evt => { - let prevent_stop_inject = a => - Vdom.Event.Many([ - Vdom.Event.Prevent_default, - Vdom.Event.Stop_propagation, - inject(a), - ]); - let ci = model.cursor_info; - switch ( - ci.position, - JSUtil.is_movement_key(evt), - JSUtil.is_single_key(evt), - KeyCombo.of_evt(evt), - ) { - | (Staging(_), true, _, _) => - switch (evt |> JSUtil.get_key) { - | "ArrowLeft" => - prevent_stop_inject(Update.Action.EditAction(ShiftLeft)) - | "ArrowRight" => - prevent_stop_inject(Update.Action.EditAction(ShiftRight)) - | "ArrowUp" => - prevent_stop_inject(Update.Action.EditAction(ShiftUp)) - | "ArrowDown" => - prevent_stop_inject(Update.Action.EditAction(ShiftDown)) - | _ => Event.Ignore - } - | (OnText(_) | OnDelim(_, _), true, _, _) => Event.Many([]) - | (_, _, None, None) => Event.Ignore - | (_, _, Some(single_key), opt_kc) => - switch ( - entered_single_key( - ~prevent_stop_inject, - ci, - single_key, - opt_kc, - ) - ) { - | Some(event) => event - | None => - let zblock = model |> Model.zblock; - switch (ci.position) { - | Staging(_) - | OnText(_) => Event.Ignore - | OnDelim(_, side) => - let move_cursor = - switch (side) { - | Before => ZExp.move_cursor_left_block - | After => ZExp.move_cursor_right_block - }; - switch (zblock |> move_cursor) { - | None => Event.Ignore - | Some(zblock) => - switch ( - CursorInfo.syn_cursor_info_block( - Contexts.empty, - zblock, - ) - ) { - | None => Event.Ignore - | Some(ci) => - switch ( - entered_single_key( - ~prevent_stop_inject, - ci, - single_key, - opt_kc, - ) - ) { - | None => Event.Ignore - | Some(event) => event - } - } - }; - }; - } - | (_, _, _, Some((Backspace | Delete) as kc)) => - let (string_edit, update, cursor_escaped) = - switch (kc) { - | Backspace => ( - string_backspace, - Update.Action.EditAction(Backspace), - ci |> CursorInfo.is_before_node, - ) - | _ => ( - string_delete, - Update.Action.EditAction(Delete), - ci |> CursorInfo.is_after_node, - ) - }; - switch ( - kc, - model.user_newlines - |> CursorPath.StepsMap.mem(ci.node_steps), - cursor_escaped, - ci.position, - ) { - | (Backspace, true, _, _) => - prevent_stop_inject( - Update.Action.RemoveUserNewline(ci.node_steps), - ) - | (_, true, _, _) => prevent_stop_inject(update) - | (_, false, true, _) - | (_, false, _, OnDelim(_, _) | Staging(_)) => - prevent_stop_inject(update) - | (_, false, false, OnText(_)) => - let nodeValue = JSUtil.force_get_anchor_node_value(); - let anchorOffset = JSUtil.get_anchor_offset(); - let ctrlKey = Js.to_bool(evt##.ctrlKey); - let (nodeValue', anchorOffset') = - string_edit(nodeValue, anchorOffset, ctrlKey); - switch ( - String.equal(nodeValue', ""), - int_of_string_opt(nodeValue'), - ) { - | (true, _) => prevent_stop_inject(update) - | (false, Some(new_n)) => - prevent_stop_inject( - Update.Action.EditAction( - Construct(SNumLit(new_n, OnText(anchorOffset'))), - ), - ) - | (false, None) => - Var.is_valid(nodeValue') - ? prevent_stop_inject( - Update.Action.EditAction( - Construct( - SVar(nodeValue', OnText(anchorOffset')), - ), - ), - ) - : prevent_stop_inject( - Update.Action.InvalidVar(nodeValue'), - ) - }; - }; - | (OnText(_) | OnDelim(_, _), _, _, Some(Enter)) => - switch ( - model.user_newlines - |> CursorPath.StepsMap.mem(ci.node_steps), - model |> Model.zblock |> ZExp.is_after_case_rule, - model |> Model.zblock |> ZExp.is_on_user_newlineable_hole, - ) { - | (false, false, true) => - prevent_stop_inject( - Update.Action.AddUserNewline(ci.node_steps), - ) - | (_, _, _) => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, Enter), - ), - ) - } - | (Staging(_), _, _, Some(Escape)) => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, Enter), - ), - ) - | (_, _, _, Some((Ctrl_Z | Ctrl_Shift_Z) as kc)) => - switch (kc) { - | Ctrl_Z => prevent_stop_inject(Update.Action.Undo) - | Ctrl_Shift_Z => prevent_stop_inject(Update.Action.Redo) - | _ => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, kc), - ), - ) - } - | (_, _, _, Some(kc)) => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, kc), - ), - ) - }; - }), - ], - [ - model.is_cell_focused - ? Code.view_of_zblock( - ~inject, - ~user_newlines=model.user_newlines, - model |> Model.zblock, - ) - : Code.view_of_block( - ~inject, - ~user_newlines=model.user_newlines, - model |> Model.block, - ), - ...CursorIndicators.view( - ~is_cell_focused=model.is_cell_focused, - ~holes_steps= - model |> Model.block |> CursorIndicators.collect_holes, - ~ci=model.cursor_info, - ), - ], - ), - ], - ) - ); -}; - -let elem = () => JSUtil.force_get_elem_by_id(cell_id); +module Vdom = Virtual_dom.Vdom; +module Dom = Js_of_ocaml.Dom; +module Dom_html = Js_of_ocaml.Dom_html; +module Js = Js_of_ocaml.Js; +module Sexp = Sexplib.Sexp; +module KeyCombo = JSUtil.KeyCombo; +open GeneralUtil; +open ViewUtil; +open Sexplib.Std; + +let string_insert = (s1, offset, s2) => { + let prefix = String.sub(s1, 0, offset); + let length = String.length(s1); + let suffix = String.sub(s1, offset, length - offset); + prefix ++ s2 ++ suffix; +}; + +let string_backspace = (s, offset, ctrlKey) => { + let prefix = ctrlKey ? "" : String.sub(s, 0, offset - 1); + let length = String.length(s); + let suffix = String.sub(s, offset, length - offset); + let offset' = ctrlKey ? 0 : offset - 1; + (prefix ++ suffix, offset'); +}; + +let string_delete = (s, offset, ctrlKey) => { + let prefix = String.sub(s, 0, offset); + let length = String.length(s); + let suffix = ctrlKey ? "" : String.sub(s, offset + 1, length - offset - 1); + (prefix ++ suffix, offset); +}; + +let kc_actions: Hashtbl.t(KeyCombo.t, CursorInfo.node => Action.t) = + Hashtbl.of_seq( + [ + (KeyCombo.Backspace, _ => Action.Backspace), + (KeyCombo.Delete, _ => Action.Delete), + (KeyCombo.ShiftTab, _ => Action.MoveToPrevHole), + (KeyCombo.Tab, _ => Action.MoveToNextHole), + (KeyCombo.Key_N, _ => Action.Construct(SNum)), + (KeyCombo.Key_B, _ => Action.Construct(SBool)), + ( + KeyCombo.GT, + fun + | CursorInfo.Typ(_) => Action.Construct(SOp(SArrow)) + | _ => Action.Construct(SOp(SGreaterThan)), + ), + (KeyCombo.Ampersand, _ => Action.Construct(SOp(SAnd))), + (KeyCombo.VBar, _ => Action.Construct(SOp(SOr))), + (KeyCombo.Key_L, _ => Action.Construct(SList)), + (KeyCombo.LeftParen, _ => Action.Construct(SParenthesized)), + (KeyCombo.Colon, _ => Action.Construct(SAsc)), + (KeyCombo.Equals, _ => Action.Construct(SOp(SEquals))), + (KeyCombo.Enter, _ => Action.Construct(SLine)), + (KeyCombo.Backslash, _ => Action.Construct(SLam)), + (KeyCombo.Plus, _ => Action.Construct(SOp(SPlus))), + (KeyCombo.Minus, _ => Action.Construct(SOp(SMinus))), + (KeyCombo.Asterisk, _ => Action.Construct(SOp(STimes))), + (KeyCombo.LT, _ => Action.Construct(SOp(SLessThan))), + (KeyCombo.Space, _ => Action.Construct(SOp(SSpace))), + (KeyCombo.Comma, _ => Action.Construct(SOp(SComma))), + (KeyCombo.LeftBracket, _ => Action.Construct(SListNil)), + (KeyCombo.Semicolon, _ => Action.Construct(SOp(SCons))), + (KeyCombo.Alt_L, _ => Action.Construct(SInj(L))), + (KeyCombo.Alt_R, _ => Action.Construct(SInj(R))), + (KeyCombo.Alt_C, _ => Action.Construct(SCase)), + ] + |> List.to_seq, + ); +let entered_single_key = + ( + ~prevent_stop_inject, + ci: CursorInfo.t, + single_key: JSUtil.single_key, + opt_kc: option(KeyCombo.t), + ) + : option(Vdom.Event.t) => + switch (ci.node, opt_kc) { + | (Typ(_), Some((Key_B | Key_L | Key_N) as kc)) => + Some( + prevent_stop_inject( + Update.Action.EditAction(ci.node |> Hashtbl.find(kc_actions, kc)), + ), + ) + | (Pat(OtherPat(EmptyHole(_))), _) => + let shape = + switch (single_key) { + | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) + | Letter(x) => Action.SVar(x, OnText(Var.length(x))) + | Underscore => Action.SWild + }; + Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); + | (Pat(OtherPat(Wild(_))), _) => + let shape = + switch (single_key) { + | Number(n) => + Action.SVar("_" ++ string_of_int(n), OnText(num_digits(n) + 1)) + | Letter(x) => Action.SVar("_" ++ x, OnText(Var.length(x) + 1)) + | Underscore => Action.SVar("__", OnText(2)) + }; + Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); + | (Line(EmptyLine | ExpLine(EmptyHole(_))) | Exp(EmptyHole(_)), _) => + let shape = + switch (single_key) { + | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) + | Letter(x) => Action.SVar(x, OnText(Var.length(x))) + | Underscore => Action.SVar("_", OnText(1)) + }; + Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); + | ( + Exp(NumLit(_, _) | BoolLit(_, _) | Var(_, _, _)) | + Pat(OtherPat(NumLit(_, _) | BoolLit(_, _)) | VarPat(_, _)), + _, + ) => + let (nodeValue, anchorOffset) = + switch (ci.node, ci.position) { + | (Exp(NumLit(_, n)) | Pat(OtherPat(NumLit(_, n))), OnText(j)) => ( + string_of_int(n), + j, + ) + | (Exp(BoolLit(_, b)) | Pat(OtherPat(BoolLit(_, b))), OnText(j)) => ( + b ? "true" : "false", + j, + ) + | (Exp(Var(_, _, x)) | Pat(VarPat(x, _)), OnText(j)) => (x, j) + | (_, _) => assert(false) + }; + let key_string = JSUtil.single_key_string(single_key); + let newNodeValue = string_insert(nodeValue, anchorOffset, key_string); + switch (int_of_string_opt(newNodeValue), single_key) { + | (Some(_), Underscore) => + // OCaml accepts and ignores underscores + // when parsing ints from strings, we don't + Some(Vdom.Event.Ignore) + | (Some(new_n), _) => + Some( + // defensive check in case OCaml is + // doing any other weird things + num_digits(new_n) != String.length(newNodeValue) + ? Vdom.Event.Ignore + : prevent_stop_inject( + Update.Action.EditAction( + Action.Construct( + Action.SNumLit(new_n, OnText(anchorOffset + 1)), + ), + ), + ), + ) + | (None, _) => + Some( + Var.is_valid(newNodeValue) + ? prevent_stop_inject( + Update.Action.EditAction( + Action.Construct( + Action.SVar(newNodeValue, OnText(anchorOffset + 1)), + ), + ), + ) + : prevent_stop_inject(Update.Action.InvalidVar(newNodeValue)), + ) + }; + | (Line(_) | Exp(_) | Rule(_) | Pat(_) | Typ(_), _) => None + }; + +let view = + (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t => { + Vdom.( + Node.div( + [ + Attr.id("pp_view"), + Attr.classes(["ModelExp"]), + Attr.create( + "style", + "font-size: " + ++ (font_size |> JSUtil.px) + ++ "; line-height: " + ++ string_of_float(line_height) + ++ "; padding: " + ++ (cell_padding |> JSUtil.px) + ++ "; border: " + ++ (cell_border |> JSUtil.px) + ++ " solid #CCC;", + ), + ], + [ + Node.div( + [ + Attr.id(cell_id), + Attr.create("contenteditable", "true"), + Attr.on("drop", _ => Event.Prevent_default), + Attr.on_focus(_ => inject(FocusCell)), + Attr.on_blur(_ => inject(BlurCell)), + Attr.on_keypress(evt => + switch ( + model.cursor_info.position, + JSUtil.is_movement_key(evt), + ) { + | (Staging(_), _) => Event.Prevent_default + | (OnText(_) | OnDelim(_, _), true) => Event.Many([]) + | (OnText(_) | OnDelim(_, _), false) => Event.Prevent_default + } + ), + Attr.on_keydown(evt => { + let prevent_stop_inject = a => + Vdom.Event.Many([ + Vdom.Event.Prevent_default, + Vdom.Event.Stop_propagation, + inject(a), + ]); + let ci = model.cursor_info; + switch ( + ci.position, + JSUtil.is_movement_key(evt), + JSUtil.is_single_key(evt), + KeyCombo.of_evt(evt), + ) { + | (Staging(_), true, _, _) => + switch (evt |> JSUtil.get_key) { + | "ArrowLeft" => + prevent_stop_inject(Update.Action.EditAction(ShiftLeft)) + | "ArrowRight" => + prevent_stop_inject(Update.Action.EditAction(ShiftRight)) + | "ArrowUp" => + prevent_stop_inject(Update.Action.EditAction(ShiftUp)) + | "ArrowDown" => + prevent_stop_inject(Update.Action.EditAction(ShiftDown)) + | _ => Event.Ignore + } + | (OnText(_) | OnDelim(_, _), true, _, _) => Event.Many([]) + | (_, _, None, None) => Event.Ignore + | (_, _, Some(single_key), opt_kc) => + switch ( + entered_single_key( + ~prevent_stop_inject, + ci, + single_key, + opt_kc, + ) + ) { + | Some(event) => event + | None => + let zblock = model |> Model.zblock; + switch (ci.position) { + | Staging(_) + | OnText(_) => Event.Ignore + | OnDelim(_, side) => + let move_cursor = + switch (side) { + | Before => ZExp.move_cursor_left_block + | After => ZExp.move_cursor_right_block + }; + switch (zblock |> move_cursor) { + | None => Event.Ignore + | Some(zblock) => + switch ( + CursorInfo.syn_cursor_info_block( + Contexts.empty, + zblock, + ) + ) { + | None => Event.Ignore + | Some(ci) => + switch ( + entered_single_key( + ~prevent_stop_inject, + ci, + single_key, + opt_kc, + ) + ) { + | None => Event.Ignore + | Some(event) => event + } + } + }; + }; + } + | (_, _, _, Some((Backspace | Delete) as kc)) => + let (string_edit, update, cursor_escaped) = + switch (kc) { + | Backspace => ( + string_backspace, + Update.Action.EditAction(Backspace), + ci |> CursorInfo.is_before_node, + ) + | _ => ( + string_delete, + Update.Action.EditAction(Delete), + ci |> CursorInfo.is_after_node, + ) + }; + switch ( + kc, + model.user_newlines + |> CursorPath.StepsMap.mem(ci.node_steps), + cursor_escaped, + ci.position, + ) { + | (Backspace, true, _, _) => + prevent_stop_inject( + Update.Action.RemoveUserNewline(ci.node_steps), + ) + | (_, true, _, _) => prevent_stop_inject(update) + | (_, false, true, _) + | (_, false, _, OnDelim(_, _) | Staging(_)) => + prevent_stop_inject(update) + | (_, false, false, OnText(_)) => + let nodeValue = JSUtil.force_get_anchor_node_value(); + let anchorOffset = JSUtil.get_anchor_offset(); + let ctrlKey = Js.to_bool(evt##.ctrlKey); + let (nodeValue', anchorOffset') = + string_edit(nodeValue, anchorOffset, ctrlKey); + switch ( + String.equal(nodeValue', ""), + int_of_string_opt(nodeValue'), + ) { + | (true, _) => prevent_stop_inject(update) + | (false, Some(new_n)) => + prevent_stop_inject( + Update.Action.EditAction( + Construct(SNumLit(new_n, OnText(anchorOffset'))), + ), + ) + | (false, None) => + Var.is_valid(nodeValue') + ? prevent_stop_inject( + Update.Action.EditAction( + Construct( + SVar(nodeValue', OnText(anchorOffset')), + ), + ), + ) + : prevent_stop_inject( + Update.Action.InvalidVar(nodeValue'), + ) + }; + }; + | (OnText(_) | OnDelim(_, _), _, _, Some(Enter)) => + switch ( + model.user_newlines + |> CursorPath.StepsMap.mem(ci.node_steps), + model |> Model.zblock |> ZExp.is_after_case_rule, + model |> Model.zblock |> ZExp.is_on_user_newlineable_hole, + ) { + | (false, false, true) => + prevent_stop_inject( + Update.Action.AddUserNewline(ci.node_steps), + ) + | (_, _, _) => + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, Enter), + ), + ) + } + | (Staging(_), _, _, Some(Escape)) => + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, Enter), + ), + ) + | (_, _, _, Some(kc)) => + switch (kc) { + | Ctrl_Z => prevent_stop_inject(Update.Action.Undo) + | Ctrl_Shift_Z => prevent_stop_inject(Update.Action.Redo) + | _ => + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, kc), + ), + ) + } + }; + }), + ], + [ + model.is_cell_focused + ? Code.view_of_zblock( + ~inject, + ~user_newlines=model.user_newlines, + model |> Model.zblock, + ) + : Code.view_of_block( + ~inject, + ~user_newlines=model.user_newlines, + model |> Model.block, + ), + ...CursorIndicators.view( + ~is_cell_focused=model.is_cell_focused, + ~holes_steps= + model |> Model.block |> CursorIndicators.collect_holes, + ~ci=model.cursor_info, + ), + ], + ), + ], + ) + ); +}; + +let elem = () => JSUtil.force_get_elem_by_id(cell_id); From 34f4cb7dac0728fb7ff99e9562be836770197f6b Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 2 Jan 2020 17:34:49 -0500 Subject: [PATCH 04/60] integrate into new module UndoHistory --- src/hazelweb/Model.re | 80 ++-- src/hazelweb/UndoHistory.re | 27 ++ src/hazelweb/Update.re | 21 +- src/hazelweb/gui/Cell.re | 802 ++++++++++++++++++------------------ 4 files changed, 475 insertions(+), 455 deletions(-) create mode 100644 src/hazelweb/UndoHistory.re diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 5ec9f6f0b6..fa12bcb1b1 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -86,9 +86,6 @@ type result_state = | ResultsDisabled | Result(has_result_state); -[@deriving sexp] -type history = ZList.t(Statics.edit_state, Statics.edit_state); - type t = { cardstacks, cardstacks_state /* these are derived from the cardstack state: */, @@ -103,29 +100,7 @@ type t = { right_sidebar_open: bool, show_content_editable: bool, show_presentation: bool, - history, -}; - -let add_history = (history: history, edit_state: Statics.edit_state): history => { - /* first add new edit state to the end, then shift_next */ - let add_new = ( - ZList.prj_prefix(history), - ZList.prj_z(history), - [edit_state], - ); - ZList.shift_next(add_new); -}; -let undo_edit_state = (history): option(history) => { - switch (ZList.prj_prefix(history)) { - | [] => None - | _ => Some(ZList.shift_prev(history)) - }; -}; -let redo_edit_state = (history): option(history) => { - switch (ZList.prj_suffix(history)) { - | [] => None - | _ => Some(ZList.shift_next(history)) - }; + undo_history: UndoHistory.t, }; let cardstack_state_of = model => ZList.prj_z(model.cardstacks_state); @@ -397,7 +372,7 @@ let init = (): t => { user_newlines: CursorPath.StepsMap.empty, selected_example: None, is_cell_focused: false, - history: ([], edit_state, []), + undo_history: ([], edit_state, []), left_sidebar_open: false, right_sidebar_open: true, show_content_editable: false, @@ -427,7 +402,8 @@ let perform_edit_action = (model: t, a: Action.t): t => { | UpdateApPalette(_) | Delete | Backspace - | Construct(_) => add_history(model.history, new_edit_state) + | Construct(_) => + UndoHistory.add_history(model.undo_history, new_edit_state) | MoveTo(_) | MoveToBefore(_) | MoveLeft @@ -437,10 +413,10 @@ let perform_edit_action = (model: t, a: Action.t): t => { | ShiftLeft | ShiftRight | ShiftUp - | ShiftDown => model.history + | ShiftDown => model.undo_history }; }; - {...new_model, history: new_history}; + {...new_model, undo_history: new_history}; }; }; @@ -459,28 +435,28 @@ let move_to_hole = (model: t, u: MetaVar.t): t => { }; }; -let undo = (model: t): t => { - let new_history = - switch (undo_edit_state(model.history)) { - | Some(his) => his - | None => model.history - }; - - let new_edit_state = ZList.prj_z(new_history); - let new_model = model |> update_edit_state(new_edit_state); - {...new_model, history: new_history}; -}; - -let redo = (model: t): t => { - let new_history = - switch (redo_edit_state(model.history)) { - | Some(his) => his - | None => model.history - }; - let new_edit_state = ZList.prj_z(new_history); - let new_model = model |> update_edit_state(new_edit_state); - {...new_model, history: new_history}; -}; +/* let undo = (model: t): t => { + let new_history = + switch (undo_edit_state(model.undo_history)) { + | Some(his) => his + | None => model.undo_history + }; + + let new_edit_state = ZList.prj_z(new_history); + let new_model = model |> update_edit_state(new_edit_state); + {...new_model, undo_history: new_history}; + }; */ + +/* let redo = (model: t): t => { + let new_history = + switch (redo_edit_state(model.undo_history)) { + | Some(his) => his + | None => model.undo_history + }; + let new_edit_state = ZList.prj_z(new_history); + let new_model = model |> update_edit_state(new_edit_state); + {...new_model, undo_history: new_history}; + }; */ let select_hole_instance = (model, (u, i) as inst) => { switch (model.result_state) { | ResultsDisabled => model diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re new file mode 100644 index 0000000000..2cb1f05934 --- /dev/null +++ b/src/hazelweb/UndoHistory.re @@ -0,0 +1,27 @@ +module ZList = GeneralUtil.ZList; + +type t = ZList.t(Statics.edit_state, Statics.edit_state); + +let add_history = (undo_history: t, edit_state: Statics.edit_state): t => { + /* first add new edit state to the end, then shift_next */ + let add_new = ( + ZList.prj_prefix(undo_history), + ZList.prj_z(undo_history), + [edit_state], + ); + ZList.shift_next(add_new); +}; + +let undo_edit_state = (undo_history: t): option(t) => { + switch (ZList.prj_prefix(undo_history)) { + | [] => None + | _ => Some(ZList.shift_prev(undo_history)) + }; +}; + +let redo_edit_state = (undo_history: t): option(t) => { + switch (ZList.prj_suffix(undo_history)) { + | [] => None + | _ => Some(ZList.shift_next(undo_history)) + }; +}; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index ede5565c18..b2e3b0939b 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -296,7 +296,24 @@ let apply_action = }; }; model; - | Redo => Model.redo(model) - | Undo => Model.undo(model) + | Redo => + let new_history = + switch (UndoHistory.redo_edit_state(model.undo_history)) { + | Some(his) => his + | None => model.undo_history + }; + let new_edit_state = ZList.prj_z(new_history); + let new_model = model |> Model.update_edit_state(new_edit_state); + {...new_model, undo_history: new_history}; + | Undo => + let new_history = + switch (UndoHistory.undo_edit_state(model.undo_history)) { + | Some(his) => his + | None => model.undo_history + }; + + let new_edit_state = ZList.prj_z(new_history); + let new_model = model |> Model.update_edit_state(new_edit_state); + {...new_model, undo_history: new_history}; }; }; diff --git a/src/hazelweb/gui/Cell.re b/src/hazelweb/gui/Cell.re index e334ae6f8f..a6f1d9c651 100644 --- a/src/hazelweb/gui/Cell.re +++ b/src/hazelweb/gui/Cell.re @@ -1,401 +1,401 @@ -module Vdom = Virtual_dom.Vdom; -module Dom = Js_of_ocaml.Dom; -module Dom_html = Js_of_ocaml.Dom_html; -module Js = Js_of_ocaml.Js; -module Sexp = Sexplib.Sexp; -module KeyCombo = JSUtil.KeyCombo; -open GeneralUtil; -open ViewUtil; -open Sexplib.Std; - -let string_insert = (s1, offset, s2) => { - let prefix = String.sub(s1, 0, offset); - let length = String.length(s1); - let suffix = String.sub(s1, offset, length - offset); - prefix ++ s2 ++ suffix; -}; - -let string_backspace = (s, offset, ctrlKey) => { - let prefix = ctrlKey ? "" : String.sub(s, 0, offset - 1); - let length = String.length(s); - let suffix = String.sub(s, offset, length - offset); - let offset' = ctrlKey ? 0 : offset - 1; - (prefix ++ suffix, offset'); -}; - -let string_delete = (s, offset, ctrlKey) => { - let prefix = String.sub(s, 0, offset); - let length = String.length(s); - let suffix = ctrlKey ? "" : String.sub(s, offset + 1, length - offset - 1); - (prefix ++ suffix, offset); -}; - -let kc_actions: Hashtbl.t(KeyCombo.t, CursorInfo.node => Action.t) = - Hashtbl.of_seq( - [ - (KeyCombo.Backspace, _ => Action.Backspace), - (KeyCombo.Delete, _ => Action.Delete), - (KeyCombo.ShiftTab, _ => Action.MoveToPrevHole), - (KeyCombo.Tab, _ => Action.MoveToNextHole), - (KeyCombo.Key_N, _ => Action.Construct(SNum)), - (KeyCombo.Key_B, _ => Action.Construct(SBool)), - ( - KeyCombo.GT, - fun - | CursorInfo.Typ(_) => Action.Construct(SOp(SArrow)) - | _ => Action.Construct(SOp(SGreaterThan)), - ), - (KeyCombo.Ampersand, _ => Action.Construct(SOp(SAnd))), - (KeyCombo.VBar, _ => Action.Construct(SOp(SOr))), - (KeyCombo.Key_L, _ => Action.Construct(SList)), - (KeyCombo.LeftParen, _ => Action.Construct(SParenthesized)), - (KeyCombo.Colon, _ => Action.Construct(SAsc)), - (KeyCombo.Equals, _ => Action.Construct(SOp(SEquals))), - (KeyCombo.Enter, _ => Action.Construct(SLine)), - (KeyCombo.Backslash, _ => Action.Construct(SLam)), - (KeyCombo.Plus, _ => Action.Construct(SOp(SPlus))), - (KeyCombo.Minus, _ => Action.Construct(SOp(SMinus))), - (KeyCombo.Asterisk, _ => Action.Construct(SOp(STimes))), - (KeyCombo.LT, _ => Action.Construct(SOp(SLessThan))), - (KeyCombo.Space, _ => Action.Construct(SOp(SSpace))), - (KeyCombo.Comma, _ => Action.Construct(SOp(SComma))), - (KeyCombo.LeftBracket, _ => Action.Construct(SListNil)), - (KeyCombo.Semicolon, _ => Action.Construct(SOp(SCons))), - (KeyCombo.Alt_L, _ => Action.Construct(SInj(L))), - (KeyCombo.Alt_R, _ => Action.Construct(SInj(R))), - (KeyCombo.Alt_C, _ => Action.Construct(SCase)), - ] - |> List.to_seq, - ); -let entered_single_key = - ( - ~prevent_stop_inject, - ci: CursorInfo.t, - single_key: JSUtil.single_key, - opt_kc: option(KeyCombo.t), - ) - : option(Vdom.Event.t) => - switch (ci.node, opt_kc) { - | (Typ(_), Some((Key_B | Key_L | Key_N) as kc)) => - Some( - prevent_stop_inject( - Update.Action.EditAction(ci.node |> Hashtbl.find(kc_actions, kc)), - ), - ) - | (Pat(OtherPat(EmptyHole(_))), _) => - let shape = - switch (single_key) { - | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) - | Letter(x) => Action.SVar(x, OnText(Var.length(x))) - | Underscore => Action.SWild - }; - Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); - | (Pat(OtherPat(Wild(_))), _) => - let shape = - switch (single_key) { - | Number(n) => - Action.SVar("_" ++ string_of_int(n), OnText(num_digits(n) + 1)) - | Letter(x) => Action.SVar("_" ++ x, OnText(Var.length(x) + 1)) - | Underscore => Action.SVar("__", OnText(2)) - }; - Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); - | (Line(EmptyLine | ExpLine(EmptyHole(_))) | Exp(EmptyHole(_)), _) => - let shape = - switch (single_key) { - | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) - | Letter(x) => Action.SVar(x, OnText(Var.length(x))) - | Underscore => Action.SVar("_", OnText(1)) - }; - Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); - | ( - Exp(NumLit(_, _) | BoolLit(_, _) | Var(_, _, _)) | - Pat(OtherPat(NumLit(_, _) | BoolLit(_, _)) | VarPat(_, _)), - _, - ) => - let (nodeValue, anchorOffset) = - switch (ci.node, ci.position) { - | (Exp(NumLit(_, n)) | Pat(OtherPat(NumLit(_, n))), OnText(j)) => ( - string_of_int(n), - j, - ) - | (Exp(BoolLit(_, b)) | Pat(OtherPat(BoolLit(_, b))), OnText(j)) => ( - b ? "true" : "false", - j, - ) - | (Exp(Var(_, _, x)) | Pat(VarPat(x, _)), OnText(j)) => (x, j) - | (_, _) => assert(false) - }; - let key_string = JSUtil.single_key_string(single_key); - let newNodeValue = string_insert(nodeValue, anchorOffset, key_string); - switch (int_of_string_opt(newNodeValue), single_key) { - | (Some(_), Underscore) => - // OCaml accepts and ignores underscores - // when parsing ints from strings, we don't - Some(Vdom.Event.Ignore) - | (Some(new_n), _) => - Some( - // defensive check in case OCaml is - // doing any other weird things - num_digits(new_n) != String.length(newNodeValue) - ? Vdom.Event.Ignore - : prevent_stop_inject( - Update.Action.EditAction( - Action.Construct( - Action.SNumLit(new_n, OnText(anchorOffset + 1)), - ), - ), - ), - ) - | (None, _) => - Some( - Var.is_valid(newNodeValue) - ? prevent_stop_inject( - Update.Action.EditAction( - Action.Construct( - Action.SVar(newNodeValue, OnText(anchorOffset + 1)), - ), - ), - ) - : prevent_stop_inject(Update.Action.InvalidVar(newNodeValue)), - ) - }; - | (Line(_) | Exp(_) | Rule(_) | Pat(_) | Typ(_), _) => None - }; - -let view = - (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t => { - Vdom.( - Node.div( - [ - Attr.id("pp_view"), - Attr.classes(["ModelExp"]), - Attr.create( - "style", - "font-size: " - ++ (font_size |> JSUtil.px) - ++ "; line-height: " - ++ string_of_float(line_height) - ++ "; padding: " - ++ (cell_padding |> JSUtil.px) - ++ "; border: " - ++ (cell_border |> JSUtil.px) - ++ " solid #CCC;", - ), - ], - [ - Node.div( - [ - Attr.id(cell_id), - Attr.create("contenteditable", "true"), - Attr.on("drop", _ => Event.Prevent_default), - Attr.on_focus(_ => inject(FocusCell)), - Attr.on_blur(_ => inject(BlurCell)), - Attr.on_keypress(evt => - switch ( - model.cursor_info.position, - JSUtil.is_movement_key(evt), - ) { - | (Staging(_), _) => Event.Prevent_default - | (OnText(_) | OnDelim(_, _), true) => Event.Many([]) - | (OnText(_) | OnDelim(_, _), false) => Event.Prevent_default - } - ), - Attr.on_keydown(evt => { - let prevent_stop_inject = a => - Vdom.Event.Many([ - Vdom.Event.Prevent_default, - Vdom.Event.Stop_propagation, - inject(a), - ]); - let ci = model.cursor_info; - switch ( - ci.position, - JSUtil.is_movement_key(evt), - JSUtil.is_single_key(evt), - KeyCombo.of_evt(evt), - ) { - | (Staging(_), true, _, _) => - switch (evt |> JSUtil.get_key) { - | "ArrowLeft" => - prevent_stop_inject(Update.Action.EditAction(ShiftLeft)) - | "ArrowRight" => - prevent_stop_inject(Update.Action.EditAction(ShiftRight)) - | "ArrowUp" => - prevent_stop_inject(Update.Action.EditAction(ShiftUp)) - | "ArrowDown" => - prevent_stop_inject(Update.Action.EditAction(ShiftDown)) - | _ => Event.Ignore - } - | (OnText(_) | OnDelim(_, _), true, _, _) => Event.Many([]) - | (_, _, None, None) => Event.Ignore - | (_, _, Some(single_key), opt_kc) => - switch ( - entered_single_key( - ~prevent_stop_inject, - ci, - single_key, - opt_kc, - ) - ) { - | Some(event) => event - | None => - let zblock = model |> Model.zblock; - switch (ci.position) { - | Staging(_) - | OnText(_) => Event.Ignore - | OnDelim(_, side) => - let move_cursor = - switch (side) { - | Before => ZExp.move_cursor_left_block - | After => ZExp.move_cursor_right_block - }; - switch (zblock |> move_cursor) { - | None => Event.Ignore - | Some(zblock) => - switch ( - CursorInfo.syn_cursor_info_block( - Contexts.empty, - zblock, - ) - ) { - | None => Event.Ignore - | Some(ci) => - switch ( - entered_single_key( - ~prevent_stop_inject, - ci, - single_key, - opt_kc, - ) - ) { - | None => Event.Ignore - | Some(event) => event - } - } - }; - }; - } - | (_, _, _, Some((Backspace | Delete) as kc)) => - let (string_edit, update, cursor_escaped) = - switch (kc) { - | Backspace => ( - string_backspace, - Update.Action.EditAction(Backspace), - ci |> CursorInfo.is_before_node, - ) - | _ => ( - string_delete, - Update.Action.EditAction(Delete), - ci |> CursorInfo.is_after_node, - ) - }; - switch ( - kc, - model.user_newlines - |> CursorPath.StepsMap.mem(ci.node_steps), - cursor_escaped, - ci.position, - ) { - | (Backspace, true, _, _) => - prevent_stop_inject( - Update.Action.RemoveUserNewline(ci.node_steps), - ) - | (_, true, _, _) => prevent_stop_inject(update) - | (_, false, true, _) - | (_, false, _, OnDelim(_, _) | Staging(_)) => - prevent_stop_inject(update) - | (_, false, false, OnText(_)) => - let nodeValue = JSUtil.force_get_anchor_node_value(); - let anchorOffset = JSUtil.get_anchor_offset(); - let ctrlKey = Js.to_bool(evt##.ctrlKey); - let (nodeValue', anchorOffset') = - string_edit(nodeValue, anchorOffset, ctrlKey); - switch ( - String.equal(nodeValue', ""), - int_of_string_opt(nodeValue'), - ) { - | (true, _) => prevent_stop_inject(update) - | (false, Some(new_n)) => - prevent_stop_inject( - Update.Action.EditAction( - Construct(SNumLit(new_n, OnText(anchorOffset'))), - ), - ) - | (false, None) => - Var.is_valid(nodeValue') - ? prevent_stop_inject( - Update.Action.EditAction( - Construct( - SVar(nodeValue', OnText(anchorOffset')), - ), - ), - ) - : prevent_stop_inject( - Update.Action.InvalidVar(nodeValue'), - ) - }; - }; - | (OnText(_) | OnDelim(_, _), _, _, Some(Enter)) => - switch ( - model.user_newlines - |> CursorPath.StepsMap.mem(ci.node_steps), - model |> Model.zblock |> ZExp.is_after_case_rule, - model |> Model.zblock |> ZExp.is_on_user_newlineable_hole, - ) { - | (false, false, true) => - prevent_stop_inject( - Update.Action.AddUserNewline(ci.node_steps), - ) - | (_, _, _) => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, Enter), - ), - ) - } - | (Staging(_), _, _, Some(Escape)) => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, Enter), - ), - ) - | (_, _, _, Some(kc)) => - switch (kc) { - | Ctrl_Z => prevent_stop_inject(Update.Action.Undo) - | Ctrl_Shift_Z => prevent_stop_inject(Update.Action.Redo) - | _ => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, kc), - ), - ) - } - }; - }), - ], - [ - model.is_cell_focused - ? Code.view_of_zblock( - ~inject, - ~user_newlines=model.user_newlines, - model |> Model.zblock, - ) - : Code.view_of_block( - ~inject, - ~user_newlines=model.user_newlines, - model |> Model.block, - ), - ...CursorIndicators.view( - ~is_cell_focused=model.is_cell_focused, - ~holes_steps= - model |> Model.block |> CursorIndicators.collect_holes, - ~ci=model.cursor_info, - ), - ], - ), - ], - ) - ); -}; - -let elem = () => JSUtil.force_get_elem_by_id(cell_id); +module Vdom = Virtual_dom.Vdom; +module Dom = Js_of_ocaml.Dom; +module Dom_html = Js_of_ocaml.Dom_html; +module Js = Js_of_ocaml.Js; +module Sexp = Sexplib.Sexp; +module KeyCombo = JSUtil.KeyCombo; +open GeneralUtil; +open ViewUtil; +open Sexplib.Std; + +let string_insert = (s1, offset, s2) => { + let prefix = String.sub(s1, 0, offset); + let length = String.length(s1); + let suffix = String.sub(s1, offset, length - offset); + prefix ++ s2 ++ suffix; +}; + +let string_backspace = (s, offset, ctrlKey) => { + let prefix = ctrlKey ? "" : String.sub(s, 0, offset - 1); + let length = String.length(s); + let suffix = String.sub(s, offset, length - offset); + let offset' = ctrlKey ? 0 : offset - 1; + (prefix ++ suffix, offset'); +}; + +let string_delete = (s, offset, ctrlKey) => { + let prefix = String.sub(s, 0, offset); + let length = String.length(s); + let suffix = ctrlKey ? "" : String.sub(s, offset + 1, length - offset - 1); + (prefix ++ suffix, offset); +}; + +let kc_actions: Hashtbl.t(KeyCombo.t, CursorInfo.node => Action.t) = + Hashtbl.of_seq( + [ + (KeyCombo.Backspace, _ => Action.Backspace), + (KeyCombo.Delete, _ => Action.Delete), + (KeyCombo.ShiftTab, _ => Action.MoveToPrevHole), + (KeyCombo.Tab, _ => Action.MoveToNextHole), + (KeyCombo.Key_N, _ => Action.Construct(SNum)), + (KeyCombo.Key_B, _ => Action.Construct(SBool)), + ( + KeyCombo.GT, + fun + | CursorInfo.Typ(_) => Action.Construct(SOp(SArrow)) + | _ => Action.Construct(SOp(SGreaterThan)), + ), + (KeyCombo.Ampersand, _ => Action.Construct(SOp(SAnd))), + (KeyCombo.VBar, _ => Action.Construct(SOp(SOr))), + (KeyCombo.Key_L, _ => Action.Construct(SList)), + (KeyCombo.LeftParen, _ => Action.Construct(SParenthesized)), + (KeyCombo.Colon, _ => Action.Construct(SAsc)), + (KeyCombo.Equals, _ => Action.Construct(SOp(SEquals))), + (KeyCombo.Enter, _ => Action.Construct(SLine)), + (KeyCombo.Backslash, _ => Action.Construct(SLam)), + (KeyCombo.Plus, _ => Action.Construct(SOp(SPlus))), + (KeyCombo.Minus, _ => Action.Construct(SOp(SMinus))), + (KeyCombo.Asterisk, _ => Action.Construct(SOp(STimes))), + (KeyCombo.LT, _ => Action.Construct(SOp(SLessThan))), + (KeyCombo.Space, _ => Action.Construct(SOp(SSpace))), + (KeyCombo.Comma, _ => Action.Construct(SOp(SComma))), + (KeyCombo.LeftBracket, _ => Action.Construct(SListNil)), + (KeyCombo.Semicolon, _ => Action.Construct(SOp(SCons))), + (KeyCombo.Alt_L, _ => Action.Construct(SInj(L))), + (KeyCombo.Alt_R, _ => Action.Construct(SInj(R))), + (KeyCombo.Alt_C, _ => Action.Construct(SCase)), + ] + |> List.to_seq, + ); +let entered_single_key = + ( + ~prevent_stop_inject, + ci: CursorInfo.t, + single_key: JSUtil.single_key, + opt_kc: option(KeyCombo.t), + ) + : option(Vdom.Event.t) => + switch (ci.node, opt_kc) { + | (Typ(_), Some((Key_B | Key_L | Key_N) as kc)) => + Some( + prevent_stop_inject( + Update.Action.EditAction(ci.node |> Hashtbl.find(kc_actions, kc)), + ), + ) + | (Pat(OtherPat(EmptyHole(_))), _) => + let shape = + switch (single_key) { + | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) + | Letter(x) => Action.SVar(x, OnText(Var.length(x))) + | Underscore => Action.SWild + }; + Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); + | (Pat(OtherPat(Wild(_))), _) => + let shape = + switch (single_key) { + | Number(n) => + Action.SVar("_" ++ string_of_int(n), OnText(num_digits(n) + 1)) + | Letter(x) => Action.SVar("_" ++ x, OnText(Var.length(x) + 1)) + | Underscore => Action.SVar("__", OnText(2)) + }; + Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); + | (Line(EmptyLine | ExpLine(EmptyHole(_))) | Exp(EmptyHole(_)), _) => + let shape = + switch (single_key) { + | Number(n) => Action.SNumLit(n, OnText(num_digits(n))) + | Letter(x) => Action.SVar(x, OnText(Var.length(x))) + | Underscore => Action.SVar("_", OnText(1)) + }; + Some(prevent_stop_inject(Update.Action.EditAction(Construct(shape)))); + | ( + Exp(NumLit(_, _) | BoolLit(_, _) | Var(_, _, _)) | + Pat(OtherPat(NumLit(_, _) | BoolLit(_, _)) | VarPat(_, _)), + _, + ) => + let (nodeValue, anchorOffset) = + switch (ci.node, ci.position) { + | (Exp(NumLit(_, n)) | Pat(OtherPat(NumLit(_, n))), OnText(j)) => ( + string_of_int(n), + j, + ) + | (Exp(BoolLit(_, b)) | Pat(OtherPat(BoolLit(_, b))), OnText(j)) => ( + b ? "true" : "false", + j, + ) + | (Exp(Var(_, _, x)) | Pat(VarPat(x, _)), OnText(j)) => (x, j) + | (_, _) => assert(false) + }; + let key_string = JSUtil.single_key_string(single_key); + let newNodeValue = string_insert(nodeValue, anchorOffset, key_string); + switch (int_of_string_opt(newNodeValue), single_key) { + | (Some(_), Underscore) => + // OCaml accepts and ignores underscores + // when parsing ints from strings, we don't + Some(Vdom.Event.Ignore) + | (Some(new_n), _) => + Some( + // defensive check in case OCaml is + // doing any other weird things + num_digits(new_n) != String.length(newNodeValue) + ? Vdom.Event.Ignore + : prevent_stop_inject( + Update.Action.EditAction( + Action.Construct( + Action.SNumLit(new_n, OnText(anchorOffset + 1)), + ), + ), + ), + ) + | (None, _) => + Some( + Var.is_valid(newNodeValue) + ? prevent_stop_inject( + Update.Action.EditAction( + Action.Construct( + Action.SVar(newNodeValue, OnText(anchorOffset + 1)), + ), + ), + ) + : prevent_stop_inject(Update.Action.InvalidVar(newNodeValue)), + ) + }; + | (Line(_) | Exp(_) | Rule(_) | Pat(_) | Typ(_), _) => None + }; + +let view = + (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t => { + Vdom.( + Node.div( + [ + Attr.id("pp_view"), + Attr.classes(["ModelExp"]), + Attr.create( + "style", + "font-size: " + ++ (font_size |> JSUtil.px) + ++ "; line-height: " + ++ string_of_float(line_height) + ++ "; padding: " + ++ (cell_padding |> JSUtil.px) + ++ "; border: " + ++ (cell_border |> JSUtil.px) + ++ " solid #CCC;", + ), + ], + [ + Node.div( + [ + Attr.id(cell_id), + Attr.create("contenteditable", "true"), + Attr.on("drop", _ => Event.Prevent_default), + Attr.on_focus(_ => inject(FocusCell)), + Attr.on_blur(_ => inject(BlurCell)), + Attr.on_keypress(evt => + switch ( + model.cursor_info.position, + JSUtil.is_movement_key(evt), + ) { + | (Staging(_), _) => Event.Prevent_default + | (OnText(_) | OnDelim(_, _), true) => Event.Many([]) + | (OnText(_) | OnDelim(_, _), false) => Event.Prevent_default + } + ), + Attr.on_keydown(evt => { + let prevent_stop_inject = a => + Vdom.Event.Many([ + Vdom.Event.Prevent_default, + Vdom.Event.Stop_propagation, + inject(a), + ]); + let ci = model.cursor_info; + switch ( + ci.position, + JSUtil.is_movement_key(evt), + JSUtil.is_single_key(evt), + KeyCombo.of_evt(evt), + ) { + | (Staging(_), true, _, _) => + switch (evt |> JSUtil.get_key) { + | "ArrowLeft" => + prevent_stop_inject(Update.Action.EditAction(ShiftLeft)) + | "ArrowRight" => + prevent_stop_inject(Update.Action.EditAction(ShiftRight)) + | "ArrowUp" => + prevent_stop_inject(Update.Action.EditAction(ShiftUp)) + | "ArrowDown" => + prevent_stop_inject(Update.Action.EditAction(ShiftDown)) + | _ => Event.Ignore + } + | (OnText(_) | OnDelim(_, _), true, _, _) => Event.Many([]) + | (_, _, None, None) => Event.Ignore + | (_, _, Some(single_key), opt_kc) => + switch ( + entered_single_key( + ~prevent_stop_inject, + ci, + single_key, + opt_kc, + ) + ) { + | Some(event) => event + | None => + let zblock = model |> Model.zblock; + switch (ci.position) { + | Staging(_) + | OnText(_) => Event.Ignore + | OnDelim(_, side) => + let move_cursor = + switch (side) { + | Before => ZExp.move_cursor_left_block + | After => ZExp.move_cursor_right_block + }; + switch (zblock |> move_cursor) { + | None => Event.Ignore + | Some(zblock) => + switch ( + CursorInfo.syn_cursor_info_block( + Contexts.empty, + zblock, + ) + ) { + | None => Event.Ignore + | Some(ci) => + switch ( + entered_single_key( + ~prevent_stop_inject, + ci, + single_key, + opt_kc, + ) + ) { + | None => Event.Ignore + | Some(event) => event + } + } + }; + }; + } + | (_, _, _, Some((Backspace | Delete) as kc)) => + let (string_edit, update, cursor_escaped) = + switch (kc) { + | Backspace => ( + string_backspace, + Update.Action.EditAction(Backspace), + ci |> CursorInfo.is_before_node, + ) + | _ => ( + string_delete, + Update.Action.EditAction(Delete), + ci |> CursorInfo.is_after_node, + ) + }; + switch ( + kc, + model.user_newlines + |> CursorPath.StepsMap.mem(ci.node_steps), + cursor_escaped, + ci.position, + ) { + | (Backspace, true, _, _) => + prevent_stop_inject( + Update.Action.RemoveUserNewline(ci.node_steps), + ) + | (_, true, _, _) => prevent_stop_inject(update) + | (_, false, true, _) + | (_, false, _, OnDelim(_, _) | Staging(_)) => + prevent_stop_inject(update) + | (_, false, false, OnText(_)) => + let nodeValue = JSUtil.force_get_anchor_node_value(); + let anchorOffset = JSUtil.get_anchor_offset(); + let ctrlKey = Js.to_bool(evt##.ctrlKey); + let (nodeValue', anchorOffset') = + string_edit(nodeValue, anchorOffset, ctrlKey); + switch ( + String.equal(nodeValue', ""), + int_of_string_opt(nodeValue'), + ) { + | (true, _) => prevent_stop_inject(update) + | (false, Some(new_n)) => + prevent_stop_inject( + Update.Action.EditAction( + Construct(SNumLit(new_n, OnText(anchorOffset'))), + ), + ) + | (false, None) => + Var.is_valid(nodeValue') + ? prevent_stop_inject( + Update.Action.EditAction( + Construct( + SVar(nodeValue', OnText(anchorOffset')), + ), + ), + ) + : prevent_stop_inject( + Update.Action.InvalidVar(nodeValue'), + ) + }; + }; + | (OnText(_) | OnDelim(_, _), _, _, Some(Enter)) => + switch ( + model.user_newlines + |> CursorPath.StepsMap.mem(ci.node_steps), + model |> Model.zblock |> ZExp.is_after_case_rule, + model |> Model.zblock |> ZExp.is_on_user_newlineable_hole, + ) { + | (false, false, true) => + prevent_stop_inject( + Update.Action.AddUserNewline(ci.node_steps), + ) + | (_, _, _) => + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, Enter), + ), + ) + } + | (Staging(_), _, _, Some(Escape)) => + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, Enter), + ), + ) + | (_, _, _, Some(kc)) => + switch (kc) { + | Ctrl_Z => prevent_stop_inject(Update.Action.Undo) + | Ctrl_Shift_Z => prevent_stop_inject(Update.Action.Redo) + | _ => + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, kc), + ), + ) + } + }; + }), + ], + [ + model.is_cell_focused + ? Code.view_of_zblock( + ~inject, + ~user_newlines=model.user_newlines, + model |> Model.zblock, + ) + : Code.view_of_block( + ~inject, + ~user_newlines=model.user_newlines, + model |> Model.block, + ), + ...CursorIndicators.view( + ~is_cell_focused=model.is_cell_focused, + ~holes_steps= + model |> Model.block |> CursorIndicators.collect_holes, + ~ci=model.cursor_info, + ), + ], + ), + ], + ) + ); +}; + +let elem = () => JSUtil.force_get_elem_by_id(cell_id); From 8b160422763a9aa32933fd003da932bc5746259b Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 2 Jan 2020 17:57:13 -0500 Subject: [PATCH 05/60] Test zoe's ssh --- src/hazelweb/UndoHistory.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 2cb1f05934..d0cf943a77 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -25,3 +25,4 @@ let redo_edit_state = (undo_history: t): option(t) => { | _ => Some(ZList.shift_next(undo_history)) }; }; +/*hello*/ \ No newline at end of file From 2e14accc12249b978a5dc50475b204bd5c69e379 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 3 Jan 2020 13:31:46 -0500 Subject: [PATCH 06/60] modify Zlist.shift_next and prev to return option type --- src/hazelcore/GeneralUtil.re | 12 ++++++------ src/hazelweb/Model.re | 36 +++++++++++------------------------- src/hazelweb/UndoHistory.re | 26 ++++++++++++++------------ src/hazelweb/Update.re | 13 ++----------- src/hazelweb/gui/Cell.re | 19 +++++++++---------- 5 files changed, 42 insertions(+), 64 deletions(-) diff --git a/src/hazelcore/GeneralUtil.re b/src/hazelcore/GeneralUtil.re index 7e2026981f..dc4300c5c2 100644 --- a/src/hazelcore/GeneralUtil.re +++ b/src/hazelcore/GeneralUtil.re @@ -286,23 +286,23 @@ module ZList = { prefix @ [a, ...suffix]; }; - let shift_next = (zxs: t('a, 'a)) => { + let shift_next = (zxs: t('a, 'a)): option(t('a, 'a)) => { let (prefix, z, suffix) = zxs; switch (suffix) { - | [] => zxs + | [] => None | [next, ...suffix] => let prefix = prefix @ [z]; - (prefix, next, suffix); + Some((prefix, next, suffix)); }; }; - let shift_prev = (zxs: t('a, 'a)) => { + let shift_prev = (zxs: t('a, 'a)): option(t('a, 'a)) => { let (prefix, z, suffix) = zxs; switch (List.rev(prefix)) { - | [] => zxs + | [] => None | [prev, ...rev_prefix] => let suffix = [z, ...suffix]; - (List.rev(rev_prefix), prev, suffix); + Some((List.rev(rev_prefix), prev, suffix)); }; }; }; diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index fa12bcb1b1..e0494743e3 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -336,7 +336,11 @@ let prev_card = model => { let cardstack_state = cardstack_state_of(model); let cardstack_state = { ...cardstack_state, - zcards: ZList.shift_prev(cardstack_state.zcards), + zcards: + switch (ZList.shift_prev(cardstack_state.zcards)) { + | None => cardstack_state.zcards + | Some(card) => card + }, }; { ...update_cardstack_state(model, cardstack_state), @@ -349,7 +353,11 @@ let next_card = model => { let cardstack_state = cardstack_state_of(model); let cardstack_state = { ...cardstack_state, - zcards: ZList.shift_next(cardstack_state.zcards), + zcards: + switch (ZList.shift_next(cardstack_state.zcards)) { + | None => cardstack_state.zcards + | Some(card) => card + }, }; { ...update_cardstack_state(model, cardstack_state), @@ -403,7 +411,7 @@ let perform_edit_action = (model: t, a: Action.t): t => { | Delete | Backspace | Construct(_) => - UndoHistory.add_history(model.undo_history, new_edit_state) + UndoHistory.push_edit_state(model.undo_history, new_edit_state) | MoveTo(_) | MoveToBefore(_) | MoveLeft @@ -435,28 +443,6 @@ let move_to_hole = (model: t, u: MetaVar.t): t => { }; }; -/* let undo = (model: t): t => { - let new_history = - switch (undo_edit_state(model.undo_history)) { - | Some(his) => his - | None => model.undo_history - }; - - let new_edit_state = ZList.prj_z(new_history); - let new_model = model |> update_edit_state(new_edit_state); - {...new_model, undo_history: new_history}; - }; */ - -/* let redo = (model: t): t => { - let new_history = - switch (redo_edit_state(model.undo_history)) { - | Some(his) => his - | None => model.undo_history - }; - let new_edit_state = ZList.prj_z(new_history); - let new_model = model |> update_edit_state(new_edit_state); - {...new_model, undo_history: new_history}; - }; */ let select_hole_instance = (model, (u, i) as inst) => { switch (model.result_state) { | ResultsDisabled => model diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index d0cf943a77..e0c700504c 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -2,27 +2,29 @@ module ZList = GeneralUtil.ZList; type t = ZList.t(Statics.edit_state, Statics.edit_state); -let add_history = (undo_history: t, edit_state: Statics.edit_state): t => { +let push_edit_state = (undo_history: t, edit_state: Statics.edit_state): t => { /* first add new edit state to the end, then shift_next */ - let add_new = ( + let after_push = ( ZList.prj_prefix(undo_history), ZList.prj_z(undo_history), [edit_state], ); - ZList.shift_next(add_new); + switch (ZList.shift_next(after_push)) { + | None => after_push + | Some(new_history) => new_history + }; }; -let undo_edit_state = (undo_history: t): option(t) => { - switch (ZList.prj_prefix(undo_history)) { - | [] => None - | _ => Some(ZList.shift_prev(undo_history)) +let undo_edit_state = (undo_history: t): t => { + switch (ZList.shift_prev(undo_history)) { + | None => undo_history + | Some(new_history) => new_history }; }; -let redo_edit_state = (undo_history: t): option(t) => { - switch (ZList.prj_suffix(undo_history)) { - | [] => None - | _ => Some(ZList.shift_next(undo_history)) +let redo_edit_state = (undo_history: t): t => { + switch (ZList.shift_next(undo_history)) { + | None => undo_history + | Some(new_history) => new_history }; }; -/*hello*/ \ No newline at end of file diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index b2e3b0939b..c260f9ba76 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -297,21 +297,12 @@ let apply_action = }; model; | Redo => - let new_history = - switch (UndoHistory.redo_edit_state(model.undo_history)) { - | Some(his) => his - | None => model.undo_history - }; + let new_history = UndoHistory.redo_edit_state(model.undo_history); let new_edit_state = ZList.prj_z(new_history); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; | Undo => - let new_history = - switch (UndoHistory.undo_edit_state(model.undo_history)) { - | Some(his) => his - | None => model.undo_history - }; - + let new_history = UndoHistory.undo_edit_state(model.undo_history); let new_edit_state = ZList.prj_z(new_history); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; diff --git a/src/hazelweb/gui/Cell.re b/src/hazelweb/gui/Cell.re index a6f1d9c651..10b1adf3f0 100644 --- a/src/hazelweb/gui/Cell.re +++ b/src/hazelweb/gui/Cell.re @@ -359,17 +359,16 @@ let view = ci.node |> Hashtbl.find(kc_actions, Enter), ), ) + | (_, _, _, Some(Ctrl_Z)) => + prevent_stop_inject(Update.Action.Undo) + | (_, _, _, Some(Ctrl_Shift_Z)) => + prevent_stop_inject(Update.Action.Redo) | (_, _, _, Some(kc)) => - switch (kc) { - | Ctrl_Z => prevent_stop_inject(Update.Action.Undo) - | Ctrl_Shift_Z => prevent_stop_inject(Update.Action.Redo) - | _ => - prevent_stop_inject( - Update.Action.EditAction( - ci.node |> Hashtbl.find(kc_actions, kc), - ), - ) - } + prevent_stop_inject( + Update.Action.EditAction( + ci.node |> Hashtbl.find(kc_actions, kc), + ), + ) }; }), ], From 8d2aba427cdbf004bb85ca98530b621b19b15cc7 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 3 Jan 2020 19:11:43 -0500 Subject: [PATCH 07/60] small adjustment --- src/hazelweb/UndoHistory.re | 6 +++--- src/hazelweb/Update.re | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index e0c700504c..d73a8afa4d 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -10,19 +10,19 @@ let push_edit_state = (undo_history: t, edit_state: Statics.edit_state): t => { [edit_state], ); switch (ZList.shift_next(after_push)) { - | None => after_push + | None => failwith("Impossible because suffix is non-empty") | Some(new_history) => new_history }; }; -let undo_edit_state = (undo_history: t): t => { +let undo = (undo_history: t): t => { switch (ZList.shift_prev(undo_history)) { | None => undo_history | Some(new_history) => new_history }; }; -let redo_edit_state = (undo_history: t): t => { +let redo = (undo_history: t): t => { switch (ZList.shift_next(undo_history)) { | None => undo_history | Some(new_history) => new_history diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index c260f9ba76..c0519c0316 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -86,8 +86,8 @@ let log_action = (action: Action.t, _: State.t): unit => { | AddUserNewline(_) | RemoveUserNewline(_) | MoveToHole(_) - | Redo - | Undo => + | Undo + | Redo => Logger.append( Sexp.to_string( sexp_of_timestamped_action(mk_timestamped_action(action)), @@ -296,13 +296,13 @@ let apply_action = }; }; model; - | Redo => - let new_history = UndoHistory.redo_edit_state(model.undo_history); + | Undo => + let new_history = UndoHistory.undo(model.undo_history); let new_edit_state = ZList.prj_z(new_history); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; - | Undo => - let new_history = UndoHistory.undo_edit_state(model.undo_history); + | Redo => + let new_history = UndoHistory.redo(model.undo_history); let new_edit_state = ZList.prj_z(new_history); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; From d37d48cac22b088ba7d2470ca3a57ccfd73e7f63 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 3 Jan 2020 19:36:35 -0500 Subject: [PATCH 08/60] add function undoable_action --- src/hazelweb/Model.re | 23 +++++------------------ src/hazelweb/UndoHistory.re | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index e0494743e3..ed35af96a9 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -405,25 +405,12 @@ let perform_edit_action = (model: t, a: Action.t): t => { | CantShift => raise(CantShift) | Succeeded(new_edit_state) => let new_model = model |> update_edit_state(new_edit_state); - let new_history = { - switch (a) { - | UpdateApPalette(_) - | Delete - | Backspace - | Construct(_) => - UndoHistory.push_edit_state(model.undo_history, new_edit_state) - | MoveTo(_) - | MoveToBefore(_) - | MoveLeft - | MoveRight - | MoveToNextHole - | MoveToPrevHole - | ShiftLeft - | ShiftRight - | ShiftUp - | ShiftDown => model.undo_history + let new_history = + if (UndoHistory.undoable_action(a)) { + UndoHistory.push_edit_state(model.undo_history, new_edit_state); + } else { + model.undo_history; }; - }; {...new_model, undo_history: new_history}; }; }; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index d73a8afa4d..46f617b123 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -28,3 +28,22 @@ let redo = (undo_history: t): t => { | Some(new_history) => new_history }; }; + +let undoable_action = (action: Action.t): bool => { + switch (action) { + | UpdateApPalette(_) + | Delete + | Backspace + | Construct(_) => true + | MoveTo(_) + | MoveToBefore(_) + | MoveLeft + | MoveRight + | MoveToNextHole + | MoveToPrevHole + | ShiftLeft + | ShiftRight + | ShiftUp + | ShiftDown => false + }; +}; From c42da0184eb719082b71ade508ffe64966c2f6ef Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 4 Jan 2020 09:44:48 -0500 Subject: [PATCH 09/60] undo and redo button --- src/hazelweb/gui/Page.re | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index a4273bd57e..c37df36714 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -84,7 +84,29 @@ let next_card_button = (~inject, model: Model.t) => { ) ); }; +let undo_button = (~inject) => { + Vdom.( + Node.button( + [ + Attr.id("undo-button"), + Attr.on_click(_ => inject(Update.Action.Undo)), + ], + [Node.text("Undo")], + ) + ); +}; +let redo_button = (~inject) => { + Vdom.( + Node.button( + [ + Attr.id("redo-button"), + Attr.on_click(_ => inject(Update.Action.Redo)), + ], + [Node.text("Redo")], + ) + ); +}; let cardstack_controls = (~inject, model: Model.t) => Vdom.( Node.div( @@ -95,6 +117,8 @@ let cardstack_controls = (~inject, model: Model.t) => [ prev_card_button(~inject, model), next_card_button(~inject, model), + undo_button(~inject), + redo_button(~inject), ], ), ], From 94b007b445bd12d8f2bcea954d404a627f4fb6b7 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 4 Jan 2020 17:59:03 -0500 Subject: [PATCH 10/60] history panel --- src/hazelweb/gui/Page.re | 1 + src/hazelweb/gui/UndoHistoryList.re | 416 ++++++++++++++++++++++++++++ 2 files changed, 417 insertions(+) create mode 100644 src/hazelweb/gui/UndoHistoryList.re diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index c37df36714..3b7f76921c 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -302,6 +302,7 @@ let page_view = [ CursorInspector.view(~inject, model), ContextInspector.view(~inject, model), + UndoHistoryList.view(~inject, model), OptionsPanel.view(~inject, model), ], ), diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re new file mode 100644 index 0000000000..84095075c8 --- /dev/null +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -0,0 +1,416 @@ +module Vdom = Virtual_dom.Vdom; +module KeyCombo = JSUtil.KeyCombo; + +exception InvalidInstance; +/* let view = + /* (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t */ () => { + let button_view = Vdom.Node.div([], []); + let context_view = Vdom.Node.div([], []); + Vdom.( + Node.div( + [Attr.classes(["panel", "context-inspector-panel"])], + [ + Panel.view_of_main_title_bar("history"), + Node.div([ + [Attr.classes(["panel-body", "context-inspector-body"])], + [Node.div([], [Node.text("ahhhh")])], + ]), + ], + ) + ); + }; */ + +let view = + (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t => { + let static_info = ((x, ty)) => { + Vdom.( + Node.div( + [Attr.classes(["static-info"])], + [ + Node.div( + [Attr.classes(["code"])], + [ + Node.span([Attr.classes(["var"])], [Node.text(x)]), + Node.text(" : "), + Code.view_of_htyp(~inject, ty), + ], + ), + ], + ) + ); + }; + + let dynamic_info = (sigma, x) => + switch (VarMap.lookup(sigma, x)) { + | None => + Some( + Vdom.( + Node.div( + [Attr.classes(["dynamic-info"])], + [ + Node.div( + [Attr.classes(["code"])], + [Node.span([], [Node.text("NONE!!!!!!")])], + ), + ], + ) + ), + ) + | Some(Dynamics.DHExp.BoundVar(x')) when Var.eq(x, x') => None + | Some(d) => + Some( + Vdom.( + Node.div( + [Attr.classes(["dynamic-info"])], + [ + Node.div( + [Attr.classes(["code"])], + [Code.view_of_dhexp(~inject, d)], + ), + ], + ) + ), + ) + }; + + let context_entry = (sigma, (x, ty)) => { + let static_info = static_info((x, ty)); + let children = + switch (dynamic_info(sigma, x)) { + | Some(dynamic_info) => [static_info, dynamic_info] + | None => [static_info] + }; + Vdom.(Node.div([Attr.classes(["context-entry"])], children)); + }; + + let context_view = { + let ctx = Contexts.gamma(model.cursor_info.ctx); + let sigma = + switch (model.result_state) { + | ResultsDisabled => Dynamics.DHExp.id_env(ctx) + | Result(has_result_state) => + let (_, hii, _) = has_result_state.result; + switch (has_result_state.selected_instance) { + | None => Dynamics.DHExp.id_env(ctx) + | Some(inst) => + switch (Dynamics.DHExp.HoleInstanceInfo.lookup(hii, inst)) { + | None => raise(InvalidInstance) + | Some((sigma, _)) => sigma + } + }; + }; + switch (VarCtx.to_list(ctx)) { + | [] => + Vdom.( + Node.div( + [Attr.classes(["the-context"])], + [ + Vdom.( + Node.div( + [Attr.classes(["context-is-empty-msg"])], + [Node.text("no variables in scope")], + ) + ), + ], + ) + ) + | ctx_lst => + Vdom.( + Node.div( + [Attr.classes(["the-context"])], + List.map(context_entry(sigma), ctx_lst), + ) + ) + }; + }; + + let path_view_titlebar = + Panel.view_of_other_title_bar("Closure above observed at "); + let instructional_msg = msg => + Vdom.( + Node.div([Attr.classes(["instructional-msg"])], [Node.text(msg)]) + ); + let view_of_path_item = ((inst, x)) => + Vdom.( + Node.div( + [Attr.classes(["path-item"])], + [ + Node.div( + [Attr.classes(["inst"])], + [Code.view_of_hole_instance(~inject, inst)], + ), + Node.div( + [Attr.classes(["inst-var-separator"])], + [Node.text("·")], + ), + Node.div( + [Attr.classes(["path-var"])], + [Code.view_of_Var(~inject, x)], + ), + ], + ) + ); + + let path_view = (inst, path: Dynamics.DHExp.InstancePath.t) => { + let (titlebar_txt, path_area_children) = + switch (path) { + | [] => ( + "which is in the result", + [ + Vdom.( + Node.div( + [Attr.classes(["special-msg"])], + [Node.div([], [Node.text("immediately")])], + ) + ), + ], + ) + | _ => + let titlebar_txt = "which is in the result via path"; + let path_area_children = + List.fold_left( + (acc, path_item) => + [ + view_of_path_item(path_item), + Vdom.( + Node.span( + [Attr.classes(["path-item-separator"])], + [Node.text(" 〉 ")], + ) + ), + ...acc, + ], + [ + Vdom.( + Node.div( + [Attr.classes(["trailing-inst"])], + [Code.view_of_hole_instance(~inject, inst)], + ) + ), + ], + path, + ); + + ( + titlebar_txt, + [ + Vdom.( + Node.div([Attr.classes(["path-area"])], path_area_children) + ), + ], + ); + }; + + Vdom.( + Node.div( + [Attr.classes(["path-view-with-path"])], + [ + Panel.view_of_other_title_bar(titlebar_txt), + Node.div( + [Attr.classes(["path-area-parent"])], + path_area_children, + ), + ], + ) + ); + }; + + let hii_summary = + (hii, (u_, i_) as inst, context_inspector: Model.context_inspector) => { + let num_instances = + Dynamics.DHExp.HoleInstanceInfo.num_instances(hii, u_); + let msg = + Vdom.( + Node.div( + [Attr.classes(["instance-info"])], + [ + Node.div( + [], + [ + Node.div( + [Attr.classes(["hii-summary-inst"])], + [Code.view_of_hole_instance(~inject, inst)], + ), + Node.text(" = hole "), + Node.span( + [Attr.classes(["hole-name-normal-txt"])], + [Node.text(string_of_int(u_ + 1))], + ), + Node.text(" instance "), + Node.span( + [Attr.classes(["inst-number-normal-txt"])], + [Node.text(string_of_int(i_ + 1))], + ), + Node.text(" of "), + Node.span( + [Attr.classes(["inst-number-normal-txt"])], + [Node.text(string_of_int(num_instances))], + ), + ], + ), + ], + ) + ); + + let prev_key = KeyCombo.Details.alt_PageUp; + let next_key = KeyCombo.Details.alt_PageDown; + + let prev_title = + "Previous instance (" ++ KeyCombo.Details.name(prev_key) ++ ")"; + let next_title = + "Next instance (" ++ KeyCombo.Details.name(next_key) ++ ")"; + + let prev_btn = + switch (context_inspector.prev_state) { + | Some((u, i)) => + Vdom.( + Node.div( + [ + Attr.create("title", prev_title), + Attr.classes(["instance-button-wrapper"]), + Attr.on_click(_ => inject(SelectHoleInstance(u, i))), + Attr.on_keydown(ev => { + let updates = + KeyCombo.Details.matches(prev_key, ev) + ? [inject(SelectHoleInstance(u, i))] : []; + Event.Many([Event.Prevent_default, ...updates]); + }), + ], + [ + SvgShapes.left_arrow( + ["prev-instance", "has-prev", "noselect"], + (), + ), + ], + ) + ) + | None => + Vdom.( + Node.div( + [ + Attr.create("title", prev_title), + Attr.classes(["instance-button-wrapper"]), + ], + [ + SvgShapes.left_arrow( + ["prev-instance", "no-prev", "noselect"], + (), + ), + ], + ) + ) + }; + + let next_btn = + switch (context_inspector.next_state) { + | Some((u, i)) => + Vdom.( + Node.div( + [ + Attr.create("title", next_title), + Attr.classes(["instance-button-wrapper"]), + Attr.on_click(_ => inject(SelectHoleInstance(u, i))), + Attr.on_keydown(ev => { + let updates = + KeyCombo.Details.matches(next_key, ev) + ? [inject(SelectHoleInstance(u, i))] : []; + Event.Many([Event.Prevent_default, ...updates]); + }), + ], + [ + SvgShapes.right_arrow( + ["next-instance", "has-next", "noselect"], + (), + ), + ], + ) + ) + | None => + Vdom.( + Node.div( + [ + Attr.create("title", next_title), + Attr.classes(["instance-button-wrapper"]), + ], + [ + SvgShapes.right_arrow( + ["next-instance", "no-next", "noselect"], + (), + ), + ], + ) + ) + }; + + let controls = + Vdom.( + Node.div( + [Attr.classes(["instance-controls"])], + [prev_btn, next_btn], + ) + ); + + Vdom.(Node.div([Attr.classes(["path-summary"])], [msg, controls])); + }; + + let path_viewer = { + switch (model.result_state) { + | ResultsDisabled => Vdom.Node.div([], []) + | Result(has_result_state) => + let (_, hii, _) = has_result_state.result; + if (VarMap.is_empty(Contexts.gamma(model.cursor_info.ctx))) { + Vdom.Node.div([], []); + } else { + let children = + switch (model.cursor_info.node) { + | CursorInfo.Exp(EmptyHole(u)) => + switch (has_result_state.selected_instance) { + | Some((u', _) as inst) => + if (MetaVar.eq(u, u')) { + switch (Dynamics.DHExp.HoleInstanceInfo.lookup(hii, inst)) { + | Some((_, path)) => [ + path_view_titlebar, + hii_summary( + hii, + inst, + has_result_state.context_inspector, + ), + path_view(inst, path), + ] + | None => raise(InvalidInstance) + }; + } else { + [ + instructional_msg( + "Internal Error: cursor is not at the selected hole instance.", + ), + ]; + } + | None => [ + instructional_msg("Click on a hole instance in the result"), + ] + } + | _ => [ + instructional_msg( + "Move cursor to a hole, or click a hole instance in the result, to see closures.", + ), + ] + }; + Vdom.(Node.div([Attr.classes(["the-path-viewer"])], children)); + }; + }; + }; + + Vdom.( + Node.div( + [Attr.classes(["panel", "context-inspector-panel"])], + [ + Panel.view_of_main_title_bar("context"), + Node.div( + [Attr.classes(["panel-body", "context-inspector-body"])], + [context_view, path_viewer], + ), + ], + ) + ); +}; From defd7ec26bd0d064b546b1598e61ae1219bd91b5 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 7 Jan 2020 14:44:03 -0500 Subject: [PATCH 11/60] history panel draft --- src/hazelweb/gui/Page.re | 2 +- src/hazelweb/gui/UndoHistoryList.re | 403 +--------------------------- 2 files changed, 10 insertions(+), 395 deletions(-) diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index 3b7f76921c..ae6c02bf67 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -302,7 +302,7 @@ let page_view = [ CursorInspector.view(~inject, model), ContextInspector.view(~inject, model), - UndoHistoryList.view(~inject, model), + UndoHistoryList.view(), OptionsPanel.view(~inject, model), ], ), diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 84095075c8..c8a77379e1 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -2,413 +2,28 @@ module Vdom = Virtual_dom.Vdom; module KeyCombo = JSUtil.KeyCombo; exception InvalidInstance; -/* let view = - /* (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t */ () => { - let button_view = Vdom.Node.div([], []); - let context_view = Vdom.Node.div([], []); - Vdom.( - Node.div( - [Attr.classes(["panel", "context-inspector-panel"])], - [ - Panel.view_of_main_title_bar("history"), - Node.div([ - [Attr.classes(["panel-body", "context-inspector-body"])], - [Node.div([], [Node.text("ahhhh")])], - ]), - ], - ) - ); - }; */ - -let view = - (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t => { - let static_info = ((x, ty)) => { - Vdom.( - Node.div( - [Attr.classes(["static-info"])], - [ - Node.div( - [Attr.classes(["code"])], - [ - Node.span([Attr.classes(["var"])], [Node.text(x)]), - Node.text(" : "), - Code.view_of_htyp(~inject, ty), - ], - ), - ], - ) - ); - }; - - let dynamic_info = (sigma, x) => - switch (VarMap.lookup(sigma, x)) { - | None => - Some( - Vdom.( - Node.div( - [Attr.classes(["dynamic-info"])], - [ - Node.div( - [Attr.classes(["code"])], - [Node.span([], [Node.text("NONE!!!!!!")])], - ), - ], - ) - ), - ) - | Some(Dynamics.DHExp.BoundVar(x')) when Var.eq(x, x') => None - | Some(d) => - Some( - Vdom.( - Node.div( - [Attr.classes(["dynamic-info"])], - [ - Node.div( - [Attr.classes(["code"])], - [Code.view_of_dhexp(~inject, d)], - ), - ], - ) - ), - ) - }; - - let context_entry = (sigma, (x, ty)) => { +let history_entry = (sigma, (x, ty)) => { let static_info = static_info((x, ty)); let children = switch (dynamic_info(sigma, x)) { | Some(dynamic_info) => [static_info, dynamic_info] | None => [static_info] }; - Vdom.(Node.div([Attr.classes(["context-entry"])], children)); - }; - - let context_view = { - let ctx = Contexts.gamma(model.cursor_info.ctx); - let sigma = - switch (model.result_state) { - | ResultsDisabled => Dynamics.DHExp.id_env(ctx) - | Result(has_result_state) => - let (_, hii, _) = has_result_state.result; - switch (has_result_state.selected_instance) { - | None => Dynamics.DHExp.id_env(ctx) - | Some(inst) => - switch (Dynamics.DHExp.HoleInstanceInfo.lookup(hii, inst)) { - | None => raise(InvalidInstance) - | Some((sigma, _)) => sigma - } - }; - }; - switch (VarCtx.to_list(ctx)) { - | [] => - Vdom.( - Node.div( - [Attr.classes(["the-context"])], - [ - Vdom.( - Node.div( - [Attr.classes(["context-is-empty-msg"])], - [Node.text("no variables in scope")], - ) - ), - ], - ) - ) - | ctx_lst => - Vdom.( - Node.div( - [Attr.classes(["the-context"])], - List.map(context_entry(sigma), ctx_lst), - ) - ) - }; - }; - - let path_view_titlebar = - Panel.view_of_other_title_bar("Closure above observed at "); - let instructional_msg = msg => - Vdom.( - Node.div([Attr.classes(["instructional-msg"])], [Node.text(msg)]) - ); - let view_of_path_item = ((inst, x)) => - Vdom.( - Node.div( - [Attr.classes(["path-item"])], - [ - Node.div( - [Attr.classes(["inst"])], - [Code.view_of_hole_instance(~inject, inst)], - ), - Node.div( - [Attr.classes(["inst-var-separator"])], - [Node.text("·")], - ), - Node.div( - [Attr.classes(["path-var"])], - [Code.view_of_Var(~inject, x)], - ), - ], - ) - ); - - let path_view = (inst, path: Dynamics.DHExp.InstancePath.t) => { - let (titlebar_txt, path_area_children) = - switch (path) { - | [] => ( - "which is in the result", - [ - Vdom.( - Node.div( - [Attr.classes(["special-msg"])], - [Node.div([], [Node.text("immediately")])], - ) - ), - ], - ) - | _ => - let titlebar_txt = "which is in the result via path"; - let path_area_children = - List.fold_left( - (acc, path_item) => - [ - view_of_path_item(path_item), - Vdom.( - Node.span( - [Attr.classes(["path-item-separator"])], - [Node.text(" 〉 ")], - ) - ), - ...acc, - ], - [ - Vdom.( - Node.div( - [Attr.classes(["trailing-inst"])], - [Code.view_of_hole_instance(~inject, inst)], - ) - ), - ], - path, - ); - - ( - titlebar_txt, - [ - Vdom.( - Node.div([Attr.classes(["path-area"])], path_area_children) - ), - ], - ); - }; - - Vdom.( - Node.div( - [Attr.classes(["path-view-with-path"])], - [ - Panel.view_of_other_title_bar(titlebar_txt), - Node.div( - [Attr.classes(["path-area-parent"])], - path_area_children, - ), - ], - ) - ); + Vdom.(Node.div([Attr.classes(["history-entry"])], children)); }; - - let hii_summary = - (hii, (u_, i_) as inst, context_inspector: Model.context_inspector) => { - let num_instances = - Dynamics.DHExp.HoleInstanceInfo.num_instances(hii, u_); - let msg = - Vdom.( - Node.div( - [Attr.classes(["instance-info"])], - [ - Node.div( - [], - [ - Node.div( - [Attr.classes(["hii-summary-inst"])], - [Code.view_of_hole_instance(~inject, inst)], - ), - Node.text(" = hole "), - Node.span( - [Attr.classes(["hole-name-normal-txt"])], - [Node.text(string_of_int(u_ + 1))], - ), - Node.text(" instance "), - Node.span( - [Attr.classes(["inst-number-normal-txt"])], - [Node.text(string_of_int(i_ + 1))], - ), - Node.text(" of "), - Node.span( - [Attr.classes(["inst-number-normal-txt"])], - [Node.text(string_of_int(num_instances))], - ), - ], - ), - ], - ) - ); - - let prev_key = KeyCombo.Details.alt_PageUp; - let next_key = KeyCombo.Details.alt_PageDown; - - let prev_title = - "Previous instance (" ++ KeyCombo.Details.name(prev_key) ++ ")"; - let next_title = - "Next instance (" ++ KeyCombo.Details.name(next_key) ++ ")"; - - let prev_btn = - switch (context_inspector.prev_state) { - | Some((u, i)) => - Vdom.( - Node.div( - [ - Attr.create("title", prev_title), - Attr.classes(["instance-button-wrapper"]), - Attr.on_click(_ => inject(SelectHoleInstance(u, i))), - Attr.on_keydown(ev => { - let updates = - KeyCombo.Details.matches(prev_key, ev) - ? [inject(SelectHoleInstance(u, i))] : []; - Event.Many([Event.Prevent_default, ...updates]); - }), - ], - [ - SvgShapes.left_arrow( - ["prev-instance", "has-prev", "noselect"], - (), - ), - ], - ) - ) - | None => - Vdom.( - Node.div( - [ - Attr.create("title", prev_title), - Attr.classes(["instance-button-wrapper"]), - ], - [ - SvgShapes.left_arrow( - ["prev-instance", "no-prev", "noselect"], - (), - ), - ], - ) - ) - }; - - let next_btn = - switch (context_inspector.next_state) { - | Some((u, i)) => - Vdom.( - Node.div( - [ - Attr.create("title", next_title), - Attr.classes(["instance-button-wrapper"]), - Attr.on_click(_ => inject(SelectHoleInstance(u, i))), - Attr.on_keydown(ev => { - let updates = - KeyCombo.Details.matches(next_key, ev) - ? [inject(SelectHoleInstance(u, i))] : []; - Event.Many([Event.Prevent_default, ...updates]); - }), - ], - [ - SvgShapes.right_arrow( - ["next-instance", "has-next", "noselect"], - (), - ), - ], - ) - ) - | None => - Vdom.( - Node.div( - [ - Attr.create("title", next_title), - Attr.classes(["instance-button-wrapper"]), - ], - [ - SvgShapes.right_arrow( - ["next-instance", "no-next", "noselect"], - (), - ), - ], - ) - ) - }; - - let controls = - Vdom.( - Node.div( - [Attr.classes(["instance-controls"])], - [prev_btn, next_btn], - ) - ); - - Vdom.(Node.div([Attr.classes(["path-summary"])], [msg, controls])); - }; - - let path_viewer = { - switch (model.result_state) { - | ResultsDisabled => Vdom.Node.div([], []) - | Result(has_result_state) => - let (_, hii, _) = has_result_state.result; - if (VarMap.is_empty(Contexts.gamma(model.cursor_info.ctx))) { - Vdom.Node.div([], []); - } else { - let children = - switch (model.cursor_info.node) { - | CursorInfo.Exp(EmptyHole(u)) => - switch (has_result_state.selected_instance) { - | Some((u', _) as inst) => - if (MetaVar.eq(u, u')) { - switch (Dynamics.DHExp.HoleInstanceInfo.lookup(hii, inst)) { - | Some((_, path)) => [ - path_view_titlebar, - hii_summary( - hii, - inst, - has_result_state.context_inspector, - ), - path_view(inst, path), - ] - | None => raise(InvalidInstance) - }; - } else { - [ - instructional_msg( - "Internal Error: cursor is not at the selected hole instance.", - ), - ]; - } - | None => [ - instructional_msg("Click on a hole instance in the result"), - ] - } - | _ => [ - instructional_msg( - "Move cursor to a hole, or click a hole instance in the result, to see closures.", - ), - ] - }; - Vdom.(Node.div([Attr.classes(["the-path-viewer"])], children)); - }; - }; - }; - + +let view = + /* (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t */ () => { + /* let button_view = Vdom.Node.div([], []); + let context_view = Vdom.Node.div([], []); */ Vdom.( Node.div( [Attr.classes(["panel", "context-inspector-panel"])], [ - Panel.view_of_main_title_bar("context"), + Panel.view_of_main_title_bar("history"), Node.div( [Attr.classes(["panel-body", "context-inspector-body"])], - [context_view, path_viewer], + [Node.div([], [Node.text("ahhhh")])], ), ], ) From 7bf037889e62c5177a091ec1c9909d91e53c5022 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 7 Jan 2020 15:43:37 -0500 Subject: [PATCH 12/60] history panel --- src/hazelweb/Model.re | 8 ++++++-- src/hazelweb/UndoHistory.re | 16 ++++++++++++---- src/hazelweb/Update.re | 4 ++-- src/hazelweb/gui/UndoHistoryList.re | 27 +++++++++++++-------------- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index ed35af96a9..a930e90bac 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -380,7 +380,7 @@ let init = (): t => { user_newlines: CursorPath.StepsMap.empty, selected_example: None, is_cell_focused: false, - undo_history: ([], edit_state, []), + undo_history: ([], (edit_state, None, 0), []), left_sidebar_open: false, right_sidebar_open: true, show_content_editable: false, @@ -407,7 +407,11 @@ let perform_edit_action = (model: t, a: Action.t): t => { let new_model = model |> update_edit_state(new_edit_state); let new_history = if (UndoHistory.undoable_action(a)) { - UndoHistory.push_edit_state(model.undo_history, new_edit_state); + UndoHistory.push_edit_state( + model.undo_history, + new_edit_state, + Some(a), + ); } else { model.undo_history; }; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 46f617b123..1e0a5ce3fa 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -1,13 +1,21 @@ module ZList = GeneralUtil.ZList; +/*new edit state, the previous action, id*/ +type undo_history_entry = (Statics.edit_state, option(Action.t), int); +type t = ZList.t(undo_history_entry, undo_history_entry); -type t = ZList.t(Statics.edit_state, Statics.edit_state); - -let push_edit_state = (undo_history: t, edit_state: Statics.edit_state): t => { +let push_edit_state = + ( + undo_history: t, + edit_state: Statics.edit_state, + action: option(Action.t), + ) + : t => { + let (_, _, last_id) = ZList.prj_z(undo_history); /* first add new edit state to the end, then shift_next */ let after_push = ( ZList.prj_prefix(undo_history), ZList.prj_z(undo_history), - [edit_state], + [(edit_state, action, last_id + 1)], ); switch (ZList.shift_next(after_push)) { | None => failwith("Impossible because suffix is non-empty") diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index c0519c0316..daf65a54c6 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -298,12 +298,12 @@ let apply_action = model; | Undo => let new_history = UndoHistory.undo(model.undo_history); - let new_edit_state = ZList.prj_z(new_history); + let (new_edit_state, _, _) = ZList.prj_z(new_history); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; | Redo => let new_history = UndoHistory.redo(model.undo_history); - let new_edit_state = ZList.prj_z(new_history); + let (new_edit_state, _, _) = ZList.prj_z(new_history); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; }; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index c8a77379e1..b06636dfb5 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -2,20 +2,19 @@ module Vdom = Virtual_dom.Vdom; module KeyCombo = JSUtil.KeyCombo; exception InvalidInstance; -let history_entry = (sigma, (x, ty)) => { - let static_info = static_info((x, ty)); - let children = - switch (dynamic_info(sigma, x)) { - | Some(dynamic_info) => [static_info, dynamic_info] - | None => [static_info] - }; - Vdom.(Node.div([Attr.classes(["history-entry"])], children)); - }; - -let view = - /* (~inject: Update.Action.t => Vdom.Event.t, model: Model.t): Vdom.Node.t */ () => { - /* let button_view = Vdom.Node.div([], []); - let context_view = Vdom.Node.div([], []); */ +/* let history_entry = (history: UndoHistory.t) => { + let (_, option(Action.t), id) = history; + switch + let static_info = static_info((x, ty)); + let children = + switch (dynamic_info(sigma, x)) { + | Some(dynamic_info) => [static_info, dynamic_info] + | None => [static_info] + }; + Vdom.(Node.div([Attr.classes(["history-entry"])], children)); + }; */ + +let view = () => { Vdom.( Node.div( [Attr.classes(["panel", "context-inspector-panel"])], From 31a100d9436a91b98443fd3029e39bff3cad374a Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 9 Jan 2020 20:00:36 -0500 Subject: [PATCH 13/60] basic history panel without group --- src/hazelcore/semantics/Action.re | 18 ++++- src/hazelweb/UndoHistory.re | 10 ++- src/hazelweb/gui/Page.re | 2 +- src/hazelweb/gui/UndoHistoryList.re | 101 ++++++++++++++++++++++++---- src/hazelweb/www/style.css | 7 ++ 5 files changed, 119 insertions(+), 19 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index 4ac2ea627d..73753eae0d 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -17,7 +17,23 @@ type op_shape = | SCons | SAnd | SOr; - +let op_shape_to_string = (op: op_shape) => { + switch (op) { + | SMinus => "-" + | SPlus => "+" + | STimes => "*" + | SLessThan => "<" + | SGreaterThan => ">" + | SEquals => "=" + | SSpace => "[space]" + | SComma => "," + | SArrow => "=>" + | SVBar => "[VBar]" + | SCons => "[Cons]" + | SAnd => "&" + | SOr => "|" + }; +}; let ty_op_of = (os: op_shape): option(UHTyp.op) => switch (os) { | SArrow => Some(Arrow) diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 1e0a5ce3fa..b9ab6880fc 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -39,10 +39,14 @@ let redo = (undo_history: t): t => { let undoable_action = (action: Action.t): bool => { switch (action) { - | UpdateApPalette(_) + | UpdateApPalette(_) => + JSUtil.log("UpdateApPalette!!!"); + true; | Delete - | Backspace - | Construct(_) => true + | Backspace => true + | Construct(_) => + JSUtil.log("Construct!!!"); + true; | MoveTo(_) | MoveToBefore(_) | MoveLeft diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index 29b0187ac0..a78f755b40 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -291,7 +291,7 @@ let page_view = [ CursorInspector.view(~inject, model), ContextInspector.view(~inject, model), - UndoHistoryList.view(), + UndoHistoryList.view(model), OptionsPanel.view(~inject, model), ], ), diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index b06636dfb5..0cf49061e5 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -1,20 +1,93 @@ module Vdom = Virtual_dom.Vdom; module KeyCombo = JSUtil.KeyCombo; - +module ZList = GeneralUtil.ZList; exception InvalidInstance; -/* let history_entry = (history: UndoHistory.t) => { - let (_, option(Action.t), id) = history; - switch - let static_info = static_info((x, ty)); - let children = - switch (dynamic_info(sigma, x)) { - | Some(dynamic_info) => [static_info, dynamic_info] - | None => [static_info] - }; - Vdom.(Node.div([Attr.classes(["history-entry"])], children)); - }; */ -let view = () => { +let shape_to_string = (shape: Action.shape): string => { + switch (shape) { + | SParenthesized => "parentheise" + /* type shapes */ + | SNum => "number" + | SBool => "bool" + | SList => "list" + /* expression shapes */ + | SAsc => "???" + | SVar(varstr, _) => "var: " ++ varstr // convert int to char? ++ Char.chr(intvar) + | SLam => "lambada?" + | SNumLit(value, _) => "numlit? " ++ string_of_int(value) + | SListNil => "listNil" + | SInj(direction) => + switch (direction) { + | L => "inject left" + | R => "inject right" + } + | SLet => "let" + | SLine => "a new line" + | SCase => "case" + | SOp(op) => "operator " ++ Action.op_shape_to_string(op) + | SApPalette(_) => "appalette?" + /* pattern-only shapes */ + | SWild => "wild?" + }; +}; +let action_to_stirng = (action: Action.t) => { + switch (action) { + | UpdateApPalette(_) => "updatePlate?" + | Delete => "delete" + | Backspace => "backspace" + | Construct(shape) => "add " ++ shape_to_string(shape) + | MoveTo(_) + | MoveToBefore(_) + | MoveLeft + | MoveRight + | MoveToNextHole + | MoveToPrevHole + | ShiftLeft + | ShiftRight + | ShiftUp + | ShiftDown => "" + }; +}; +let history_entry_view = undo_history_entry => { + let (_, action, _) = undo_history_entry; + let txt = + switch (action) { + | None => "no action" + | Some(detail_ac) => action_to_stirng(detail_ac) + }; + Vdom.(Node.div([], [Node.text(txt)])); +}; +let history_view = (model: Model.t) => { + let erase_func = + (undo_history_entry: UndoHistory.undo_history_entry) + : UndoHistory.undo_history_entry => undo_history_entry; + let history = ZList.erase(model.undo_history, erase_func); + switch (history) { + | [] => + Vdom.( + Node.div( + [Attr.classes(["the-context"])], + [ + Vdom.( + Node.div( + [Attr.classes(["context-is-empty-msg"])], + [Node.text("no variables in scope")], + ) + ), + ], + ) + ) + | his_lst => + Vdom.( + Node.div( + [Attr.classes(["the-context"])], + List.map(history_entry_view, List.rev(his_lst)), + ) + ) + }; +}; + +let view = (model: Model.t) => { Vdom.( Node.div( [Attr.classes(["panel", "context-inspector-panel"])], @@ -22,7 +95,7 @@ let view = () => { Panel.view_of_main_title_bar("history"), Node.div( [Attr.classes(["panel-body", "context-inspector-body"])], - [Node.div([], [Node.text("ahhhh")])], + [history_view(model)], ), ], ) diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index c37f65e03a..551c1d2dfd 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -502,6 +502,13 @@ html, body { border-right: 0px; } +.history-entry { + font-size: 125%; + border: 1px outset #ffffff; + border-left: 0px; + border-right: 0px; +} + .instructional-msg { background-color: var(--title-bar-color); padding: 5px; From db0edabbfb7212c735a7489c283ece67c95c760a Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 9 Jan 2020 21:04:57 -0500 Subject: [PATCH 14/60] set different classes to prev and suc history --- src/hazelweb/gui/UndoHistoryList.re | 56 ++++++++++++++++++++--------- src/hazelweb/www/style.css | 21 +++++++++++ 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 0cf49061e5..9621d2cf5c 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -50,38 +50,62 @@ let action_to_stirng = (action: Action.t) => { }; let history_entry_view = undo_history_entry => { let (_, action, _) = undo_history_entry; - let txt = - switch (action) { - | None => "no action" - | Some(detail_ac) => action_to_stirng(detail_ac) - }; - Vdom.(Node.div([], [Node.text(txt)])); + switch (action) { + | None => Vdom.(Node.div([], [])) + | Some(detail_ac) => + Vdom.(Node.div([], [Node.text(action_to_stirng(detail_ac))])) + }; +}; +let prev_history_view = history => { + Vdom.( + Node.div( + [Attr.classes(["the-prev-history"])], + List.map(history_entry_view, history), + ) + ); +}; +let suc_history_view = history => { + Vdom.( + Node.div( + [Attr.classes(["the-suc-history"])], + List.map(history_entry_view, history), + ) + ); +}; +let now_history_view = (history: UndoHistory.undo_history_entry) => { + Vdom.( + Node.div( + [Attr.classes(["the-now-history"])], + [history_entry_view(history)], + ) + ); }; let history_view = (model: Model.t) => { - let erase_func = - (undo_history_entry: UndoHistory.undo_history_entry) - : UndoHistory.undo_history_entry => undo_history_entry; - let history = ZList.erase(model.undo_history, erase_func); - switch (history) { - | [] => + let (prev_his, now, suc_his) = model.undo_history; + switch (now) { + | (_, None, _) => Vdom.( Node.div( [Attr.classes(["the-context"])], [ Vdom.( Node.div( - [Attr.classes(["context-is-empty-msg"])], - [Node.text("no variables in scope")], + [Attr.classes(["history-is-empty-msg"])], + [Node.text("no history in scope")], ) ), ], ) ) - | his_lst => + | (_, Some(_), _) => Vdom.( Node.div( [Attr.classes(["the-context"])], - List.map(history_entry_view, List.rev(his_lst)), + [ + prev_history_view(prev_his), + now_history_view(now), + suc_history_view(suc_his), + ], ) ) }; diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 551c1d2dfd..93db5191ea 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1199,3 +1199,24 @@ button:disabled { flex-direction: column; align-content: flex-start; } + +/* edit action history panel */ + +.history-is-empty-msg { + margin-top: 10px; + opacity: 0.50; + text-align: center; + /* text-transform: uppercase; */ + font-size: 75%; +} + +.the-prev-history { + color: #0d0a05; +} + +.the-suc-history { + color: gray; +} +.the-now-history { + color: red; +} \ No newline at end of file From 2e3dd0e354e77b415211cb72ae67202165b8ea94 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 9 Jan 2020 22:01:51 -0500 Subject: [PATCH 15/60] click to go to certain edit-state --- src/hazelcore/semantics/Action.re | 2 +- src/hazelweb/Update.re | 16 ++- src/hazelweb/gui/Page.re | 2 +- src/hazelweb/gui/UndoHistoryList.re | 202 ++++++++++++++-------------- src/hazelweb/www/style.css | 4 +- 5 files changed, 122 insertions(+), 104 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index 73753eae0d..b4ea050e81 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -29,7 +29,7 @@ let op_shape_to_string = (op: op_shape) => { | SComma => "," | SArrow => "=>" | SVBar => "[VBar]" - | SCons => "[Cons]" + | SCons => "::" | SAnd => "&" | SOr => "|" }; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index daf65a54c6..085501c051 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -30,7 +30,8 @@ module Action = { | AddUserNewline(CursorPath.steps) | RemoveUserNewline(CursorPath.steps) | Redo - | Undo; + | Undo + | ShiftHistory(int); }; [@deriving sexp] @@ -87,7 +88,8 @@ let log_action = (action: Action.t, _: State.t): unit => { | RemoveUserNewline(_) | MoveToHole(_) | Undo - | Redo => + | Redo + | ShiftHistory(_) => Logger.append( Sexp.to_string( sexp_of_timestamped_action(mk_timestamped_action(action)), @@ -306,5 +308,15 @@ let apply_action = let (new_edit_state, _, _) = ZList.prj_z(new_history); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; + | ShiftHistory(n) => + let erase_func = his => his; + let his_lst = ZList.erase(model.undo_history, erase_func); + switch (ZList.split_at(n, his_lst)) { + | None => failwith("Impossible because undo_history is non-empty") + | Some(new_history) => + let (new_edit_state, _, _) = ZList.prj_z(new_history); + let new_model = model |> Model.update_edit_state(new_edit_state); + {...new_model, undo_history: new_history}; + }; }; }; diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index a78f755b40..87cd337c7c 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -291,7 +291,7 @@ let page_view = [ CursorInspector.view(~inject, model), ContextInspector.view(~inject, model), - UndoHistoryList.view(model), + UndoHistoryList.view(~inject, model), OptionsPanel.view(~inject, model), ], ), diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 9621d2cf5c..9201a0b2b0 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -3,115 +3,119 @@ module KeyCombo = JSUtil.KeyCombo; module ZList = GeneralUtil.ZList; exception InvalidInstance; -let shape_to_string = (shape: Action.shape): string => { - switch (shape) { - | SParenthesized => "parentheise" - /* type shapes */ - | SNum => "number" - | SBool => "bool" - | SList => "list" - /* expression shapes */ - | SAsc => "???" - | SVar(varstr, _) => "var: " ++ varstr // convert int to char? ++ Char.chr(intvar) - | SLam => "lambada?" - | SNumLit(value, _) => "numlit? " ++ string_of_int(value) - | SListNil => "listNil" - | SInj(direction) => - switch (direction) { - | L => "inject left" - | R => "inject right" - } - | SLet => "let" - | SLine => "a new line" - | SCase => "case" - | SOp(op) => "operator " ++ Action.op_shape_to_string(op) - | SApPalette(_) => "appalette?" - /* pattern-only shapes */ - | SWild => "wild?" +let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { + let shape_to_string = (shape: Action.shape): string => { + switch (shape) { + | SParenthesized => "parentheise" + /* type shapes */ + | SNum => "number" + | SBool => "bool" + | SList => "list" + /* expression shapes */ + | SAsc => "???" + | SVar(varstr, _) => "var: " ++ varstr // convert int to char? ++ Char.chr(intvar) + | SLam => "lambada?" + | SNumLit(value, _) => "numlit? " ++ string_of_int(value) + | SListNil => "listNil" + | SInj(direction) => + switch (direction) { + | L => "inject left" + | R => "inject right" + } + | SLet => "let" + | SLine => "a new line" + | SCase => "case" + | SOp(op) => "operator " ++ Action.op_shape_to_string(op) + | SApPalette(_) => "appalette?" + /* pattern-only shapes */ + | SWild => "wild?" + }; }; -}; -let action_to_stirng = (action: Action.t) => { - switch (action) { - | UpdateApPalette(_) => "updatePlate?" - | Delete => "delete" - | Backspace => "backspace" - | Construct(shape) => "add " ++ shape_to_string(shape) - | MoveTo(_) - | MoveToBefore(_) - | MoveLeft - | MoveRight - | MoveToNextHole - | MoveToPrevHole - | ShiftLeft - | ShiftRight - | ShiftUp - | ShiftDown => "" + let action_to_stirng = (action: Action.t) => { + switch (action) { + | UpdateApPalette(_) => "updatePlate?" + | Delete => "delete" + | Backspace => "backspace" + | Construct(shape) => "add " ++ shape_to_string(shape) + | MoveTo(_) + | MoveToBefore(_) + | MoveLeft + | MoveRight + | MoveToNextHole + | MoveToPrevHole + | ShiftLeft + | ShiftRight + | ShiftUp + | ShiftDown => "" + }; }; -}; -let history_entry_view = undo_history_entry => { - let (_, action, _) = undo_history_entry; - switch (action) { - | None => Vdom.(Node.div([], [])) - | Some(detail_ac) => - Vdom.(Node.div([], [Node.text(action_to_stirng(detail_ac))])) + let history_entry_view = undo_history_entry => { + let (_, action, id) = undo_history_entry; + switch (action) { + | None => Vdom.(Node.div([], [])) + | Some(detail_ac) => + Vdom.( + Node.div( + [Attr.on_click(_ => inject(Update.Action.ShiftHistory(id)))], + [Node.text(action_to_stirng(detail_ac))], + ) + ) + }; }; -}; -let prev_history_view = history => { - Vdom.( - Node.div( - [Attr.classes(["the-prev-history"])], - List.map(history_entry_view, history), - ) - ); -}; -let suc_history_view = history => { - Vdom.( - Node.div( - [Attr.classes(["the-suc-history"])], - List.map(history_entry_view, history), - ) - ); -}; -let now_history_view = (history: UndoHistory.undo_history_entry) => { - Vdom.( - Node.div( - [Attr.classes(["the-now-history"])], - [history_entry_view(history)], - ) - ); -}; -let history_view = (model: Model.t) => { - let (prev_his, now, suc_his) = model.undo_history; - switch (now) { - | (_, None, _) => + let prev_history_view = history => { Vdom.( Node.div( - [Attr.classes(["the-context"])], - [ - Vdom.( - Node.div( - [Attr.classes(["history-is-empty-msg"])], - [Node.text("no history in scope")], - ) - ), - ], + [Attr.classes(["the-prev-history"])], + List.map(history_entry_view, history), ) - ) - | (_, Some(_), _) => + ); + }; + let suc_history_view = history => { Vdom.( Node.div( - [Attr.classes(["the-context"])], - [ - prev_history_view(prev_his), - now_history_view(now), - suc_history_view(suc_his), - ], + [Attr.classes(["the-suc-history"])], + List.map(history_entry_view, history), ) - ) + ); + }; + let now_history_view = (history: UndoHistory.undo_history_entry) => { + Vdom.( + Node.div( + [Attr.classes(["the-now-history"])], + [history_entry_view(history)], + ) + ); + }; + let history_view = (model: Model.t) => { + let (prev_his, now, suc_his) = model.undo_history; + switch (now) { + | (_, None, _) => + Vdom.( + Node.div( + [Attr.classes(["the-history"])], + [ + Vdom.( + Node.div( + [Attr.classes(["history-is-empty-msg"])], + [Node.text("no history in scope")], + ) + ), + ], + ) + ) + | (_, Some(_), _) => + Vdom.( + Node.div( + [Attr.classes(["the-history"])], + [ + suc_history_view(List.rev(suc_his)), + now_history_view(now), + prev_history_view(List.rev(prev_his)), + ], + ) + ) + }; }; -}; - -let view = (model: Model.t) => { Vdom.( Node.div( [Attr.classes(["panel", "context-inspector-panel"])], diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 93db5191ea..6cd9e62226 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1219,4 +1219,6 @@ button:disabled { } .the-now-history { color: red; -} \ No newline at end of file + overflow-anchor: auto; +} + From f3c99e1a5c669b2f55285bcf7f72a794b2ad1e4a Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 9 Jan 2020 22:27:36 -0500 Subject: [PATCH 16/60] basic history panel --- src/hazelweb/gui/UndoHistoryList.re | 5 ++++- src/hazelweb/www/style.css | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 9201a0b2b0..0df676f9af 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -56,7 +56,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Some(detail_ac) => Vdom.( Node.div( - [Attr.on_click(_ => inject(Update.Action.ShiftHistory(id)))], + [ + Attr.classes(["the-history-entry"]), + Attr.on_click(_ => inject(Update.Action.ShiftHistory(id))), + ], [Node.text(action_to_stirng(detail_ac))], ) ) diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 6cd9e62226..b3bd777cc2 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1209,16 +1209,28 @@ button:disabled { /* text-transform: uppercase; */ font-size: 75%; } - +.the-history-entry { + border: 1px outset #ffffff; + cursor: pointer; + padding: 5px; + font-size: 12pt; +} +.the-history-entry:hover { + color: #b30000; + background-color: lightgoldenrodyellow; +} .the-prev-history { color: #0d0a05; + background-color: #f0fff3; } .the-suc-history { - color: gray; + color: dimgray; + background-color: lightgray; } .the-now-history { - color: red; + color: #b30000; overflow-anchor: auto; + background-color: lightgoldenrodyellow; } From 5bc2ad3c9de6416694b6bdc43223c75b9d3799b8 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 9 Jan 2020 22:37:02 -0500 Subject: [PATCH 17/60] modify display history content --- src/hazelweb/gui/UndoHistoryList.re | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 0df676f9af..ca6feb086e 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -8,24 +8,24 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { switch (shape) { | SParenthesized => "parentheise" /* type shapes */ - | SNum => "number" - | SBool => "bool" - | SList => "list" + | SNum => "type Num" + | SBool => "type Bool" + | SList => "type List" /* expression shapes */ - | SAsc => "???" - | SVar(varstr, _) => "var: " ++ varstr // convert int to char? ++ Char.chr(intvar) - | SLam => "lambada?" - | SNumLit(value, _) => "numlit? " ++ string_of_int(value) - | SListNil => "listNil" + | SAsc => "add ':'" + | SVar(varstr, _) => "edit var: " ++ varstr // convert int to char? ++ Char.chr(intvar) + | SLam => "add lambada" + | SNumLit(value, _) => "edit number: " ++ string_of_int(value) + | SListNil => "add list" | SInj(direction) => switch (direction) { | L => "inject left" | R => "inject right" } - | SLet => "let" - | SLine => "a new line" - | SCase => "case" - | SOp(op) => "operator " ++ Action.op_shape_to_string(op) + | SLet => "bulid 'let'" + | SLine => "add a new line" + | SCase => "add case" + | SOp(op) => "add operator " ++ Action.op_shape_to_string(op) | SApPalette(_) => "appalette?" /* pattern-only shapes */ | SWild => "wild?" @@ -36,7 +36,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | UpdateApPalette(_) => "updatePlate?" | Delete => "delete" | Backspace => "backspace" - | Construct(shape) => "add " ++ shape_to_string(shape) + | Construct(shape) => shape_to_string(shape) | MoveTo(_) | MoveToBefore(_) | MoveLeft From ec01112b4505be32e382085557cbf91ee5a9a690 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 10 Jan 2020 12:36:08 -0500 Subject: [PATCH 18/60] finish basic panel, start to group edit actions --- src/hazelcore/semantics/Action.re | 50 +++++++++++++++++++++++++++++ src/hazelweb/gui/UndoHistoryList.re | 24 +++++++++++--- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index b4ea050e81..9b69ba3df6 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -34,6 +34,7 @@ let op_shape_to_string = (op: op_shape) => { | SOr => "|" }; }; + let ty_op_of = (os: op_shape): option(UHTyp.op) => switch (os) { | SArrow => Some(Arrow) @@ -159,6 +160,55 @@ type result('success) = | CantShift | Failed; +let shape_to_string = (shape: shape): string => { + switch (shape) { + | SParenthesized => "SParenthesized" + /* type shapes */ + | SNum => "SNum" + | SBool => "SBool" + | SList => "SList" + /* expression shapes */ + | SAsc => "SAsc" + | SVar(_, _) => "SVar" + | SLam => "SLam" + | SNumLit(_, _) => "SNumLit" + | SListNil => "SListNil" + | SInj(direction) => + switch (direction) { + | L => "SInjL" + | R => "SInjR" + } + | SLet => "SLet" + | SLine => "SLine" + | SCase => "SCase" + | SOp(op) => "SOp" ++ op_shape_to_string(op) + | SApPalette(_) => "SApPalette" + /* pattern-only shapes */ + | SWild => "SWild" + }; +}; +let action_to_comp_string = (action: t): string => { + switch (action) { + | UpdateApPalette(_) => "UpdateApPalette" + | Delete => "Delete" + | Backspace => "Backspace" + | Construct(shape) => shape_to_string(shape) + | MoveTo(_) + | MoveToBefore(_) + | MoveLeft + | MoveRight + | MoveToNextHole + | MoveToPrevHole + | ShiftLeft + | ShiftRight + | ShiftUp + | ShiftDown => "Not Show In undo_history" + }; +}; + +let comp_action = (action_1: t, action_2: t): bool => + action_to_comp_string(action_1) == action_to_comp_string(action_2); + let make_ty_OpSeqZ = (zty0: ZTyp.t, surround: ZTyp.opseq_surround): ZTyp.t => { let uty0 = ZTyp.erase(zty0); let seq = OperatorSeq.opseq_of_exp_and_surround(uty0, surround); diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index ca6feb086e..e82c50b49b 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -4,7 +4,7 @@ module ZList = GeneralUtil.ZList; exception InvalidInstance; let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { - let shape_to_string = (shape: Action.shape): string => { + let shape_to_display_string = (shape: Action.shape): string => { switch (shape) { | SParenthesized => "parentheise" /* type shapes */ @@ -31,12 +31,13 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | SWild => "wild?" }; }; - let action_to_stirng = (action: Action.t) => { + + let action_to_display_string = (action: Action.t) => { switch (action) { | UpdateApPalette(_) => "updatePlate?" | Delete => "delete" | Backspace => "backspace" - | Construct(shape) => shape_to_string(shape) + | Construct(shape) => shape_to_display_string(shape) | MoveTo(_) | MoveToBefore(_) | MoveLeft @@ -49,6 +50,21 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | ShiftDown => "" }; }; + + /* let rec group_history_view = (history, prev_action: option(Action.t), result) => { + switch(history){ + | [] => result + | [head, _] => { + let (_,action,_)=head; + switch(action){ + | None => result; //reach init history + | Some(action_now) => + } + + } + } + } + */ let history_entry_view = undo_history_entry => { let (_, action, id) = undo_history_entry; switch (action) { @@ -60,7 +76,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Attr.classes(["the-history-entry"]), Attr.on_click(_ => inject(Update.Action.ShiftHistory(id))), ], - [Node.text(action_to_stirng(detail_ac))], + [Node.text(action_to_display_string(detail_ac))], ) ) }; From 90f27340bbfd92a71ece8f6ada2623124929616c Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 10 Jan 2020 21:57:39 -0500 Subject: [PATCH 19/60] some problems on red/undo shift --- src/hazelcore/semantics/Action.re | 15 +- src/hazelweb/Model.re | 2 +- src/hazelweb/UndoHistory.re | 95 +++++++++-- src/hazelweb/Update.re | 48 +++++- src/hazelweb/gui/SvgShapes.re | 5 + src/hazelweb/gui/UndoHistoryList.re | 254 ++++++++++++++++++++++++++-- src/hazelweb/www/style.css | 37 +++- 7 files changed, 412 insertions(+), 44 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index 9b69ba3df6..90552bb336 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -206,8 +206,19 @@ let action_to_comp_string = (action: t): string => { }; }; -let comp_action = (action_1: t, action_2: t): bool => - action_to_comp_string(action_1) == action_to_comp_string(action_2); +let in_same_history_group = (action_1: option(t), action_2: option(t)): bool => { + let action_1_string = + switch (action_1) { + | None => "None" + | Some(action) => action_to_comp_string(action) + }; + let action_2_string = + switch (action_2) { + | None => "None" + | Some(action) => action_to_comp_string(action) + }; + action_1_string == action_2_string; +}; let make_ty_OpSeqZ = (zty0: ZTyp.t, surround: ZTyp.opseq_surround): ZTyp.t => { let uty0 = ZTyp.erase(zty0); diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index a930e90bac..4e0601b6d2 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -380,7 +380,7 @@ let init = (): t => { user_newlines: CursorPath.StepsMap.empty, selected_example: None, is_cell_focused: false, - undo_history: ([], (edit_state, None, 0), []), + undo_history: ([], (([], (edit_state, None, 0), []), 0, false), []), left_sidebar_open: false, right_sidebar_open: true, show_content_editable: false, diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index b9ab6880fc..5434606959 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -1,7 +1,13 @@ module ZList = GeneralUtil.ZList; /*new edit state, the previous action, id*/ type undo_history_entry = (Statics.edit_state, option(Action.t), int); -type t = ZList.t(undo_history_entry, undo_history_entry); +/* group edit action, group id */ +type undo_history_group = ( + ZList.t(undo_history_entry, undo_history_entry), + int, + bool, +); +type t = ZList.t(undo_history_group, undo_history_group); let push_edit_state = ( @@ -10,30 +16,85 @@ let push_edit_state = action: option(Action.t), ) : t => { - let (_, _, last_id) = ZList.prj_z(undo_history); - /* first add new edit state to the end, then shift_next */ - let after_push = ( - ZList.prj_prefix(undo_history), - ZList.prj_z(undo_history), - [(edit_state, action, last_id + 1)], - ); - switch (ZList.shift_next(after_push)) { - | None => failwith("Impossible because suffix is non-empty") - | Some(new_history) => new_history + let (prev_group, prev_group_id, _) = ZList.prj_z(undo_history); + let (_, prev_action, prev_id) = ZList.prj_z(prev_group); + if (Action.in_same_history_group(action, prev_action)) { + /* first add new edit state to the end, then shift_next */ + let after_push = ( + ZList.prj_prefix(prev_group), + ZList.prj_z(prev_group), + [(edit_state, action, prev_id + 1)], + ); + let group_after_push = + switch (ZList.shift_next(after_push)) { + | None => failwith("Impossible because suffix is non-empty") + | Some(new_group) => new_group + }; + ( + ZList.prj_prefix(undo_history), + (group_after_push, prev_group_id, false), + [], + ); + } else { + let new_group = ( + ([], (edit_state, action, 0), []), + prev_group_id + 1, + false, + ); + let after_push = ( + ZList.prj_prefix(undo_history), + ZList.prj_z(undo_history), + [new_group], + ); + switch (ZList.shift_next(after_push)) { + | None => failwith("Impossible because suffix is non-empty") + | Some(new_history) => new_history + }; }; }; +/* let push_edit_state = + ( + undo_history: t, + edit_state: Statics.edit_state, + action: option(Action.t), + ) + : t => { + let (_, _, last_id) = ZList.prj_z(undo_history); + /* first add new edit state to the end, then shift_next */ + let after_push = ( + ZList.prj_prefix(undo_history), + ZList.prj_z(undo_history), + [(edit_state, action, last_id + 1)], + ); + switch (ZList.shift_next(after_push)) { + | None => failwith("Impossible because suffix is non-empty") + | Some(new_history) => new_history + }; + }; */ let undo = (undo_history: t): t => { - switch (ZList.shift_prev(undo_history)) { - | None => undo_history - | Some(new_history) => new_history + let (group_now, gp_id, isexpanded) = ZList.prj_z(undo_history); + switch (ZList.shift_prev(group_now)) { + | None => + switch (ZList.shift_prev(undo_history)) { + | None => undo_history + | Some(new_history) => new_history + } + | Some(new_group) => + ZList.replace_z(undo_history, (new_group, gp_id, isexpanded)) }; }; let redo = (undo_history: t): t => { - switch (ZList.shift_next(undo_history)) { - | None => undo_history - | Some(new_history) => new_history + let (group_now, gp_id, isexpanded) = ZList.prj_z(undo_history); + switch (ZList.shift_next(group_now)) { + | None => + switch (ZList.shift_prev(undo_history)) { + | None => undo_history + | Some(new_history) => new_history + } + | Some(new_group) => + ZList.replace_z(undo_history, (new_group, gp_id, isexpanded)) }; }; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 085501c051..26ad24832d 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -31,7 +31,8 @@ module Action = { | RemoveUserNewline(CursorPath.steps) | Redo | Undo - | ShiftHistory(int); + | ShiftHistory(int, int) + | ToggleHistoryGroup(int); }; [@deriving sexp] @@ -89,7 +90,8 @@ let log_action = (action: Action.t, _: State.t): unit => { | MoveToHole(_) | Undo | Redo - | ShiftHistory(_) => + | ShiftHistory(_, _) + | ToggleHistoryGroup(_) => Logger.append( Sexp.to_string( sexp_of_timestamped_action(mk_timestamped_action(action)), @@ -300,23 +302,51 @@ let apply_action = model; | Undo => let new_history = UndoHistory.undo(model.undo_history); - let (new_edit_state, _, _) = ZList.prj_z(new_history); + let (group_now, _, _) = ZList.prj_z(model.undo_history); + let (new_edit_state, _, _) = ZList.prj_z(group_now); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; | Redo => let new_history = UndoHistory.redo(model.undo_history); - let (new_edit_state, _, _) = ZList.prj_z(new_history); + let (group_now, _, _) = ZList.prj_z(model.undo_history); + let (new_edit_state, _, _) = ZList.prj_z(group_now); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; - | ShiftHistory(n) => + | ShiftHistory(gp_id, ent_id) => let erase_func = his => his; let his_lst = ZList.erase(model.undo_history, erase_func); - switch (ZList.split_at(n, his_lst)) { + switch (ZList.split_at(gp_id, his_lst)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => - let (new_edit_state, _, _) = ZList.prj_z(new_history); - let new_model = model |> Model.update_edit_state(new_edit_state); - {...new_model, undo_history: new_history}; + let (new_group, _, isexpanded) = ZList.prj_z(new_history); + let entry_lst = ZList.erase(new_group, erase_func); + switch (ZList.split_at(ent_id, entry_lst)) { + | None => failwith("Impossible because undo_history is non-empty") + | Some(group) => + let (new_edit_state, _, _) = ZList.prj_z(group); + let new_model = model |> Model.update_edit_state(new_edit_state); + { + ...new_model, + undo_history: + ZList.replace_z(new_history, (group, gp_id, isexpanded)), + }; + }; + }; + | ToggleHistoryGroup(gp_id) => + let (_, cur_gp_id, _) = ZList.prj_z(model.undo_history); + let erase_func = his => his; + let his_lst = ZList.erase(model.undo_history, erase_func); + switch (ZList.split_at(gp_id, his_lst)) { + | None => failwith("Impossible because undo_history is non-empty") + | Some(history) => + let (gp_lst, _, isexpanded) = ZList.prj_z(history); + let after_toggle = + ZList.replace_z(history, (gp_lst, gp_id, !isexpanded)); + let new_his_lst = ZList.erase(after_toggle, erase_func); + switch (ZList.split_at(cur_gp_id, new_his_lst)) { + | None => failwith("Impossible because undo_history is non-empty") + | Some(new_history) => {...model, undo_history: new_history} + }; }; }; }; diff --git a/src/hazelweb/gui/SvgShapes.re b/src/hazelweb/gui/SvgShapes.re index 33746584e1..629c7b178e 100644 --- a/src/hazelweb/gui/SvgShapes.re +++ b/src/hazelweb/gui/SvgShapes.re @@ -5,6 +5,9 @@ open Incr_dom; */ let icon = (classes, _points) => Vdom.Node.div([Vdom.Attr.classes(classes)], []); + +/* let expand_icon = (classes, _points) => + Vdom.Node.div([Vdom.Attr.classes(classes)], []); */ /* TODO dunno how to get svg stuff working with incr_dom Tyxml.( Svg.svg( @@ -21,3 +24,5 @@ let left_arrow = (classes: list(string), _: unit) => icon(classes, [(0.0, 50.0), (100.0, 0.0), (100.0, 100.0)]); let right_arrow = (classes: list(string), _: unit) => icon(classes, [(0.0, 0.0), (100.0, 50.0), (0.0, 100.0)]); +let down_arrow = (classes: list(string), _: unit) => + icon(classes, [(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)]); diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index e82c50b49b..98c2f5fbd5 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -65,7 +65,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } } */ - let history_entry_view = undo_history_entry => { + let history_entry_view = (group_id: int, undo_history_entry) => { let (_, action, id) = undo_history_entry; switch (action) { | None => Vdom.(Node.div([], [])) @@ -74,18 +74,237 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Node.div( [ Attr.classes(["the-history-entry"]), - Attr.on_click(_ => inject(Update.Action.ShiftHistory(id))), + Attr.on_click(_ => + inject(Update.Action.ShiftHistory(group_id, id)) + ), ], [Node.text(action_to_display_string(detail_ac))], ) ) }; }; + + let history_title_entry_view = + (has_hidden_part: bool, group_id: int, undo_history_entry) => { + let (_, action, id) = undo_history_entry; + let hidden_part = (group_id: int) => + if (has_hidden_part) { + Vdom.( + Node.div( + [ + Attr.classes(["history-tab-icon"]), + Attr.on_click(_ => + inject(Update.Action.ToggleHistoryGroup(group_id)) + ), + ], + [], + ) + ); + } else { + Vdom.(Node.div([], [])); + }; + switch (action) { + | None => Vdom.(Node.div([], [])) + | Some(detail_ac) => + Vdom.( + Node.div( + [Attr.classes(["the-history-title"])], + [ + Node.div( + [ + Attr.classes(["the-history-title-entry"]), + Attr.on_click(_ => + inject(Update.Action.ShiftHistory(group_id, id)) + ), + ], + [ + Node.text(action_to_display_string(detail_ac)), + hidden_part(group_id), + ], + ), + ], + ) + ) + }; + }; + /* (ZList.t(undo_history_entry, undo_history_entry),int,bool) */ + let group_view = + ( + is_cur_group: bool, + group_entry: ( + ZList.t( + UndoHistory.undo_history_entry, + UndoHistory.undo_history_entry, + ), + int, + bool, + ), + ) => { + let (group_lst_old_first, group_id, isexpanded) = group_entry; + let group_lst_new_first = ( + List.rev(ZList.prj_suffix(group_lst_old_first)), + ZList.prj_z(group_lst_old_first), + List.rev(ZList.prj_prefix(group_lst_old_first)), + ); + let suc_his_classes = + if (is_cur_group) { + ["the-suc-history"]; + } else { + []; + }; + let prev_his_classes = + if (is_cur_group) { + ["the-prev-history"]; + } else { + []; + }; + let cur_his_classes = + if (is_cur_group) { + ["the-now-history"]; + } else { + []; + }; + switch (group_lst_new_first) { + | ([], entry_now, prev_lst) => + switch (entry_now) { + | (_, None, _) => Vdom.(Node.div([], [])) + | (_, Some(_), _) => + let has_hidden_part = + switch (prev_lst) { + | [] => false + | _ => true + }; + if (isexpanded) { + Vdom.( + Node.div( + [Attr.classes(["the-history-group"])], + [ + Vdom.( + Node.div( + [ + Attr.classes( + ["always-display-history-entry"] @ cur_his_classes, + ), + ], + [ + history_title_entry_view( + has_hidden_part, + group_id, + entry_now, + ), + ], + ) + ), + Vdom.( + Node.div( + [ + Attr.classes( + ["hidden-history-entry"] @ prev_his_classes, + ), + ], + List.map(history_entry_view(group_id), prev_lst), + ) + ), + ], + ) + ); + } else { + Vdom.( + Node.div( + [Attr.classes(["the-history-group"])], + [ + Vdom.( + Node.div( + [ + Attr.classes( + ["always-display-history-entry"] @ cur_his_classes, + ), + ], + [ + history_title_entry_view( + has_hidden_part, + group_id, + entry_now, + ), + ], + ) + ), + ], + ) + ); + }; + } + + | ([title_entry, ...suc_lst], entry_now, prev_lst) => + if (isexpanded) { + Vdom.( + Node.div( + [Attr.classes(["the-history-group"])], + [ + Vdom.( + Node.div( + [ + Attr.classes( + ["always-display-history-entry"] @ suc_his_classes, + ), + ], + [history_title_entry_view(true, group_id, title_entry)], + ) + ), + Vdom.( + Node.div( + [ + Attr.classes(["hidden-history-entry"] @ suc_his_classes), + ], + List.map(history_entry_view(group_id), suc_lst), + ) + ), + Vdom.( + Node.div( + [ + Attr.classes(["hidden-history-entry"] @ cur_his_classes), + ], + [history_entry_view(group_id, entry_now)], + ) + ), + Vdom.( + Node.div( + [ + Attr.classes(["hidden-history-entry"] @ prev_his_classes), + ], + List.map(history_entry_view(group_id), prev_lst), + ) + ), + ], + ) + ); + } else { + Vdom.( + Node.div( + [Attr.classes(["the-history-group"])], + [ + Vdom.( + Node.div( + [ + Attr.classes( + ["always-display-history-entry"] @ suc_his_classes, + ), + ], + [history_title_entry_view(true, group_id, title_entry)], + ) + ), + ], + ) + ); + } + }; + }; + let prev_history_view = history => { Vdom.( Node.div( [Attr.classes(["the-prev-history"])], - List.map(history_entry_view, history), + List.map(group_view(false), history), ) ); }; @@ -93,21 +312,28 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.( Node.div( [Attr.classes(["the-suc-history"])], - List.map(history_entry_view, history), + List.map(group_view(false), history), ) ); }; - let now_history_view = (history: UndoHistory.undo_history_entry) => { - Vdom.( - Node.div( - [Attr.classes(["the-now-history"])], - [history_entry_view(history)], - ) - ); + let now_history_view = + ( + history: ( + ZList.t( + UndoHistory.undo_history_entry, + UndoHistory.undo_history_entry, + ), + int, + bool, + ), + ) => { + Vdom.(Node.div([], [group_view(true, history)])); }; let history_view = (model: Model.t) => { - let (prev_his, now, suc_his) = model.undo_history; - switch (now) { + let (prev_his, cur_group, suc_his) = model.undo_history; + let (cur_group_lst, _, _) = cur_group; + let (_, cur_entry, _) = cur_group_lst; + switch (cur_entry) { | (_, None, _) => Vdom.( Node.div( @@ -128,7 +354,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [Attr.classes(["the-history"])], [ suc_history_view(List.rev(suc_his)), - now_history_view(now), + now_history_view(cur_group), prev_history_view(List.rev(prev_his)), ], ) diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index b3bd777cc2..03ac101b89 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1210,15 +1210,24 @@ button:disabled { font-size: 75%; } .the-history-entry { - border: 1px outset #ffffff; cursor: pointer; padding: 5px; font-size: 12pt; + width: 100%; } .the-history-entry:hover { color: #b30000; background-color: lightgoldenrodyellow; } +.the-history-title { + display: inline-block; + padding: 5px; +} +.the-history-title-entry { + float: left; + cursor: pointer; + width: 100%; +} .the-prev-history { color: #0d0a05; background-color: #f0fff3; @@ -1234,3 +1243,29 @@ button:disabled { background-color: lightgoldenrodyellow; } +/* .history-tab-icon-wrapper { + float: left; +} */ +.always-display-history-entry { + font-weight: bold; + border: 1px outset #ffffff; + width: 100%; + display: grid; +} +.history-tab-icon { + cursor: pointer; + float: right; + width: 0; + height: 0; + border-left: 9px solid transparent; + border-right: 9px solid transparent; + border-top: 16px solid #558e62; + +/* cursor: pointer; + padding: 4px; + width: 12px; + height: 12px; + flex-shrink: 0; + fill: black; + display: block; *//* MUST do this: inline elements have in-built vertical margins */ +} From cc686010425848ae831137f17fc7fc4c8ced35f2 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 10 Jan 2020 23:00:06 -0500 Subject: [PATCH 20/60] history panel with grouping --- src/hazelcore/GeneralUtil.re | 20 ++++++++++++++++++++ src/hazelweb/UndoHistory.re | 15 ++++++++++++--- src/hazelweb/Update.re | 4 ++-- src/hazelweb/gui/SvgShapes.re | 6 ++---- src/hazelweb/gui/UndoHistoryList.re | 4 ++-- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/hazelcore/GeneralUtil.re b/src/hazelcore/GeneralUtil.re index dc4300c5c2..992b495b4c 100644 --- a/src/hazelcore/GeneralUtil.re +++ b/src/hazelcore/GeneralUtil.re @@ -305,6 +305,26 @@ module ZList = { Some((List.rev(rev_prefix), prev, suffix)); }; }; + + let shift_end = (zxs: t('a, 'a)): t('a, 'a) => { + let (prefix, z, suffix) = zxs; + switch (List.rev(suffix)) { + | [] => zxs + | [last_elt, ...tail] => + let prefix = prefix @ [z] @ List.rev(tail); + (prefix, last_elt, []); + }; + }; + + let shift_front = (zxs: t('a, 'a)): t('a, 'a) => { + let (prefix, z, suffix) = zxs; + switch (prefix) { + | [] => zxs + | [head, ...tail] => + let suffix = tail @ [z] @ suffix; + ([], head, suffix); + }; + }; }; /* Section StringUtil */ diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 5434606959..0623458c5c 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -73,12 +73,18 @@ let push_edit_state = }; */ let undo = (undo_history: t): t => { + JSUtil.log("## before undo:"); let (group_now, gp_id, isexpanded) = ZList.prj_z(undo_history); + JSUtil.log("group_id is:"); + JSUtil.log(gp_id); switch (ZList.shift_prev(group_now)) { | None => switch (ZList.shift_prev(undo_history)) { | None => undo_history - | Some(new_history) => new_history + | Some(new_history) => + let (group_lst, id, isexpanded) = ZList.prj_z(new_history); + let new_group = (ZList.shift_end(group_lst), id, isexpanded); + ZList.replace_z(new_history, new_group); } | Some(new_group) => ZList.replace_z(undo_history, (new_group, gp_id, isexpanded)) @@ -89,9 +95,12 @@ let redo = (undo_history: t): t => { let (group_now, gp_id, isexpanded) = ZList.prj_z(undo_history); switch (ZList.shift_next(group_now)) { | None => - switch (ZList.shift_prev(undo_history)) { + switch (ZList.shift_next(undo_history)) { | None => undo_history - | Some(new_history) => new_history + | Some(new_history) => + let (group_lst, id, isexpanded) = ZList.prj_z(new_history); + let new_group = (ZList.shift_front(group_lst), id, isexpanded); + ZList.replace_z(new_history, new_group); } | Some(new_group) => ZList.replace_z(undo_history, (new_group, gp_id, isexpanded)) diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 26ad24832d..173e3c364f 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -302,13 +302,13 @@ let apply_action = model; | Undo => let new_history = UndoHistory.undo(model.undo_history); - let (group_now, _, _) = ZList.prj_z(model.undo_history); + let (group_now, _, _) = ZList.prj_z(new_history); let (new_edit_state, _, _) = ZList.prj_z(group_now); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; | Redo => let new_history = UndoHistory.redo(model.undo_history); - let (group_now, _, _) = ZList.prj_z(model.undo_history); + let (group_now, _, _) = ZList.prj_z(new_history); let (new_edit_state, _, _) = ZList.prj_z(group_now); let new_model = model |> Model.update_edit_state(new_edit_state); {...new_model, undo_history: new_history}; diff --git a/src/hazelweb/gui/SvgShapes.re b/src/hazelweb/gui/SvgShapes.re index 629c7b178e..6046619461 100644 --- a/src/hazelweb/gui/SvgShapes.re +++ b/src/hazelweb/gui/SvgShapes.re @@ -6,8 +6,6 @@ open Incr_dom; let icon = (classes, _points) => Vdom.Node.div([Vdom.Attr.classes(classes)], []); -/* let expand_icon = (classes, _points) => - Vdom.Node.div([Vdom.Attr.classes(classes)], []); */ /* TODO dunno how to get svg stuff working with incr_dom Tyxml.( Svg.svg( @@ -24,5 +22,5 @@ let left_arrow = (classes: list(string), _: unit) => icon(classes, [(0.0, 50.0), (100.0, 0.0), (100.0, 100.0)]); let right_arrow = (classes: list(string), _: unit) => icon(classes, [(0.0, 0.0), (100.0, 50.0), (0.0, 100.0)]); -let down_arrow = (classes: list(string), _: unit) => - icon(classes, [(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)]); +/* let down_arrow = (classes: list(string), _: unit) => + icon(classes, [(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)]); */ diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 98c2f5fbd5..8588ae96dc 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -23,7 +23,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | R => "inject right" } | SLet => "bulid 'let'" - | SLine => "add a new line" + | SLine => "add new line[s]" | SCase => "add case" | SOp(op) => "add operator " ++ Action.op_shape_to_string(op) | SApPalette(_) => "appalette?" @@ -47,7 +47,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | ShiftLeft | ShiftRight | ShiftUp - | ShiftDown => "" + | ShiftDown => "will not show in undo_history" }; }; From 7f02a54913d69bedfef6e66cc4d64a3f30aa6eba Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 10 Jan 2020 23:27:53 -0500 Subject: [PATCH 21/60] finish basic edit action his panel --- src/hazelweb/gui/UndoHistoryList.re | 95 +++++++++++++++++------------ src/hazelweb/www/style.css | 32 +++++----- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 8588ae96dc..53809e78eb 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -7,11 +7,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let shape_to_display_string = (shape: Action.shape): string => { switch (shape) { | SParenthesized => "parentheise" - /* type shapes */ | SNum => "type Num" | SBool => "type Bool" | SList => "type List" - /* expression shapes */ | SAsc => "add ':'" | SVar(varstr, _) => "edit var: " ++ varstr // convert int to char? ++ Char.chr(intvar) | SLam => "add lambada" @@ -51,20 +49,6 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - /* let rec group_history_view = (history, prev_action: option(Action.t), result) => { - switch(history){ - | [] => result - | [head, _] => { - let (_,action,_)=head; - switch(action){ - | None => result; //reach init history - | Some(action_now) => - } - - } - } - } - */ let history_entry_view = (group_id: int, undo_history_entry) => { let (_, action, id) = undo_history_entry; switch (action) { @@ -85,14 +69,25 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; let history_title_entry_view = - (has_hidden_part: bool, group_id: int, undo_history_entry) => { + ( + is_expanded: bool, + has_hidden_part: bool, + group_id: int, + undo_history_entry, + ) => { let (_, action, id) = undo_history_entry; + let icon_classes = + if (is_expanded) { + ["history-tab-icon-open"]; + } else { + ["history-tab-icon-close"]; + }; let hidden_part = (group_id: int) => if (has_hidden_part) { Vdom.( Node.div( [ - Attr.classes(["history-tab-icon"]), + Attr.classes(icon_classes), Attr.on_click(_ => inject(Update.Action.ToggleHistoryGroup(group_id)) ), @@ -188,6 +183,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( + isexpanded, has_hidden_part, group_id, entry_now, @@ -222,6 +218,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( + isexpanded, has_hidden_part, group_id, entry_now, @@ -248,7 +245,14 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ["always-display-history-entry"] @ suc_his_classes, ), ], - [history_title_entry_view(true, group_id, title_entry)], + [ + history_title_entry_view( + isexpanded, + true, + group_id, + title_entry, + ), + ], ) ), Vdom.( @@ -290,7 +294,14 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ["always-display-history-entry"] @ suc_his_classes, ), ], - [history_title_entry_view(true, group_id, title_entry)], + [ + history_title_entry_view( + isexpanded, + true, + group_id, + title_entry, + ), + ], ) ), ], @@ -331,24 +342,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; let history_view = (model: Model.t) => { let (prev_his, cur_group, suc_his) = model.undo_history; - let (cur_group_lst, _, _) = cur_group; - let (_, cur_entry, _) = cur_group_lst; - switch (cur_entry) { - | (_, None, _) => - Vdom.( - Node.div( - [Attr.classes(["the-history"])], - [ - Vdom.( - Node.div( - [Attr.classes(["history-is-empty-msg"])], - [Node.text("no history in scope")], - ) - ), - ], - ) - ) - | (_, Some(_), _) => + let display_content = Vdom.( Node.div( [Attr.classes(["the-history"])], @@ -358,7 +352,30 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { prev_history_view(List.rev(prev_his)), ], ) - ) + ); + let (cur_group_lst, _, _) = cur_group; + let (_, cur_entry, _) = cur_group_lst; + switch (cur_entry) { + | (_, None, _) => + /*if init state is only history entry */ + if (List.length(suc_his) <= 1) { + Vdom.( + Node.div( + [Attr.classes(["the-history"])], + [ + Vdom.( + Node.div( + [Attr.classes(["history-is-empty-msg"])], + [Node.text("no history in scope")], + ) + ), + ], + ) + ); + } else { + display_content; + } + | (_, Some(_), _) => display_content }; }; Vdom.( diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 03ac101b89..1fb4df6966 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1211,9 +1211,10 @@ button:disabled { } .the-history-entry { cursor: pointer; - padding: 5px; + padding: 5px 5px 5px 20px; font-size: 12pt; width: 100%; + border: 1px outset #ffffff; } .the-history-entry:hover { color: #b30000; @@ -1223,6 +1224,10 @@ button:disabled { display: inline-block; padding: 5px; } +.the-history-title:hover { + color: #b30000; + background-color: lightgoldenrodyellow; +} .the-history-title-entry { float: left; cursor: pointer; @@ -1243,16 +1248,13 @@ button:disabled { background-color: lightgoldenrodyellow; } -/* .history-tab-icon-wrapper { - float: left; -} */ + .always-display-history-entry { - font-weight: bold; border: 1px outset #ffffff; width: 100%; display: grid; } -.history-tab-icon { +.history-tab-icon-open { cursor: pointer; float: right; width: 0; @@ -1260,12 +1262,14 @@ button:disabled { border-left: 9px solid transparent; border-right: 9px solid transparent; border-top: 16px solid #558e62; - -/* cursor: pointer; - padding: 4px; - width: 12px; - height: 12px; - flex-shrink: 0; - fill: black; - display: block; *//* MUST do this: inline elements have in-built vertical margins */ } + +.history-tab-icon-close { + cursor: pointer; + float: right; + width: 0; + height: 0; + border-bottom: 9px solid transparent; + border-top: 9px solid transparent; + border-right: 16px solid #558e62; +} \ No newline at end of file From b5c71e0f25a1d0de87c66416c7406a72a3b5ae64 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 10 Jan 2020 23:32:26 -0500 Subject: [PATCH 22/60] modify ui. start to add card state --- src/hazelweb/www/style.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 1fb4df6966..959469271d 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1259,9 +1259,9 @@ button:disabled { float: right; width: 0; height: 0; - border-left: 9px solid transparent; - border-right: 9px solid transparent; - border-top: 16px solid #558e62; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 10px solid #558e62; } .history-tab-icon-close { @@ -1269,7 +1269,7 @@ button:disabled { float: right; width: 0; height: 0; - border-bottom: 9px solid transparent; - border-top: 9px solid transparent; - border-right: 16px solid #558e62; + border-bottom: 8px solid transparent; + border-top: 8px solid transparent; + border-right: 10px solid #558e62; } \ No newline at end of file From c6fd05c60a5fa20a8163048648c1ca837080ccd6 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 11 Jan 2020 13:49:47 -0500 Subject: [PATCH 23/60] add overall expand control button --- src/hazelweb/Model.re | 26 +++- src/hazelweb/UndoHistory.re | 38 ++---- src/hazelweb/Update.re | 15 ++- src/hazelweb/gui/Page.re | 46 ++++--- src/hazelweb/gui/UndoHistoryList.re | 181 ++++++++++++++++++++-------- src/hazelweb/www/style.css | 18 ++- 6 files changed, 215 insertions(+), 109 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 4e0601b6d2..39c29aa306 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -85,7 +85,11 @@ type has_result_state = { type result_state = | ResultsDisabled | Result(has_result_state); - +type hidden_history_state = + | OpenAll + | CloseAll + | CurOpenIgnore + | CurCloseIgnore; type t = { cardstacks, cardstacks_state /* these are derived from the cardstack state: */, @@ -100,6 +104,7 @@ type t = { right_sidebar_open: bool, show_content_editable: bool, show_presentation: bool, + hidden_history_state, undo_history: UndoHistory.t, }; @@ -385,6 +390,7 @@ let init = (): t => { right_sidebar_open: true, show_content_editable: false, show_presentation: false, + hidden_history_state: CloseAll, }; }; @@ -468,6 +474,24 @@ let toggle_right_sidebar = (model: t): t => { right_sidebar_open: !model.right_sidebar_open, }; +let toggle_hidden_history_all = (model: t): t => { + ...model, + hidden_history_state: + switch (model.hidden_history_state) { + | OpenAll + | CurOpenIgnore => CloseAll + | CloseAll + | CurCloseIgnore => OpenAll + }, +}; +let ignore_hidden_history_button = hidden_history_state => { + switch (hidden_history_state) { + | OpenAll + | CurOpenIgnore => CurOpenIgnore + | CloseAll + | CurCloseIgnore => CurCloseIgnore + }; +}; let load_example = (model: t, block: UHExp.block): t => model |> update_edit_state( diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 0623458c5c..d84d5f8be0 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -52,58 +52,36 @@ let push_edit_state = }; }; }; -/* let push_edit_state = - ( - undo_history: t, - edit_state: Statics.edit_state, - action: option(Action.t), - ) - : t => { - let (_, _, last_id) = ZList.prj_z(undo_history); - /* first add new edit state to the end, then shift_next */ - let after_push = ( - ZList.prj_prefix(undo_history), - ZList.prj_z(undo_history), - [(edit_state, action, last_id + 1)], - ); - switch (ZList.shift_next(after_push)) { - | None => failwith("Impossible because suffix is non-empty") - | Some(new_history) => new_history - }; - }; */ let undo = (undo_history: t): t => { - JSUtil.log("## before undo:"); - let (group_now, gp_id, isexpanded) = ZList.prj_z(undo_history); - JSUtil.log("group_id is:"); - JSUtil.log(gp_id); + let (group_now, gp_id, _) = ZList.prj_z(undo_history); switch (ZList.shift_prev(group_now)) { | None => switch (ZList.shift_prev(undo_history)) { | None => undo_history | Some(new_history) => - let (group_lst, id, isexpanded) = ZList.prj_z(new_history); - let new_group = (ZList.shift_end(group_lst), id, isexpanded); + let (group_lst, id, _) = ZList.prj_z(new_history); + let new_group = (ZList.shift_end(group_lst), id, true); /* is_expanded=true because this group should be expanded*/ ZList.replace_z(new_history, new_group); } | Some(new_group) => - ZList.replace_z(undo_history, (new_group, gp_id, isexpanded)) + ZList.replace_z(undo_history, (new_group, gp_id, true)) }; }; let redo = (undo_history: t): t => { - let (group_now, gp_id, isexpanded) = ZList.prj_z(undo_history); + let (group_now, gp_id, _) = ZList.prj_z(undo_history); switch (ZList.shift_next(group_now)) { | None => switch (ZList.shift_next(undo_history)) { | None => undo_history | Some(new_history) => - let (group_lst, id, isexpanded) = ZList.prj_z(new_history); - let new_group = (ZList.shift_front(group_lst), id, isexpanded); + let (group_lst, id, _) = ZList.prj_z(new_history); + let new_group = (ZList.shift_front(group_lst), id, true); /* is_expanded=true because this group should be expanded when redo*/ ZList.replace_z(new_history, new_group); } | Some(new_group) => - ZList.replace_z(undo_history, (new_group, gp_id, isexpanded)) + ZList.replace_z(undo_history, (new_group, gp_id, true)) }; }; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 173e3c364f..9de8e2926b 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -32,7 +32,8 @@ module Action = { | Redo | Undo | ShiftHistory(int, int) - | ToggleHistoryGroup(int); + | ToggleHistoryGroup(int) + | ToggleHiddenHistoryAll; }; [@deriving sexp] @@ -91,7 +92,8 @@ let log_action = (action: Action.t, _: State.t): unit => { | Undo | Redo | ShiftHistory(_, _) - | ToggleHistoryGroup(_) => + | ToggleHistoryGroup(_) + | ToggleHiddenHistoryAll => Logger.append( Sexp.to_string( sexp_of_timestamped_action(mk_timestamped_action(action)), @@ -333,6 +335,8 @@ let apply_action = }; }; | ToggleHistoryGroup(gp_id) => + let new_hidden_history_state = + Model.ignore_hidden_history_button(model.hidden_history_state); let (_, cur_gp_id, _) = ZList.prj_z(model.undo_history); let erase_func = his => his; let his_lst = ZList.erase(model.undo_history, erase_func); @@ -345,8 +349,13 @@ let apply_action = let new_his_lst = ZList.erase(after_toggle, erase_func); switch (ZList.split_at(cur_gp_id, new_his_lst)) { | None => failwith("Impossible because undo_history is non-empty") - | Some(new_history) => {...model, undo_history: new_history} + | Some(new_history) => { + ...model, + hidden_history_state: new_hidden_history_state, + undo_history: new_history, + } }; }; + | ToggleHiddenHistoryAll => Model.toggle_hidden_history_all(model) }; }; diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index 87cd337c7c..a10fff6900 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -84,29 +84,29 @@ let next_card_button = (~inject, model: Model.t) => { ) ); }; -let undo_button = (~inject) => { - Vdom.( - Node.button( - [ - Attr.id("undo-button"), - Attr.on_click(_ => inject(Update.Action.Undo)), - ], - [Node.text("Undo")], - ) - ); -}; +/* let undo_button = (~inject) => { + Vdom.( + Node.button( + [ + Attr.id("undo-button"), + Attr.on_click(_ => inject(Update.Action.Undo)), + ], + [Node.text("Undo")], + ) + ); + }; -let redo_button = (~inject) => { - Vdom.( - Node.button( - [ - Attr.id("redo-button"), - Attr.on_click(_ => inject(Update.Action.Redo)), - ], - [Node.text("Redo")], - ) - ); -}; + let redo_button = (~inject) => { + Vdom.( + Node.button( + [ + Attr.id("redo-button"), + Attr.on_click(_ => inject(Update.Action.Redo)), + ], + [Node.text("Redo")], + ) + ); + }; */ let cardstack_controls = (~inject, model: Model.t) => Vdom.( Node.div( @@ -117,8 +117,6 @@ let cardstack_controls = (~inject, model: Model.t) => [ prev_card_button(~inject, model), next_card_button(~inject, model), - undo_button(~inject), - redo_button(~inject), ], ), ], diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 53809e78eb..2ab79dcce1 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -11,7 +11,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | SBool => "type Bool" | SList => "type List" | SAsc => "add ':'" - | SVar(varstr, _) => "edit var: " ++ varstr // convert int to char? ++ Char.chr(intvar) + | SVar(varstr, _) => "edit var: " ++ varstr | SLam => "add lambada" | SNumLit(value, _) => "edit number: " ++ string_of_int(value) | SListNil => "add list" @@ -49,17 +49,17 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let history_entry_view = (group_id: int, undo_history_entry) => { - let (_, action, id) = undo_history_entry; + let history_hidden_entry_view = (group_id: int, undo_history_entry) => { + let (_, action, elt_id) = undo_history_entry; switch (action) { - | None => Vdom.(Node.div([], [])) + | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ | Some(detail_ac) => Vdom.( Node.div( [ Attr.classes(["the-history-entry"]), Attr.on_click(_ => - inject(Update.Action.ShiftHistory(group_id, id)) + inject(Update.Action.ShiftHistory(group_id, elt_id)) ), ], [Node.text(action_to_display_string(detail_ac))], @@ -70,19 +70,19 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let history_title_entry_view = ( - is_expanded: bool, - has_hidden_part: bool, + ~is_expanded: bool, + ~has_hidden_part: bool, group_id: int, undo_history_entry, ) => { - let (_, action, id) = undo_history_entry; + let (_, action, elt_id) = undo_history_entry; let icon_classes = if (is_expanded) { ["history-tab-icon-open"]; } else { ["history-tab-icon-close"]; }; - let hidden_part = (group_id: int) => + let history_tab_icon = (group_id: int) => if (has_hidden_part) { Vdom.( Node.div( @@ -106,15 +106,18 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [Attr.classes(["the-history-title"])], [ Node.div( + [Attr.classes(["the-history-title-entry"])], [ - Attr.classes(["the-history-title-entry"]), - Attr.on_click(_ => - inject(Update.Action.ShiftHistory(group_id, id)) + Node.span( + [ + Attr.classes(["the-history-title-content"]), + Attr.on_click(_ => + inject(Update.Action.ShiftHistory(group_id, elt_id)) + ), + ], + [Node.text(action_to_display_string(detail_ac))], ), - ], - [ - Node.text(action_to_display_string(detail_ac)), - hidden_part(group_id), + history_tab_icon(group_id), ], ), ], @@ -122,10 +125,11 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) }; }; - /* (ZList.t(undo_history_entry, undo_history_entry),int,bool) */ + let group_view = ( - is_cur_group: bool, + ~is_cur_group: bool, + hidden_history_state: Model.hidden_history_state, group_entry: ( ZList.t( UndoHistory.undo_history_entry, @@ -135,12 +139,14 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { bool, ), ) => { - let (group_lst_old_first, group_id, isexpanded) = group_entry; + let (group_lst_old_first, group_id, is_expanded) = group_entry; + /* reverse the undo_history, so the first entry shown in panel is the latest history entry */ let group_lst_new_first = ( List.rev(ZList.prj_suffix(group_lst_old_first)), ZList.prj_z(group_lst_old_first), List.rev(ZList.prj_prefix(group_lst_old_first)), ); + /* if the group containning selected history entry, it should be splited to different css styles */ let suc_his_classes = if (is_cur_group) { ["the-suc-history"]; @@ -155,21 +161,28 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; let cur_his_classes = if (is_cur_group) { - ["the-now-history"]; + ["the-cur-history"]; } else { []; }; switch (group_lst_new_first) { - | ([], entry_now, prev_lst) => - switch (entry_now) { - | (_, None, _) => Vdom.(Node.div([], [])) + | ([], cur_entry, prev_lst) => + switch (cur_entry) { + | (_, None, _) => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ | (_, Some(_), _) => let has_hidden_part = switch (prev_lst) { | [] => false | _ => true }; - if (isexpanded) { + let is_expanded = + switch (hidden_history_state) { + | OpenAll => true + | CloseAll => false + | CurOpenIgnore + | CurCloseIgnore => is_expanded + }; + if (is_expanded) { Vdom.( Node.div( [Attr.classes(["the-history-group"])], @@ -183,10 +196,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - isexpanded, - has_hidden_part, + ~is_expanded, + ~has_hidden_part, group_id, - entry_now, + cur_entry, ), ], ) @@ -198,7 +211,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ["hidden-history-entry"] @ prev_his_classes, ), ], - List.map(history_entry_view(group_id), prev_lst), + List.map(history_hidden_entry_view(group_id), prev_lst), ) ), ], @@ -218,10 +231,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - isexpanded, - has_hidden_part, + ~is_expanded, + ~has_hidden_part, group_id, - entry_now, + cur_entry, ), ], ) @@ -232,12 +245,13 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; } - | ([title_entry, ...suc_lst], entry_now, prev_lst) => - if (isexpanded) { + | ([title_entry, ...suc_lst], cur_entry, prev_lst) => + if (is_expanded) { Vdom.( Node.div( [Attr.classes(["the-history-group"])], [ + /* the history title entry */ Vdom.( Node.div( [ @@ -247,36 +261,39 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - isexpanded, - true, + ~is_expanded, + ~has_hidden_part=true, group_id, title_entry, ), ], ) ), + /* the successor history entry */ Vdom.( Node.div( [ Attr.classes(["hidden-history-entry"] @ suc_his_classes), ], - List.map(history_entry_view(group_id), suc_lst), + List.map(history_hidden_entry_view(group_id), suc_lst), ) ), + /* the selected(current) history entry */ Vdom.( Node.div( [ Attr.classes(["hidden-history-entry"] @ cur_his_classes), ], - [history_entry_view(group_id, entry_now)], + [history_hidden_entry_view(group_id, cur_entry)], ) ), + /* the previous history entry */ Vdom.( Node.div( [ Attr.classes(["hidden-history-entry"] @ prev_his_classes), ], - List.map(history_entry_view(group_id), prev_lst), + List.map(history_hidden_entry_view(group_id), prev_lst), ) ), ], @@ -296,8 +313,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - isexpanded, - true, + ~is_expanded, + ~has_hidden_part=true, group_id, title_entry, ), @@ -311,23 +328,31 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let prev_history_view = history => { + let prev_history_view = + (history, hidden_history_state: Model.hidden_history_state) => { Vdom.( Node.div( [Attr.classes(["the-prev-history"])], - List.map(group_view(false), history), + List.map( + group_view(~is_cur_group=false, hidden_history_state), + history, + ), ) ); }; - let suc_history_view = history => { + let suc_history_view = + (history, hidden_history_state: Model.hidden_history_state) => { Vdom.( Node.div( [Attr.classes(["the-suc-history"])], - List.map(group_view(false), history), + List.map( + group_view(~is_cur_group=false, hidden_history_state), + history, + ), ) ); }; - let now_history_view = + let cur_history_view = ( history: ( ZList.t( @@ -337,8 +362,14 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { int, bool, ), + hidden_history_state: Model.hidden_history_state, ) => { - Vdom.(Node.div([], [group_view(true, history)])); + Vdom.( + Node.div( + [], + [group_view(~is_cur_group=true, hidden_history_state, history)], + ) + ); }; let history_view = (model: Model.t) => { let (prev_his, cur_group, suc_his) = model.undo_history; @@ -347,9 +378,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Node.div( [Attr.classes(["the-history"])], [ - suc_history_view(List.rev(suc_his)), - now_history_view(cur_group), - prev_history_view(List.rev(prev_his)), + suc_history_view(List.rev(suc_his), model.hidden_history_state), + cur_history_view(cur_group, model.hidden_history_state), + prev_history_view( + List.rev(prev_his), + model.hidden_history_state, + ), ], ) ); @@ -378,11 +412,60 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | (_, Some(_), _) => display_content }; }; + let undo_button = + Vdom.( + Node.div( + [ + Attr.classes(["history_button"]), + Attr.id("undo-button"), + Attr.on_click(_ => inject(Update.Action.Undo)), + ], + [Node.text("Undo")], + ) + ); + + let redo_button = + Vdom.( + Node.div( + [ + Attr.classes(["history_button"]), + Attr.id("redo-button"), + Attr.on_click(_ => inject(Update.Action.Redo)), + ], + [Node.text("Redo")], + ) + ); + let expand_button = (hidden_history_state: Model.hidden_history_state) => { + let icon_classes = + switch (hidden_history_state) { + | OpenAll + | CurOpenIgnore => ["history-tab-icon-open"] + | CloseAll + | CurCloseIgnore => ["history-tab-icon-close"] + }; + Vdom.( + Node.div( + [ + Attr.classes(icon_classes), + Attr.on_click(_ => inject(Update.Action.ToggleHiddenHistoryAll)), + ], + [], + ) + ); + }; + let button_bar_view = hidden_history_state => + Vdom.( + Node.div( + [Attr.classes(["history_button_bar"])], + [undo_button, redo_button, expand_button(hidden_history_state)], + ) + ); Vdom.( Node.div( [Attr.classes(["panel", "context-inspector-panel"])], [ Panel.view_of_main_title_bar("history"), + button_bar_view(model.hidden_history_state), Node.div( [Attr.classes(["panel-body", "context-inspector-body"])], [history_view(model)], diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 959469271d..21d283ddd9 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1230,9 +1230,12 @@ button:disabled { } .the-history-title-entry { float: left; - cursor: pointer; width: 100%; } +.the-history-title-content { + cursor: pointer; + float: left; +} .the-prev-history { color: #0d0a05; background-color: #f0fff3; @@ -1242,7 +1245,7 @@ button:disabled { color: dimgray; background-color: lightgray; } -.the-now-history { +.the-cur-history { color: #b30000; overflow-anchor: auto; background-color: lightgoldenrodyellow; @@ -1272,4 +1275,15 @@ button:disabled { border-bottom: 8px solid transparent; border-top: 8px solid transparent; border-right: 10px solid #558e62; +} + +.history_button_bar { + text-transform: uppercase; + color: white; + padding: 3px; + padding-right: 0px; + font-size: 10pt; + font-weight: bold; + background-color: var(--title-bar-color); + } \ No newline at end of file From 1576b9a4434f6e441758e86e52d5bb8f2b31e452 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 11 Jan 2020 15:24:56 -0500 Subject: [PATCH 24/60] fix toggle-all hidden history button --- src/hazelweb/Model.re | 28 ++----------- src/hazelweb/UndoHistory.re | 22 +++++++---- src/hazelweb/Update.re | 25 ++++++++---- src/hazelweb/gui/UndoHistoryList.re | 61 +++++++++-------------------- src/hazelweb/www/style.css | 4 +- 5 files changed, 55 insertions(+), 85 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 39c29aa306..e13cfe52bb 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -85,11 +85,7 @@ type has_result_state = { type result_state = | ResultsDisabled | Result(has_result_state); -type hidden_history_state = - | OpenAll - | CloseAll - | CurOpenIgnore - | CurCloseIgnore; + type t = { cardstacks, cardstacks_state /* these are derived from the cardstack state: */, @@ -104,7 +100,7 @@ type t = { right_sidebar_open: bool, show_content_editable: bool, show_presentation: bool, - hidden_history_state, + all_hidden_history_expand: bool, undo_history: UndoHistory.t, }; @@ -390,7 +386,7 @@ let init = (): t => { right_sidebar_open: true, show_content_editable: false, show_presentation: false, - hidden_history_state: CloseAll, + all_hidden_history_expand: false, }; }; @@ -474,24 +470,6 @@ let toggle_right_sidebar = (model: t): t => { right_sidebar_open: !model.right_sidebar_open, }; -let toggle_hidden_history_all = (model: t): t => { - ...model, - hidden_history_state: - switch (model.hidden_history_state) { - | OpenAll - | CurOpenIgnore => CloseAll - | CloseAll - | CurCloseIgnore => OpenAll - }, -}; -let ignore_hidden_history_button = hidden_history_state => { - switch (hidden_history_state) { - | OpenAll - | CurOpenIgnore => CurOpenIgnore - | CloseAll - | CurCloseIgnore => CurCloseIgnore - }; -}; let load_example = (model: t, block: UHExp.block): t => model |> update_edit_state( diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index d84d5f8be0..090314fa81 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -87,14 +87,10 @@ let redo = (undo_history: t): t => { let undoable_action = (action: Action.t): bool => { switch (action) { - | UpdateApPalette(_) => - JSUtil.log("UpdateApPalette!!!"); - true; + | UpdateApPalette(_) | Delete - | Backspace => true - | Construct(_) => - JSUtil.log("Construct!!!"); - true; + | Backspace + | Construct(_) => true | MoveTo(_) | MoveToBefore(_) | MoveLeft @@ -107,3 +103,15 @@ let undoable_action = (action: Action.t): bool => { | ShiftDown => false }; }; + +let set_all_hidden_history = (undo_history: t, expanded: bool): t => { + let close_group_entry = (entry: undo_history_group) => { + let (group_lst, id, _) = entry; + (group_lst, id, expanded); + }; + ( + List.map(close_group_entry, ZList.prj_prefix(undo_history)), + close_group_entry(ZList.prj_z(undo_history)), + List.map(close_group_entry, ZList.prj_suffix(undo_history)), + ); +}; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 9de8e2926b..55bcae6171 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -335,8 +335,6 @@ let apply_action = }; }; | ToggleHistoryGroup(gp_id) => - let new_hidden_history_state = - Model.ignore_hidden_history_button(model.hidden_history_state); let (_, cur_gp_id, _) = ZList.prj_z(model.undo_history); let erase_func = his => his; let his_lst = ZList.erase(model.undo_history, erase_func); @@ -349,13 +347,24 @@ let apply_action = let new_his_lst = ZList.erase(after_toggle, erase_func); switch (ZList.split_at(cur_gp_id, new_his_lst)) { | None => failwith("Impossible because undo_history is non-empty") - | Some(new_history) => { - ...model, - hidden_history_state: new_hidden_history_state, - undo_history: new_history, - } + | Some(new_history) => {...model, undo_history: new_history} }; }; - | ToggleHiddenHistoryAll => Model.toggle_hidden_history_all(model) + | ToggleHiddenHistoryAll => + if (model.all_hidden_history_expand) { + { + ...model, + all_hidden_history_expand: false, + undo_history: + UndoHistory.set_all_hidden_history(model.undo_history, false), + }; + } else { + { + ...model, + all_hidden_history_expand: true, + undo_history: + UndoHistory.set_all_hidden_history(model.undo_history, true), + }; + } }; }; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 2ab79dcce1..860f1f0ecf 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -57,7 +57,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.( Node.div( [ - Attr.classes(["the-history-entry"]), + Attr.classes(["the-hidden-history-entry"]), Attr.on_click(_ => inject(Update.Action.ShiftHistory(group_id, elt_id)) ), @@ -84,6 +84,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; let history_tab_icon = (group_id: int) => if (has_hidden_part) { + /* expand icon*/ Vdom.( Node.div( [ @@ -129,7 +130,6 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let group_view = ( ~is_cur_group: bool, - hidden_history_state: Model.hidden_history_state, group_entry: ( ZList.t( UndoHistory.undo_history_entry, @@ -175,13 +175,6 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | [] => false | _ => true }; - let is_expanded = - switch (hidden_history_state) { - | OpenAll => true - | CloseAll => false - | CurOpenIgnore - | CurCloseIgnore => is_expanded - }; if (is_expanded) { Vdom.( Node.div( @@ -328,27 +321,19 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let prev_history_view = - (history, hidden_history_state: Model.hidden_history_state) => { + let prev_history_view = history => { Vdom.( Node.div( [Attr.classes(["the-prev-history"])], - List.map( - group_view(~is_cur_group=false, hidden_history_state), - history, - ), + List.map(group_view(~is_cur_group=false), history), ) ); }; - let suc_history_view = - (history, hidden_history_state: Model.hidden_history_state) => { + let suc_history_view = history => { Vdom.( Node.div( [Attr.classes(["the-suc-history"])], - List.map( - group_view(~is_cur_group=false, hidden_history_state), - history, - ), + List.map(group_view(~is_cur_group=false), history), ) ); }; @@ -362,14 +347,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { int, bool, ), - hidden_history_state: Model.hidden_history_state, ) => { - Vdom.( - Node.div( - [], - [group_view(~is_cur_group=true, hidden_history_state, history)], - ) - ); + Vdom.(Node.div([], [group_view(~is_cur_group=true, history)])); }; let history_view = (model: Model.t) => { let (prev_his, cur_group, suc_his) = model.undo_history; @@ -378,12 +357,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Node.div( [Attr.classes(["the-history"])], [ - suc_history_view(List.rev(suc_his), model.hidden_history_state), - cur_history_view(cur_group, model.hidden_history_state), - prev_history_view( - List.rev(prev_his), - model.hidden_history_state, - ), + suc_history_view(List.rev(suc_his)), + cur_history_view(cur_group), + prev_history_view(List.rev(prev_his)), ], ) ); @@ -435,13 +411,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [Node.text("Redo")], ) ); - let expand_button = (hidden_history_state: Model.hidden_history_state) => { + let expand_button = (all_hidden_history_expand: bool) => { let icon_classes = - switch (hidden_history_state) { - | OpenAll - | CurOpenIgnore => ["history-tab-icon-open"] - | CloseAll - | CurCloseIgnore => ["history-tab-icon-close"] + if (all_hidden_history_expand) { + ["history-tab-icon-open"]; + } else { + ["history-tab-icon-close"]; }; Vdom.( Node.div( @@ -453,11 +428,11 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ); }; - let button_bar_view = hidden_history_state => + let button_bar_view = (all_hidden_history_expand: bool) => Vdom.( Node.div( [Attr.classes(["history_button_bar"])], - [undo_button, redo_button, expand_button(hidden_history_state)], + [undo_button, redo_button, expand_button(all_hidden_history_expand)], ) ); Vdom.( @@ -465,7 +440,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [Attr.classes(["panel", "context-inspector-panel"])], [ Panel.view_of_main_title_bar("history"), - button_bar_view(model.hidden_history_state), + button_bar_view(model.all_hidden_history_expand), Node.div( [Attr.classes(["panel-body", "context-inspector-body"])], [history_view(model)], diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 21d283ddd9..3f97b53823 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1209,14 +1209,14 @@ button:disabled { /* text-transform: uppercase; */ font-size: 75%; } -.the-history-entry { +.the-hidden-history-entry { cursor: pointer; padding: 5px 5px 5px 20px; font-size: 12pt; width: 100%; border: 1px outset #ffffff; } -.the-history-entry:hover { +.the-hidden-history-entry:hover { color: #b30000; background-color: lightgoldenrodyellow; } From 6d78894b5dec96850eb702a346676e93c2ec4e0c Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 11 Jan 2020 17:17:07 -0500 Subject: [PATCH 25/60] complete edit action panel without card splited --- src/hazelweb/gui/UndoHistoryList.re | 30 ++++++++----- src/hazelweb/www/style.css | 70 ++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 860f1f0ecf..ba2fb2dc42 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -78,9 +78,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let (_, action, elt_id) = undo_history_entry; let icon_classes = if (is_expanded) { - ["history-tab-icon-open"]; + ["down-triangle", "history-tab-icon"]; } else { - ["history-tab-icon-close"]; + ["left-triangle", "history-tab-icon"]; }; let history_tab_icon = (group_id: int) => if (has_hidden_part) { @@ -392,11 +392,16 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.( Node.div( [ - Attr.classes(["history_button"]), - Attr.id("undo-button"), + Attr.classes(["history-button"]), Attr.on_click(_ => inject(Update.Action.Undo)), ], - [Node.text("Undo")], + [ + Node.div( + [Attr.classes(["undo-button-txt"])], + [Node.text("Undo")], + ), + Node.div([Attr.classes(["undo-button", "redo-undo-icon"])], []), + ], ) ); @@ -404,19 +409,24 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.( Node.div( [ - Attr.classes(["history_button"]), - Attr.id("redo-button"), + Attr.classes(["history-button"]), Attr.on_click(_ => inject(Update.Action.Redo)), ], - [Node.text("Redo")], + [ + Node.div([Attr.classes(["redo-button", "redo-undo-icon"])], []), + Node.div( + [Attr.classes(["redo-button-txt"])], + [Node.text("Redo")], + ), + ], ) ); let expand_button = (all_hidden_history_expand: bool) => { let icon_classes = if (all_hidden_history_expand) { - ["history-tab-icon-open"]; + ["all-history-tab-icon-open", "history-tab-icon"]; } else { - ["history-tab-icon-close"]; + ["all-history-tab-icon-close", "history-tab-icon"]; }; Vdom.( Node.div( diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 3f97b53823..59c05e9432 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1257,33 +1257,73 @@ button:disabled { width: 100%; display: grid; } -.history-tab-icon-open { - cursor: pointer; - float: right; - width: 0; - height: 0; +.history-tab-icon { + cursor: pointer; + float: right; + width: 0; + height: 0; +} +.down-triangle { border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 10px solid #558e62; } -.history-tab-icon-close { - cursor: pointer; - float: right; - width: 0; - height: 0; +.left-triangle { border-bottom: 8px solid transparent; border-top: 8px solid transparent; border-right: 10px solid #558e62; } +.all-history-tab-icon-open { + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 10px solid white; +} + +.all-history-tab-icon-close, .undo-button { +border-bottom: 8px solid transparent; +border-top: 8px solid transparent; +border-right: 10px solid white; +} +.redo-button { + border-bottom: 8px solid transparent; + border-top: 8px solid transparent; + border-left: 10px solid white; +} +.redo-undo-icon { + display: table-cell; + margin-left: 10px; + margin-right: 10px; +} + + .history_button_bar { text-transform: uppercase; color: white; - padding: 3px; - padding-right: 0px; + padding: 3px 5px 3px 3px; font-size: 10pt; - font-weight: bold; - background-color: var(--title-bar-color); + background-color: #678a61; + display: table; +} +.history-button { + cursor: pointer; + margin-right: 5px; + display: inline-table; +} +.undo-button-txt { + + display: table-cell; + padding-right: 15px; + vertical-align: center; +} + +.redo-button-txt { + + display: table-cell; + padding-left: 15px; + vertical-align: center; + } + + -} \ No newline at end of file From 61968f8be2af4ddb672697b241afe80fa1d19756 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 14 Jan 2020 15:09:36 -0500 Subject: [PATCH 26/60] undo_history store cardstacks_state --- src/hazelweb/Model.re | 117 +++++++++++++++++++++++++++- src/hazelweb/UndoHistory.re | 99 ----------------------- src/hazelweb/Update.re | 25 ++---- src/hazelweb/gui/UndoHistoryList.re | 10 +-- 4 files changed, 122 insertions(+), 129 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index e13cfe52bb..57a265cc02 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -86,6 +86,16 @@ type result_state = | ResultsDisabled | Result(has_result_state); +/*new edit state, the previous action, id*/ +type undo_history_entry = (cardstacks_state, option(Action.t), int); +/* group edit action, group id */ +type undo_history_group = ( + ZList.t(undo_history_entry, undo_history_entry), + int, + bool, +); +type undo_history = ZList.t(undo_history_group, undo_history_group); + type t = { cardstacks, cardstacks_state /* these are derived from the cardstack state: */, @@ -101,7 +111,46 @@ type t = { show_content_editable: bool, show_presentation: bool, all_hidden_history_expand: bool, - undo_history: UndoHistory.t, + undo_history, +}; + +let push_edit_state = + (undo_history, cardstacks_state, action: option(Action.t)): undo_history => { + let (prev_group, prev_group_id, _) = ZList.prj_z(undo_history); + let (_, prev_action, prev_id) = ZList.prj_z(prev_group); + if (Action.in_same_history_group(action, prev_action)) { + /* first add new edit state to the end, then shift_next */ + let after_push = ( + ZList.prj_prefix(prev_group), + ZList.prj_z(prev_group), + [(cardstacks_state, action, prev_id + 1)], + ); + let group_after_push = + switch (ZList.shift_next(after_push)) { + | None => failwith("Impossible because suffix is non-empty") + | Some(new_group) => new_group + }; + ( + ZList.prj_prefix(undo_history), + (group_after_push, prev_group_id, false), + [], + ); + } else { + let new_group = ( + ([], (cardstacks_state, action, 0), []), + prev_group_id + 1, + false, + ); + let after_push = ( + ZList.prj_prefix(undo_history), + ZList.prj_z(undo_history), + [new_group], + ); + switch (ZList.shift_next(after_push)) { + | None => failwith("Impossible because suffix is non-empty") + | Some(new_history) => new_history + }; + }; }; let cardstack_state_of = model => ZList.prj_z(model.cardstacks_state); @@ -381,7 +430,11 @@ let init = (): t => { user_newlines: CursorPath.StepsMap.empty, selected_example: None, is_cell_focused: false, - undo_history: ([], (([], (edit_state, None, 0), []), 0, false), []), + undo_history: ( + [], + (([], (cardstacks_state, None, 0), []), 0, false), + [], + ), left_sidebar_open: false, right_sidebar_open: true, show_content_editable: false, @@ -409,9 +462,9 @@ let perform_edit_action = (model: t, a: Action.t): t => { let new_model = model |> update_edit_state(new_edit_state); let new_history = if (UndoHistory.undoable_action(a)) { - UndoHistory.push_edit_state( + push_edit_state( model.undo_history, - new_edit_state, + new_model.cardstacks_state, Some(a), ); } else { @@ -483,3 +536,59 @@ let load_example = (model: t, block: UHExp.block): t => let focus_cell = model => {...model, is_cell_focused: true}; let blur_cell = model => {...model, is_cell_focused: false}; + +let undo = (model: t): t => { + let new_history = { + let (group_now, gp_id, _) = ZList.prj_z(model.undo_history); + switch (ZList.shift_prev(group_now)) { + | None => + switch (ZList.shift_prev(model.undo_history)) { + | None => model.undo_history + | Some(new_history) => + let (group_lst, id, _) = ZList.prj_z(new_history); + let new_group = (ZList.shift_end(group_lst), id, true); /* is_expanded=true because this group should be expanded*/ + ZList.replace_z(new_history, new_group); + } + | Some(new_group) => + ZList.replace_z(model.undo_history, (new_group, gp_id, true)) /* is_expanded=true because this group should be expanded*/ + }; + }; + let (group_now, _, _) = ZList.prj_z(new_history); + let (new_cardstacks_state, _, _) = ZList.prj_z(group_now); + let model' = update_cardstacks_state(model, new_cardstacks_state); + {...model', undo_history: new_history}; +}; + +let redo = (model: t): t => { + let new_history = { + let (group_now, gp_id, _) = ZList.prj_z(model.undo_history); + switch (ZList.shift_next(group_now)) { + | None => + switch (ZList.shift_next(model.undo_history)) { + | None => model.undo_history + | Some(new_history) => + let (group_lst, id, _) = ZList.prj_z(new_history); + let new_group = (ZList.shift_front(group_lst), id, true); /* is_expanded=true because this group should be expanded when redo*/ + ZList.replace_z(new_history, new_group); + } + | Some(new_group) => + ZList.replace_z(model.undo_history, (new_group, gp_id, true)) + }; + }; + let (group_now, _, _) = ZList.prj_z(new_history); + let (new_cardstacks_state, _, _) = ZList.prj_z(group_now); + let model' = update_cardstacks_state(model, new_cardstacks_state); + {...model', undo_history: new_history}; +}; + +let set_all_hidden_history = (undo_history, expanded: bool): undo_history => { + let close_group_entry = (entry: undo_history_group) => { + let (group_lst, id, _) = entry; + (group_lst, id, expanded); + }; + ( + List.map(close_group_entry, ZList.prj_prefix(undo_history)), + close_group_entry(ZList.prj_z(undo_history)), + List.map(close_group_entry, ZList.prj_suffix(undo_history)), + ); +}; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 090314fa81..0e8a1e34bc 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -1,90 +1,3 @@ -module ZList = GeneralUtil.ZList; -/*new edit state, the previous action, id*/ -type undo_history_entry = (Statics.edit_state, option(Action.t), int); -/* group edit action, group id */ -type undo_history_group = ( - ZList.t(undo_history_entry, undo_history_entry), - int, - bool, -); -type t = ZList.t(undo_history_group, undo_history_group); - -let push_edit_state = - ( - undo_history: t, - edit_state: Statics.edit_state, - action: option(Action.t), - ) - : t => { - let (prev_group, prev_group_id, _) = ZList.prj_z(undo_history); - let (_, prev_action, prev_id) = ZList.prj_z(prev_group); - if (Action.in_same_history_group(action, prev_action)) { - /* first add new edit state to the end, then shift_next */ - let after_push = ( - ZList.prj_prefix(prev_group), - ZList.prj_z(prev_group), - [(edit_state, action, prev_id + 1)], - ); - let group_after_push = - switch (ZList.shift_next(after_push)) { - | None => failwith("Impossible because suffix is non-empty") - | Some(new_group) => new_group - }; - ( - ZList.prj_prefix(undo_history), - (group_after_push, prev_group_id, false), - [], - ); - } else { - let new_group = ( - ([], (edit_state, action, 0), []), - prev_group_id + 1, - false, - ); - let after_push = ( - ZList.prj_prefix(undo_history), - ZList.prj_z(undo_history), - [new_group], - ); - switch (ZList.shift_next(after_push)) { - | None => failwith("Impossible because suffix is non-empty") - | Some(new_history) => new_history - }; - }; -}; - -let undo = (undo_history: t): t => { - let (group_now, gp_id, _) = ZList.prj_z(undo_history); - switch (ZList.shift_prev(group_now)) { - | None => - switch (ZList.shift_prev(undo_history)) { - | None => undo_history - | Some(new_history) => - let (group_lst, id, _) = ZList.prj_z(new_history); - let new_group = (ZList.shift_end(group_lst), id, true); /* is_expanded=true because this group should be expanded*/ - ZList.replace_z(new_history, new_group); - } - | Some(new_group) => - ZList.replace_z(undo_history, (new_group, gp_id, true)) - }; -}; - -let redo = (undo_history: t): t => { - let (group_now, gp_id, _) = ZList.prj_z(undo_history); - switch (ZList.shift_next(group_now)) { - | None => - switch (ZList.shift_next(undo_history)) { - | None => undo_history - | Some(new_history) => - let (group_lst, id, _) = ZList.prj_z(new_history); - let new_group = (ZList.shift_front(group_lst), id, true); /* is_expanded=true because this group should be expanded when redo*/ - ZList.replace_z(new_history, new_group); - } - | Some(new_group) => - ZList.replace_z(undo_history, (new_group, gp_id, true)) - }; -}; - let undoable_action = (action: Action.t): bool => { switch (action) { | UpdateApPalette(_) @@ -103,15 +16,3 @@ let undoable_action = (action: Action.t): bool => { | ShiftDown => false }; }; - -let set_all_hidden_history = (undo_history: t, expanded: bool): t => { - let close_group_entry = (entry: undo_history_group) => { - let (group_lst, id, _) = entry; - (group_lst, id, expanded); - }; - ( - List.map(close_group_entry, ZList.prj_prefix(undo_history)), - close_group_entry(ZList.prj_z(undo_history)), - List.map(close_group_entry, ZList.prj_suffix(undo_history)), - ); -}; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 55bcae6171..8633093eed 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -302,18 +302,8 @@ let apply_action = }; }; model; - | Undo => - let new_history = UndoHistory.undo(model.undo_history); - let (group_now, _, _) = ZList.prj_z(new_history); - let (new_edit_state, _, _) = ZList.prj_z(group_now); - let new_model = model |> Model.update_edit_state(new_edit_state); - {...new_model, undo_history: new_history}; - | Redo => - let new_history = UndoHistory.redo(model.undo_history); - let (group_now, _, _) = ZList.prj_z(new_history); - let (new_edit_state, _, _) = ZList.prj_z(group_now); - let new_model = model |> Model.update_edit_state(new_edit_state); - {...new_model, undo_history: new_history}; + | Undo => Model.undo(model) + | Redo => Model.redo(model) | ShiftHistory(gp_id, ent_id) => let erase_func = his => his; let his_lst = ZList.erase(model.undo_history, erase_func); @@ -325,8 +315,9 @@ let apply_action = switch (ZList.split_at(ent_id, entry_lst)) { | None => failwith("Impossible because undo_history is non-empty") | Some(group) => - let (new_edit_state, _, _) = ZList.prj_z(group); - let new_model = model |> Model.update_edit_state(new_edit_state); + let (new_cardstacks_state, _, _) = ZList.prj_z(group); + let new_model = + Model.update_cardstacks_state(model, new_cardstacks_state); { ...new_model, undo_history: @@ -355,15 +346,13 @@ let apply_action = { ...model, all_hidden_history_expand: false, - undo_history: - UndoHistory.set_all_hidden_history(model.undo_history, false), + undo_history: Model.set_all_hidden_history(model.undo_history, false), }; } else { { ...model, all_hidden_history_expand: true, - undo_history: - UndoHistory.set_all_hidden_history(model.undo_history, true), + undo_history: Model.set_all_hidden_history(model.undo_history, true), }; } }; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index ba2fb2dc42..72dd268056 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -131,10 +131,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ( ~is_cur_group: bool, group_entry: ( - ZList.t( - UndoHistory.undo_history_entry, - UndoHistory.undo_history_entry, - ), + ZList.t(Model.undo_history_entry, Model.undo_history_entry), int, bool, ), @@ -340,10 +337,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let cur_history_view = ( history: ( - ZList.t( - UndoHistory.undo_history_entry, - UndoHistory.undo_history_entry, - ), + ZList.t(Model.undo_history_entry, Model.undo_history_entry), int, bool, ), From 323a3b4e4db9669784e35f04d24cd627cf635392 Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 16 Jan 2020 19:41:13 -0500 Subject: [PATCH 27/60] readable data structure --- src/hazelweb/Model.re | 141 +++++++++++++++++++--------- src/hazelweb/Update.re | 30 ++++-- src/hazelweb/gui/UndoHistoryList.re | 130 ++++++++++++------------- 3 files changed, 184 insertions(+), 117 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 57a265cc02..6d80267d56 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -87,13 +87,17 @@ type result_state = | Result(has_result_state); /*new edit state, the previous action, id*/ -type undo_history_entry = (cardstacks_state, option(Action.t), int); +type undo_history_entry = { + cardstacks_state, + previous_action: option(Action.t), + elt_id: int, +}; /* group edit action, group id */ -type undo_history_group = ( - ZList.t(undo_history_entry, undo_history_entry), - int, - bool, -); +type undo_history_group = { + state_list: ZList.t(undo_history_entry, undo_history_entry), + group_id: int, + is_expanded: bool, +}; type undo_history = ZList.t(undo_history_group, undo_history_group); type t = { @@ -116,31 +120,45 @@ type t = { let push_edit_state = (undo_history, cardstacks_state, action: option(Action.t)): undo_history => { - let (prev_group, prev_group_id, _) = ZList.prj_z(undo_history); - let (_, prev_action, prev_id) = ZList.prj_z(prev_group); - if (Action.in_same_history_group(action, prev_action)) { + let cur_group = ZList.prj_z(undo_history); + let cur_state = ZList.prj_z(cur_group.state_list); + if (Action.in_same_history_group(action, cur_state.previous_action)) { /* first add new edit state to the end, then shift_next */ let after_push = ( - ZList.prj_prefix(prev_group), - ZList.prj_z(prev_group), - [(cardstacks_state, action, prev_id + 1)], + ZList.prj_prefix(cur_group.state_list), + ZList.prj_z(cur_group.state_list), + [ + { + cardstacks_state, + previous_action: action, + elt_id: cur_state.elt_id + 1, + }, + ], ); - let group_after_push = + let state_list_after_push = switch (ZList.shift_next(after_push)) { | None => failwith("Impossible because suffix is non-empty") - | Some(new_group) => new_group + | Some(new_state_list) => new_state_list }; ( ZList.prj_prefix(undo_history), - (group_after_push, prev_group_id, false), + { + state_list: state_list_after_push, + group_id: cur_group.group_id, + is_expanded: false, + }, /* initial state of group should be folded*/ [], ); } else { - let new_group = ( - ([], (cardstacks_state, action, 0), []), - prev_group_id + 1, - false, - ); + let new_group = { + state_list: ( + [], + {cardstacks_state, previous_action: action, elt_id: 0}, + [], + ), + group_id: cur_group.group_id + 1, + is_expanded: false, + }; let after_push = ( ZList.prj_prefix(undo_history), ZList.prj_z(undo_history), @@ -420,6 +438,11 @@ let init = (): t => { let cardstacks_state = mk_cardstacks_state(cardstacks); let edit_state = ZList.prj_z(ZList.prj_z(cardstacks_state).zcards).edit_state; + let undo_history_state = { + cardstacks_state, + previous_action: None, + elt_id: 0, + }; let compute_results = init_compute_results; { cardstacks, @@ -432,7 +455,11 @@ let init = (): t => { is_cell_focused: false, undo_history: ( [], - (([], (cardstacks_state, None, 0), []), 0, false), + { + state_list: ([], undo_history_state, []), + group_id: 0, + is_expanded: false, + }, [], ), left_sidebar_open: false, @@ -539,56 +566,84 @@ let blur_cell = model => {...model, is_cell_focused: false}; let undo = (model: t): t => { let new_history = { - let (group_now, gp_id, _) = ZList.prj_z(model.undo_history); - switch (ZList.shift_prev(group_now)) { + let cur_group = ZList.prj_z(model.undo_history); + /* shift to previous state in the same group */ + switch (ZList.shift_prev(cur_group.state_list)) { | None => + /*if current group doesn't have previous state, shfit to previous group*/ switch (ZList.shift_prev(model.undo_history)) { | None => model.undo_history | Some(new_history) => - let (group_lst, id, _) = ZList.prj_z(new_history); - let new_group = (ZList.shift_end(group_lst), id, true); /* is_expanded=true because this group should be expanded*/ + let cur_group = ZList.prj_z(new_history); + let new_group = { + state_list: ZList.shift_end(cur_group.state_list), + group_id: cur_group.group_id, + is_expanded: true, + }; /* is_expanded=true because this group should be expanded*/ ZList.replace_z(new_history, new_group); } - | Some(new_group) => - ZList.replace_z(model.undo_history, (new_group, gp_id, true)) /* is_expanded=true because this group should be expanded*/ + | Some(new_state_list) => + ZList.replace_z( + model.undo_history, + { + state_list: new_state_list, + group_id: cur_group.group_id, + is_expanded: true, + }, + ) /* is_expanded=true because this group should be expanded*/ }; }; - let (group_now, _, _) = ZList.prj_z(new_history); - let (new_cardstacks_state, _, _) = ZList.prj_z(group_now); + let cur_group' = ZList.prj_z(new_history); + let new_cardstacks_state = + ZList.prj_z(cur_group'.state_list).cardstacks_state; let model' = update_cardstacks_state(model, new_cardstacks_state); {...model', undo_history: new_history}; }; let redo = (model: t): t => { let new_history = { - let (group_now, gp_id, _) = ZList.prj_z(model.undo_history); - switch (ZList.shift_next(group_now)) { + let cur_group = ZList.prj_z(model.undo_history); + /* shift to previous state in the same group */ + switch (ZList.shift_next(cur_group.state_list)) { | None => + /*if current group doesn't have previous state, shfit to previous group*/ switch (ZList.shift_next(model.undo_history)) { | None => model.undo_history | Some(new_history) => - let (group_lst, id, _) = ZList.prj_z(new_history); - let new_group = (ZList.shift_front(group_lst), id, true); /* is_expanded=true because this group should be expanded when redo*/ + let cur_group = ZList.prj_z(new_history); + let new_group = { + state_list: ZList.shift_front(cur_group.state_list), + group_id: cur_group.group_id, + is_expanded: true, + }; /* is_expanded=true because this group should be expanded when redo*/ ZList.replace_z(new_history, new_group); } - | Some(new_group) => - ZList.replace_z(model.undo_history, (new_group, gp_id, true)) + | Some(new_state_list) => + ZList.replace_z( + model.undo_history, + { + state_list: new_state_list, + group_id: cur_group.group_id, + is_expanded: true, + }, + ) }; }; - let (group_now, _, _) = ZList.prj_z(new_history); - let (new_cardstacks_state, _, _) = ZList.prj_z(group_now); + let cur_group' = ZList.prj_z(new_history); + let new_cardstacks_state = + ZList.prj_z(cur_group'.state_list).cardstacks_state; let model' = update_cardstacks_state(model, new_cardstacks_state); {...model', undo_history: new_history}; }; let set_all_hidden_history = (undo_history, expanded: bool): undo_history => { - let close_group_entry = (entry: undo_history_group) => { - let (group_lst, id, _) = entry; - (group_lst, id, expanded); + let hidden_group = (group: undo_history_group) => { + ...group, + is_expanded: expanded, }; ( - List.map(close_group_entry, ZList.prj_prefix(undo_history)), - close_group_entry(ZList.prj_z(undo_history)), - List.map(close_group_entry, ZList.prj_suffix(undo_history)), + List.map(hidden_group, ZList.prj_prefix(undo_history)), + hidden_group(ZList.prj_z(undo_history)), + List.map(hidden_group, ZList.prj_suffix(undo_history)), ); }; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 8633093eed..c9d7f2713b 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -310,33 +310,43 @@ let apply_action = switch (ZList.split_at(gp_id, his_lst)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => - let (new_group, _, isexpanded) = ZList.prj_z(new_history); - let entry_lst = ZList.erase(new_group, erase_func); - switch (ZList.split_at(ent_id, entry_lst)) { + let cur_group = ZList.prj_z(new_history); + let cur_states_list = ZList.erase(cur_group.state_list, erase_func); + switch (ZList.split_at(ent_id, cur_states_list)) { | None => failwith("Impossible because undo_history is non-empty") - | Some(group) => - let (new_cardstacks_state, _, _) = ZList.prj_z(group); + | Some(new_state_list) => + let new_cardstacks_state = + ZList.prj_z(new_state_list).cardstacks_state; let new_model = Model.update_cardstacks_state(model, new_cardstacks_state); { ...new_model, undo_history: - ZList.replace_z(new_history, (group, gp_id, isexpanded)), + ZList.replace_z( + new_history, + {...cur_group, state_list: new_state_list}, + ), }; }; }; | ToggleHistoryGroup(gp_id) => - let (_, cur_gp_id, _) = ZList.prj_z(model.undo_history); + let cur_group = ZList.prj_z(model.undo_history); let erase_func = his => his; let his_lst = ZList.erase(model.undo_history, erase_func); switch (ZList.split_at(gp_id, his_lst)) { | None => failwith("Impossible because undo_history is non-empty") | Some(history) => - let (gp_lst, _, isexpanded) = ZList.prj_z(history); + let toggle_target_group = ZList.prj_z(history); let after_toggle = - ZList.replace_z(history, (gp_lst, gp_id, !isexpanded)); + ZList.replace_z( + history, + { + ...toggle_target_group, + is_expanded: !toggle_target_group.is_expanded, + }, + ); let new_his_lst = ZList.erase(after_toggle, erase_func); - switch (ZList.split_at(cur_gp_id, new_his_lst)) { + switch (ZList.split_at(cur_group.group_id, new_his_lst)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => {...model, undo_history: new_history} }; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 72dd268056..fa2e5a21b4 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -2,11 +2,12 @@ module Vdom = Virtual_dom.Vdom; module KeyCombo = JSUtil.KeyCombo; module ZList = GeneralUtil.ZList; exception InvalidInstance; - +type undo_history_group = Model.undo_history_group; +type undo_history_entry = Model.undo_history_entry; let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let shape_to_display_string = (shape: Action.shape): string => { switch (shape) { - | SParenthesized => "parentheise" + | SParenthesized => "parentheize" | SNum => "type Num" | SBool => "type Bool" | SList => "type List" @@ -49,9 +50,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let history_hidden_entry_view = (group_id: int, undo_history_entry) => { - let (_, action, elt_id) = undo_history_entry; - switch (action) { + let history_hidden_entry_view = + (group_id: int, undo_history_entry: undo_history_entry) => { + switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ | Some(detail_ac) => Vdom.( @@ -59,7 +60,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["the-hidden-history-entry"]), Attr.on_click(_ => - inject(Update.Action.ShiftHistory(group_id, elt_id)) + inject( + Update.Action.ShiftHistory( + group_id, + undo_history_entry.elt_id, + ), + ) ), ], [Node.text(action_to_display_string(detail_ac))], @@ -73,9 +79,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ~is_expanded: bool, ~has_hidden_part: bool, group_id: int, - undo_history_entry, + undo_history_entry: undo_history_entry, ) => { - let (_, action, elt_id) = undo_history_entry; let icon_classes = if (is_expanded) { ["down-triangle", "history-tab-icon"]; @@ -99,7 +104,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else { Vdom.(Node.div([], [])); }; - switch (action) { + switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) | Some(detail_ac) => Vdom.( @@ -113,7 +118,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["the-history-title-content"]), Attr.on_click(_ => - inject(Update.Action.ShiftHistory(group_id, elt_id)) + inject( + Update.Action.ShiftHistory( + group_id, + undo_history_entry.elt_id, + ), + ) ), ], [Node.text(action_to_display_string(detail_ac))], @@ -127,21 +137,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let group_view = - ( - ~is_cur_group: bool, - group_entry: ( - ZList.t(Model.undo_history_entry, Model.undo_history_entry), - int, - bool, - ), - ) => { - let (group_lst_old_first, group_id, is_expanded) = group_entry; + let group_view = (~is_cur_group: bool, group: undo_history_group) => { /* reverse the undo_history, so the first entry shown in panel is the latest history entry */ - let group_lst_new_first = ( - List.rev(ZList.prj_suffix(group_lst_old_first)), - ZList.prj_z(group_lst_old_first), - List.rev(ZList.prj_prefix(group_lst_old_first)), + let rev_state_list = ( + List.rev(ZList.prj_suffix(group.state_list)), + ZList.prj_z(group.state_list), + List.rev(ZList.prj_prefix(group.state_list)), ); /* if the group containning selected history entry, it should be splited to different css styles */ let suc_his_classes = @@ -162,17 +163,17 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else { []; }; - switch (group_lst_new_first) { - | ([], cur_entry, prev_lst) => - switch (cur_entry) { - | (_, None, _) => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ - | (_, Some(_), _) => + switch (rev_state_list) { + | ([], cur_state, prev_states) => + switch (cur_state.previous_action) { + | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ + | Some(_) => let has_hidden_part = - switch (prev_lst) { + switch (prev_states) { | [] => false | _ => true }; - if (is_expanded) { + if (group.is_expanded) { Vdom.( Node.div( [Attr.classes(["the-history-group"])], @@ -186,10 +187,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - ~is_expanded, + ~is_expanded=group.is_expanded, ~has_hidden_part, - group_id, - cur_entry, + group.group_id, + cur_state, ), ], ) @@ -201,7 +202,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ["hidden-history-entry"] @ prev_his_classes, ), ], - List.map(history_hidden_entry_view(group_id), prev_lst), + List.map( + history_hidden_entry_view(group.group_id), + prev_states, + ), ) ), ], @@ -221,10 +225,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - ~is_expanded, + ~is_expanded=group.is_expanded, ~has_hidden_part, - group_id, - cur_entry, + group.group_id, + cur_state, ), ], ) @@ -235,8 +239,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; } - | ([title_entry, ...suc_lst], cur_entry, prev_lst) => - if (is_expanded) { + | ([title_entry, ...suc_groups], cur_state, prev_states) => + if (group.is_expanded) { Vdom.( Node.div( [Attr.classes(["the-history-group"])], @@ -251,9 +255,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - ~is_expanded, + ~is_expanded=group.is_expanded, ~has_hidden_part=true, - group_id, + group.group_id, title_entry, ), ], @@ -265,7 +269,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ suc_his_classes), ], - List.map(history_hidden_entry_view(group_id), suc_lst), + List.map( + history_hidden_entry_view(group.group_id), + suc_groups, + ), ) ), /* the selected(current) history entry */ @@ -274,7 +281,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ cur_his_classes), ], - [history_hidden_entry_view(group_id, cur_entry)], + [history_hidden_entry_view(group.group_id, cur_state)], ) ), /* the previous history entry */ @@ -283,7 +290,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ prev_his_classes), ], - List.map(history_hidden_entry_view(group_id), prev_lst), + List.map( + history_hidden_entry_view(group.group_id), + prev_states, + ), ) ), ], @@ -303,9 +313,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], [ history_title_entry_view( - ~is_expanded, + ~is_expanded=group.is_expanded, ~has_hidden_part=true, - group_id, + group.group_id, title_entry, ), ], @@ -334,35 +344,27 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ); }; - let cur_history_view = - ( - history: ( - ZList.t(Model.undo_history_entry, Model.undo_history_entry), - int, - bool, - ), - ) => { + let cur_history_view = (history: undo_history_group) => { Vdom.(Node.div([], [group_view(~is_cur_group=true, history)])); }; let history_view = (model: Model.t) => { - let (prev_his, cur_group, suc_his) = model.undo_history; + let (prev_groups, cur_group, suc_groups) = model.undo_history; let display_content = Vdom.( Node.div( [Attr.classes(["the-history"])], [ - suc_history_view(List.rev(suc_his)), + suc_history_view(List.rev(suc_groups)), cur_history_view(cur_group), - prev_history_view(List.rev(prev_his)), + prev_history_view(List.rev(prev_groups)), ], ) ); - let (cur_group_lst, _, _) = cur_group; - let (_, cur_entry, _) = cur_group_lst; - switch (cur_entry) { - | (_, None, _) => + let action = ZList.prj_z(cur_group.state_list).previous_action; + switch (action) { + | None => /*if init state is only history entry */ - if (List.length(suc_his) <= 1) { + if (List.length(suc_groups) <= 1) { Vdom.( Node.div( [Attr.classes(["the-history"])], @@ -379,7 +381,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else { display_content; } - | (_, Some(_), _) => display_content + | Some(_) => display_content }; }; let undo_button = From ca64f96cc4907faff730b7dabf4d32a4f7d2a17c Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 16 Jan 2020 22:09:03 -0500 Subject: [PATCH 28/60] make code more readable. start to try to swap suffix and prefix --- src/hazelcore/GeneralUtil.re | 8 +- src/hazelcore/semantics/Action.re | 113 ++++++++++++++++++---------- src/hazelweb/JSUtil.re | 6 +- src/hazelweb/Model.re | 2 +- src/hazelweb/Update.re | 22 +++--- src/hazelweb/gui/UndoHistoryList.re | 52 +------------ 6 files changed, 100 insertions(+), 103 deletions(-) diff --git a/src/hazelcore/GeneralUtil.re b/src/hazelcore/GeneralUtil.re index 992b495b4c..96aeea68ed 100644 --- a/src/hazelcore/GeneralUtil.re +++ b/src/hazelcore/GeneralUtil.re @@ -316,7 +316,7 @@ module ZList = { }; }; - let shift_front = (zxs: t('a, 'a)): t('a, 'a) => { + let shift_begin = (zxs: t('a, 'a)): t('a, 'a) => { let (prefix, z, suffix) = zxs; switch (prefix) { | [] => zxs @@ -325,6 +325,12 @@ module ZList = { ([], head, suffix); }; }; + + let shift_to = (n: int, xs: t('a, 'a)): option(t('a, 'a)) => { + let (prefix, z, suffix) = xs; + let lst = prefix @ [z, ...suffix]; + split_at(n, lst); + }; }; /* Section StringUtil */ diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index 90552bb336..b12ac49535 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -159,40 +159,38 @@ type result('success) = | CursorEscaped(Side.t) | CantShift | Failed; - -let shape_to_string = (shape: shape): string => { +let shape_to_display_string = (shape: shape): string => { switch (shape) { - | SParenthesized => "SParenthesized" - /* type shapes */ - | SNum => "SNum" - | SBool => "SBool" - | SList => "SList" - /* expression shapes */ - | SAsc => "SAsc" - | SVar(_, _) => "SVar" - | SLam => "SLam" - | SNumLit(_, _) => "SNumLit" - | SListNil => "SListNil" + | SParenthesized => "parentheize" + | SNum => "type Num" + | SBool => "type Bool" + | SList => "type List" + | SAsc => "type inference" + | SVar(varstr, _) => "edit var: " ++ varstr + | SLam => "add lambada" + | SNumLit(value, _) => "edit number: " ++ string_of_int(value) + | SListNil => "add []" | SInj(direction) => switch (direction) { - | L => "SInjL" - | R => "SInjR" + | L => "inject left" + | R => "inject right" } - | SLet => "SLet" - | SLine => "SLine" - | SCase => "SCase" - | SOp(op) => "SOp" ++ op_shape_to_string(op) - | SApPalette(_) => "SApPalette" + | SLet => "bulid 'let'" + | SLine => "add new line[s]" + | SCase => "add case" + | SOp(op) => "add operator " ++ op_shape_to_string(op) + | SApPalette(_) => "appalette?" /* pattern-only shapes */ - | SWild => "SWild" + | SWild => "wild?" }; }; -let action_to_comp_string = (action: t): string => { + +let action_to_display_string = (action: t) => { switch (action) { - | UpdateApPalette(_) => "UpdateApPalette" - | Delete => "Delete" - | Backspace => "Backspace" - | Construct(shape) => shape_to_string(shape) + | UpdateApPalette(_) => "updatePlate?" + | Delete => "delete" + | Backspace => "backspace" + | Construct(shape) => shape_to_display_string(shape) | MoveTo(_) | MoveToBefore(_) | MoveLeft @@ -202,22 +200,59 @@ let action_to_comp_string = (action: t): string => { | ShiftLeft | ShiftRight | ShiftUp - | ShiftDown => "Not Show In undo_history" + | ShiftDown => "will not show in undo_history" + }; +}; +let is_same_shape = (shape_1: shape, shape_2: shape): bool => { + switch (shape_1, shape_2) { + | (SVar(_, _), SVar(_, _)) + | (SNumLit(_, _), SNumLit(_, _)) + | (SLine, SLine) => true + | (SParenthesized, _) + | (SNum, _) + | (SBool, _) + | (SList, _) + | (SAsc, _) + | (SVar(_, _), _) + | (SLam, _) + | (SNumLit(_, _), _) + | (SListNil, _) + | (SInj(_), _) + | (SLet, _) + | (SLine, _) + | (SCase, _) + | (SOp(_), _) + | (SApPalette(_), _) + | (SWild, _) => false }; }; - let in_same_history_group = (action_1: option(t), action_2: option(t)): bool => { - let action_1_string = - switch (action_1) { - | None => "None" - | Some(action) => action_to_comp_string(action) - }; - let action_2_string = - switch (action_2) { - | None => "None" - | Some(action) => action_to_comp_string(action) - }; - action_1_string == action_2_string; + switch (action_1, action_2) { + | (None, _) + | (_, None) => false + | (Some(detail_action_1), Some(detail_action_2)) => + switch (detail_action_1, detail_action_2) { + | (UpdateApPalette(_), UpdateApPalette(_)) + | (Delete, Delete) + | (Backspace, Backspace) => true + | (Construct(shape_1), Construct(shape_2)) => + is_same_shape(shape_1, shape_2) + | (UpdateApPalette(_), _) + | (Delete, _) + | (Backspace, _) + | (Construct(_), _) => false + | (MoveTo(_), _) + | (MoveToBefore(_), _) + | (MoveLeft, _) + | (MoveRight, _) + | (MoveToNextHole, _) + | (MoveToPrevHole, _) + | (ShiftLeft, _) + | (ShiftRight, _) + | (ShiftUp, _) + | (ShiftDown, _) => failwith("not undoable actions, will not be matched") + } + }; }; let make_ty_OpSeqZ = (zty0: ZTyp.t, surround: ZTyp.opseq_surround): ZTyp.t => { diff --git a/src/hazelweb/JSUtil.re b/src/hazelweb/JSUtil.re index 3705c00df9..594fe53556 100644 --- a/src/hazelweb/JSUtil.re +++ b/src/hazelweb/JSUtil.re @@ -145,7 +145,11 @@ let set_caret = (anchorNode, offset) => { selection##removeAllRanges; selection##addRange(range); }; - +/* let scroll_anchor = (container_id, anchor_id) => { + let container = Dom_html.document##getElementById(Js.string(container_id)); + let scroll_to = Dom_html.document##getElementById(Js.string(anchor_id)); + container##scrollTop=scroll_to##offsetTop; + }; */ let reset_caret = () => { let selection = Dom_html.window##getSelection; if (selection##.rangeCount <= 0) { diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 6d80267d56..3125665ea0 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -612,7 +612,7 @@ let redo = (model: t): t => { | Some(new_history) => let cur_group = ZList.prj_z(new_history); let new_group = { - state_list: ZList.shift_front(cur_group.state_list), + state_list: ZList.shift_begin(cur_group.state_list), group_id: cur_group.group_id, is_expanded: true, }; /* is_expanded=true because this group should be expanded when redo*/ diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index c9d7f2713b..546c6ac89c 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -304,15 +304,14 @@ let apply_action = model; | Undo => Model.undo(model) | Redo => Model.redo(model) - | ShiftHistory(gp_id, ent_id) => - let erase_func = his => his; - let his_lst = ZList.erase(model.undo_history, erase_func); - switch (ZList.split_at(gp_id, his_lst)) { + | ShiftHistory(gp_id, elt_id) => + /*shift to the group with group_id = gp_id*/ + switch (ZList.shift_to(gp_id: int, model.undo_history)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => let cur_group = ZList.prj_z(new_history); - let cur_states_list = ZList.erase(cur_group.state_list, erase_func); - switch (ZList.split_at(ent_id, cur_states_list)) { + /*shift to the element with elt_id*/ + switch (ZList.shift_to(elt_id, cur_group.state_list)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_state_list) => let new_cardstacks_state = @@ -328,12 +327,11 @@ let apply_action = ), }; }; - }; + } | ToggleHistoryGroup(gp_id) => let cur_group = ZList.prj_z(model.undo_history); - let erase_func = his => his; - let his_lst = ZList.erase(model.undo_history, erase_func); - switch (ZList.split_at(gp_id, his_lst)) { + /*shift to toggle group and change expanded state*/ + switch (ZList.shift_to(gp_id, model.undo_history)) { | None => failwith("Impossible because undo_history is non-empty") | Some(history) => let toggle_target_group = ZList.prj_z(history); @@ -345,8 +343,8 @@ let apply_action = is_expanded: !toggle_target_group.is_expanded, }, ); - let new_his_lst = ZList.erase(after_toggle, erase_func); - switch (ZList.split_at(cur_group.group_id, new_his_lst)) { + /*shift back to the current group*/ + switch (ZList.shift_to(cur_group.group_id, after_toggle)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => {...model, undo_history: new_history} }; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index fa2e5a21b4..facb576130 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -1,55 +1,9 @@ module Vdom = Virtual_dom.Vdom; -module KeyCombo = JSUtil.KeyCombo; module ZList = GeneralUtil.ZList; -exception InvalidInstance; type undo_history_group = Model.undo_history_group; type undo_history_entry = Model.undo_history_entry; -let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { - let shape_to_display_string = (shape: Action.shape): string => { - switch (shape) { - | SParenthesized => "parentheize" - | SNum => "type Num" - | SBool => "type Bool" - | SList => "type List" - | SAsc => "add ':'" - | SVar(varstr, _) => "edit var: " ++ varstr - | SLam => "add lambada" - | SNumLit(value, _) => "edit number: " ++ string_of_int(value) - | SListNil => "add list" - | SInj(direction) => - switch (direction) { - | L => "inject left" - | R => "inject right" - } - | SLet => "bulid 'let'" - | SLine => "add new line[s]" - | SCase => "add case" - | SOp(op) => "add operator " ++ Action.op_shape_to_string(op) - | SApPalette(_) => "appalette?" - /* pattern-only shapes */ - | SWild => "wild?" - }; - }; - - let action_to_display_string = (action: Action.t) => { - switch (action) { - | UpdateApPalette(_) => "updatePlate?" - | Delete => "delete" - | Backspace => "backspace" - | Construct(shape) => shape_to_display_string(shape) - | MoveTo(_) - | MoveToBefore(_) - | MoveLeft - | MoveRight - | MoveToNextHole - | MoveToPrevHole - | ShiftLeft - | ShiftRight - | ShiftUp - | ShiftDown => "will not show in undo_history" - }; - }; +let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let history_hidden_entry_view = (group_id: int, undo_history_entry: undo_history_entry) => { switch (undo_history_entry.previous_action) { @@ -68,7 +22,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ), ], - [Node.text(action_to_display_string(detail_ac))], + [Node.text(Action.action_to_display_string(detail_ac))], ) ) }; @@ -126,7 +80,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ), ], - [Node.text(action_to_display_string(detail_ac))], + [Node.text(Action.action_to_display_string(detail_ac))], ), history_tab_icon(group_id), ], From 5433dee6ecf02a7ac971cdab8cf6704281a3aaca Mon Sep 17 00:00:00 2001 From: Zoe Date: Thu, 16 Jan 2020 23:10:29 -0500 Subject: [PATCH 29/60] bug in line 121 in historypanel.re --- src/hazelweb/Model.re | 60 +++++++++++++---------------- src/hazelweb/Update.re | 28 ++++++++++++-- src/hazelweb/gui/UndoHistoryList.re | 16 ++++---- 3 files changed, 58 insertions(+), 46 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 3125665ea0..246d570362 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -123,31 +123,27 @@ let push_edit_state = let cur_group = ZList.prj_z(undo_history); let cur_state = ZList.prj_z(cur_group.state_list); if (Action.in_same_history_group(action, cur_state.previous_action)) { - /* first add new edit state to the end, then shift_next */ - let after_push = ( - ZList.prj_prefix(cur_group.state_list), - ZList.prj_z(cur_group.state_list), + let new_state = { + cardstacks_state, + previous_action: action, + elt_id: cur_state.elt_id + 1, + }; + let state_list_after_push = ( + [], + new_state, [ - { - cardstacks_state, - previous_action: action, - elt_id: cur_state.elt_id + 1, - }, + ZList.prj_z(cur_group.state_list), + ...ZList.prj_suffix(cur_group.state_list), ], ); - let state_list_after_push = - switch (ZList.shift_next(after_push)) { - | None => failwith("Impossible because suffix is non-empty") - | Some(new_state_list) => new_state_list - }; ( - ZList.prj_prefix(undo_history), + [], { state_list: state_list_after_push, group_id: cur_group.group_id, is_expanded: false, }, /* initial state of group should be folded*/ - [], + ZList.prj_suffix(undo_history), ); } else { let new_group = { @@ -159,15 +155,11 @@ let push_edit_state = group_id: cur_group.group_id + 1, is_expanded: false, }; - let after_push = ( - ZList.prj_prefix(undo_history), - ZList.prj_z(undo_history), - [new_group], + ( + [], + new_group, + [ZList.prj_z(undo_history), ...ZList.prj_suffix(undo_history)], ); - switch (ZList.shift_next(after_push)) { - | None => failwith("Impossible because suffix is non-empty") - | Some(new_history) => new_history - }; }; }; @@ -568,19 +560,19 @@ let undo = (model: t): t => { let new_history = { let cur_group = ZList.prj_z(model.undo_history); /* shift to previous state in the same group */ - switch (ZList.shift_prev(cur_group.state_list)) { + switch (ZList.shift_next(cur_group.state_list)) { | None => /*if current group doesn't have previous state, shfit to previous group*/ - switch (ZList.shift_prev(model.undo_history)) { + switch (ZList.shift_next(model.undo_history)) { | None => model.undo_history | Some(new_history) => - let cur_group = ZList.prj_z(new_history); - let new_group = { - state_list: ZList.shift_end(cur_group.state_list), - group_id: cur_group.group_id, + let new_group = ZList.prj_z(new_history); + let new_group' = { + ...new_group, + state_list: ZList.shift_begin(new_group.state_list), /*pointer may be in the wrong position after clicking history panel*/ is_expanded: true, }; /* is_expanded=true because this group should be expanded*/ - ZList.replace_z(new_history, new_group); + ZList.replace_z(new_history, new_group'); } | Some(new_state_list) => ZList.replace_z( @@ -604,15 +596,15 @@ let redo = (model: t): t => { let new_history = { let cur_group = ZList.prj_z(model.undo_history); /* shift to previous state in the same group */ - switch (ZList.shift_next(cur_group.state_list)) { + switch (ZList.shift_prev(cur_group.state_list)) { | None => /*if current group doesn't have previous state, shfit to previous group*/ - switch (ZList.shift_next(model.undo_history)) { + switch (ZList.shift_prev(model.undo_history)) { | None => model.undo_history | Some(new_history) => let cur_group = ZList.prj_z(new_history); let new_group = { - state_list: ZList.shift_begin(cur_group.state_list), + state_list: ZList.shift_end(cur_group.state_list), /*pointer may be in the wrong position after clicking history panel*/ group_id: cur_group.group_id, is_expanded: true, }; /* is_expanded=true because this group should be expanded when redo*/ diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 546c6ac89c..a1fac3decf 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -306,12 +306,22 @@ let apply_action = | Redo => Model.redo(model) | ShiftHistory(gp_id, elt_id) => /*shift to the group with group_id = gp_id*/ - switch (ZList.shift_to(gp_id: int, model.undo_history)) { + switch ( + ZList.shift_to( + ZList.length(model.undo_history) - gp_id, + model.undo_history, + ) + ) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => let cur_group = ZList.prj_z(new_history); /*shift to the element with elt_id*/ - switch (ZList.shift_to(elt_id, cur_group.state_list)) { + switch ( + ZList.shift_to( + ZList.length(cur_group.state_list) - elt_id, + cur_group.state_list, + ) + ) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_state_list) => let new_cardstacks_state = @@ -331,7 +341,12 @@ let apply_action = | ToggleHistoryGroup(gp_id) => let cur_group = ZList.prj_z(model.undo_history); /*shift to toggle group and change expanded state*/ - switch (ZList.shift_to(gp_id, model.undo_history)) { + switch ( + ZList.shift_to( + ZList.length(model.undo_history) - gp_id, + model.undo_history, + ) + ) { | None => failwith("Impossible because undo_history is non-empty") | Some(history) => let toggle_target_group = ZList.prj_z(history); @@ -344,7 +359,12 @@ let apply_action = }, ); /*shift back to the current group*/ - switch (ZList.shift_to(cur_group.group_id, after_toggle)) { + switch ( + ZList.shift_to( + ZList.length(model.undo_history) - cur_group.group_id, + after_toggle, + ) + ) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => {...model, undo_history: new_history} }; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index facb576130..9488039e8f 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -93,11 +93,11 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let group_view = (~is_cur_group: bool, group: undo_history_group) => { /* reverse the undo_history, so the first entry shown in panel is the latest history entry */ - let rev_state_list = ( - List.rev(ZList.prj_suffix(group.state_list)), - ZList.prj_z(group.state_list), - List.rev(ZList.prj_prefix(group.state_list)), - ); + /* let rev_state_list = ( + List.rev(ZList.prj_suffix(group.state_list)), + ZList.prj_z(group.state_list), + List.rev(ZList.prj_prefix(group.state_list)), + ); */ /* if the group containning selected history entry, it should be splited to different css styles */ let suc_his_classes = if (is_cur_group) { @@ -117,7 +117,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else { []; }; - switch (rev_state_list) { + switch (group.state_list) { | ([], cur_state, prev_states) => switch (cur_state.previous_action) { | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ @@ -308,9 +308,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Node.div( [Attr.classes(["the-history"])], [ - suc_history_view(List.rev(suc_groups)), + prev_history_view(prev_groups), cur_history_view(cur_group), - prev_history_view(List.rev(prev_groups)), + suc_history_view(suc_groups), ], ) ); From f6906c018f5c91390ed96194eb470dbcdb0aac1f Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 17 Jan 2020 12:47:14 -0500 Subject: [PATCH 30/60] fix order but have bugs in clicking --- src/hazelweb/gui/UndoHistoryList.re | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 9488039e8f..643884c876 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -193,7 +193,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; } - | ([title_entry, ...suc_groups], cur_state, prev_states) => + | ([title_entry, ...suc_states], cur_state, prev_states) => if (group.is_expanded) { Vdom.( Node.div( @@ -225,7 +225,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], List.map( history_hidden_entry_view(group.group_id), - suc_groups, + suc_states, ), ) ), @@ -302,15 +302,15 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.(Node.div([], [group_view(~is_cur_group=true, history)])); }; let history_view = (model: Model.t) => { - let (prev_groups, cur_group, suc_groups) = model.undo_history; + let (suc_groups, cur_group, prev_groups) = model.undo_history; let display_content = Vdom.( Node.div( [Attr.classes(["the-history"])], [ - prev_history_view(prev_groups), - cur_history_view(cur_group), suc_history_view(suc_groups), + cur_history_view(cur_group), + prev_history_view(prev_groups), ], ) ); From 81631d470634acf6563f083133542c6f8337bbe1 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 17 Jan 2020 13:01:38 -0500 Subject: [PATCH 31/60] fix clicking --- src/hazelweb/Update.re | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index a1fac3decf..ad631fdff2 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -308,7 +308,7 @@ let apply_action = /*shift to the group with group_id = gp_id*/ switch ( ZList.shift_to( - ZList.length(model.undo_history) - gp_id, + ZList.length(model.undo_history) - gp_id - 1, model.undo_history, ) ) { @@ -318,7 +318,7 @@ let apply_action = /*shift to the element with elt_id*/ switch ( ZList.shift_to( - ZList.length(cur_group.state_list) - elt_id, + ZList.length(cur_group.state_list) - elt_id - 1, cur_group.state_list, ) ) { @@ -343,7 +343,7 @@ let apply_action = /*shift to toggle group and change expanded state*/ switch ( ZList.shift_to( - ZList.length(model.undo_history) - gp_id, + ZList.length(model.undo_history) - gp_id - 1, model.undo_history, ) ) { @@ -361,7 +361,7 @@ let apply_action = /*shift back to the current group*/ switch ( ZList.shift_to( - ZList.length(model.undo_history) - cur_group.group_id, + ZList.length(model.undo_history) - cur_group.group_id - 1, after_toggle, ) ) { From 0aa3be064decb8b2b314067f0915fd8dd27a197b Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 17 Jan 2020 13:16:52 -0500 Subject: [PATCH 32/60] rename state_list to group_entries --- src/hazelweb/Model.re | 40 ++++++++++++++--------------- src/hazelweb/Update.re | 14 +++++----- src/hazelweb/gui/UndoHistoryList.re | 10 ++------ 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 246d570362..3c67a366fa 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -86,18 +86,18 @@ type result_state = | ResultsDisabled | Result(has_result_state); -/*new edit state, the previous action, id*/ type undo_history_entry = { cardstacks_state, previous_action: option(Action.t), elt_id: int, }; -/* group edit action, group id */ + type undo_history_group = { - state_list: ZList.t(undo_history_entry, undo_history_entry), + group_entries: ZList.t(undo_history_entry, undo_history_entry), group_id: int, is_expanded: bool, }; + type undo_history = ZList.t(undo_history_group, undo_history_group); type t = { @@ -121,25 +121,25 @@ type t = { let push_edit_state = (undo_history, cardstacks_state, action: option(Action.t)): undo_history => { let cur_group = ZList.prj_z(undo_history); - let cur_state = ZList.prj_z(cur_group.state_list); + let cur_state = ZList.prj_z(cur_group.group_entries); if (Action.in_same_history_group(action, cur_state.previous_action)) { let new_state = { cardstacks_state, previous_action: action, elt_id: cur_state.elt_id + 1, }; - let state_list_after_push = ( + let group_entries_after_push = ( [], new_state, [ - ZList.prj_z(cur_group.state_list), - ...ZList.prj_suffix(cur_group.state_list), + ZList.prj_z(cur_group.group_entries), + ...ZList.prj_suffix(cur_group.group_entries), ], ); ( [], { - state_list: state_list_after_push, + group_entries: group_entries_after_push, group_id: cur_group.group_id, is_expanded: false, }, /* initial state of group should be folded*/ @@ -147,7 +147,7 @@ let push_edit_state = ); } else { let new_group = { - state_list: ( + group_entries: ( [], {cardstacks_state, previous_action: action, elt_id: 0}, [], @@ -448,7 +448,7 @@ let init = (): t => { undo_history: ( [], { - state_list: ([], undo_history_state, []), + group_entries: ([], undo_history_state, []), group_id: 0, is_expanded: false, }, @@ -560,7 +560,7 @@ let undo = (model: t): t => { let new_history = { let cur_group = ZList.prj_z(model.undo_history); /* shift to previous state in the same group */ - switch (ZList.shift_next(cur_group.state_list)) { + switch (ZList.shift_next(cur_group.group_entries)) { | None => /*if current group doesn't have previous state, shfit to previous group*/ switch (ZList.shift_next(model.undo_history)) { @@ -569,16 +569,16 @@ let undo = (model: t): t => { let new_group = ZList.prj_z(new_history); let new_group' = { ...new_group, - state_list: ZList.shift_begin(new_group.state_list), /*pointer may be in the wrong position after clicking history panel*/ + group_entries: ZList.shift_begin(new_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ is_expanded: true, }; /* is_expanded=true because this group should be expanded*/ ZList.replace_z(new_history, new_group'); } - | Some(new_state_list) => + | Some(new_group_entries) => ZList.replace_z( model.undo_history, { - state_list: new_state_list, + group_entries: new_group_entries, group_id: cur_group.group_id, is_expanded: true, }, @@ -587,7 +587,7 @@ let undo = (model: t): t => { }; let cur_group' = ZList.prj_z(new_history); let new_cardstacks_state = - ZList.prj_z(cur_group'.state_list).cardstacks_state; + ZList.prj_z(cur_group'.group_entries).cardstacks_state; let model' = update_cardstacks_state(model, new_cardstacks_state); {...model', undo_history: new_history}; }; @@ -596,7 +596,7 @@ let redo = (model: t): t => { let new_history = { let cur_group = ZList.prj_z(model.undo_history); /* shift to previous state in the same group */ - switch (ZList.shift_prev(cur_group.state_list)) { + switch (ZList.shift_prev(cur_group.group_entries)) { | None => /*if current group doesn't have previous state, shfit to previous group*/ switch (ZList.shift_prev(model.undo_history)) { @@ -604,17 +604,17 @@ let redo = (model: t): t => { | Some(new_history) => let cur_group = ZList.prj_z(new_history); let new_group = { - state_list: ZList.shift_end(cur_group.state_list), /*pointer may be in the wrong position after clicking history panel*/ + group_entries: ZList.shift_end(cur_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ group_id: cur_group.group_id, is_expanded: true, }; /* is_expanded=true because this group should be expanded when redo*/ ZList.replace_z(new_history, new_group); } - | Some(new_state_list) => + | Some(new_group_entries) => ZList.replace_z( model.undo_history, { - state_list: new_state_list, + group_entries: new_group_entries, group_id: cur_group.group_id, is_expanded: true, }, @@ -623,7 +623,7 @@ let redo = (model: t): t => { }; let cur_group' = ZList.prj_z(new_history); let new_cardstacks_state = - ZList.prj_z(cur_group'.state_list).cardstacks_state; + ZList.prj_z(cur_group'.group_entries).cardstacks_state; let model' = update_cardstacks_state(model, new_cardstacks_state); {...model', undo_history: new_history}; }; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index ad631fdff2..d063c97b5e 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -305,7 +305,7 @@ let apply_action = | Undo => Model.undo(model) | Redo => Model.redo(model) | ShiftHistory(gp_id, elt_id) => - /*shift to the group with group_id = gp_id*/ + /*shift to the group with group_id = gp_id, since undo_history append the latest entry to suffix rather than prefix, so shift_to ZList.length(model.undo_history) - gp_id - 1*/ switch ( ZList.shift_to( ZList.length(model.undo_history) - gp_id - 1, @@ -318,14 +318,14 @@ let apply_action = /*shift to the element with elt_id*/ switch ( ZList.shift_to( - ZList.length(cur_group.state_list) - elt_id - 1, - cur_group.state_list, + ZList.length(cur_group.group_entries) - elt_id - 1, + cur_group.group_entries, ) ) { - | None => failwith("Impossible because undo_history is non-empty") - | Some(new_state_list) => + | None => failwith("Impossible because group_entries is non-empty") + | Some(new_group_entries) => let new_cardstacks_state = - ZList.prj_z(new_state_list).cardstacks_state; + ZList.prj_z(new_group_entries).cardstacks_state; let new_model = Model.update_cardstacks_state(model, new_cardstacks_state); { @@ -333,7 +333,7 @@ let apply_action = undo_history: ZList.replace_z( new_history, - {...cur_group, state_list: new_state_list}, + {...cur_group, group_entries: new_group_entries}, ), }; }; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 643884c876..916bc837d6 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -92,12 +92,6 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; let group_view = (~is_cur_group: bool, group: undo_history_group) => { - /* reverse the undo_history, so the first entry shown in panel is the latest history entry */ - /* let rev_state_list = ( - List.rev(ZList.prj_suffix(group.state_list)), - ZList.prj_z(group.state_list), - List.rev(ZList.prj_prefix(group.state_list)), - ); */ /* if the group containning selected history entry, it should be splited to different css styles */ let suc_his_classes = if (is_cur_group) { @@ -117,7 +111,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else { []; }; - switch (group.state_list) { + switch (group.group_entries) { | ([], cur_state, prev_states) => switch (cur_state.previous_action) { | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ @@ -314,7 +308,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], ) ); - let action = ZList.prj_z(cur_group.state_list).previous_action; + let action = ZList.prj_z(cur_group.group_entries).previous_action; switch (action) { | None => /*if init state is only history entry */ From c3d33e9b202301ec9f8f7d46c44730955c0499ef Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 18 Jan 2020 23:31:31 -0500 Subject: [PATCH 33/60] reverse store order --- src/hazelweb/Update.re | 2 +- src/hazelweb/gui/Page.re | 22 ---------------------- src/hazelweb/gui/UndoHistoryList.re | 7 ++++--- src/hazelweb/www/style.css | 2 +- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index d063c97b5e..d9b45bed9e 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -384,4 +384,4 @@ let apply_action = }; } }; -}; +}; \ No newline at end of file diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index a10fff6900..d1152846c2 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -84,29 +84,7 @@ let next_card_button = (~inject, model: Model.t) => { ) ); }; -/* let undo_button = (~inject) => { - Vdom.( - Node.button( - [ - Attr.id("undo-button"), - Attr.on_click(_ => inject(Update.Action.Undo)), - ], - [Node.text("Undo")], - ) - ); - }; - let redo_button = (~inject) => { - Vdom.( - Node.button( - [ - Attr.id("redo-button"), - Attr.on_click(_ => inject(Update.Action.Redo)), - ], - [Node.text("Redo")], - ) - ); - }; */ let cardstack_controls = (~inject, model: Model.t) => Vdom.( Node.div( diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 916bc837d6..791591bcdc 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -35,13 +35,13 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { group_id: int, undo_history_entry: undo_history_entry, ) => { - let icon_classes = + let history_tab_icon = (group_id: int) => { + let icon_classes = if (is_expanded) { ["down-triangle", "history-tab-icon"]; } else { ["left-triangle", "history-tab-icon"]; }; - let history_tab_icon = (group_id: int) => if (has_hidden_part) { /* expand icon*/ Vdom.( @@ -58,6 +58,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else { Vdom.(Node.div([], [])); }; + } switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) | Some(detail_ac) => @@ -70,7 +71,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Node.span( [ - Attr.classes(["the-history-title-content"]), + Attr.classes(["the-history-title-txt"]), Attr.on_click(_ => inject( Update.Action.ShiftHistory( diff --git a/src/hazelweb/www/style.css b/src/hazelweb/www/style.css index 59c05e9432..aa0b33e971 100644 --- a/src/hazelweb/www/style.css +++ b/src/hazelweb/www/style.css @@ -1232,7 +1232,7 @@ button:disabled { float: left; width: 100%; } -.the-history-title-content { +.the-history-title-txt { cursor: pointer; float: left; } From ff0da5996078f1e02dc25377fa0b07968ce45b9a Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 20 Jan 2020 20:54:09 -0500 Subject: [PATCH 34/60] make cardstacks into seperate file --- src/hazelweb/Model.re | 132 +++----------------------- src/hazelweb/UndoHistory.re | 78 +++++++++++++++ src/hazelweb/Update.re | 8 +- src/hazelweb/cardstacks/CardStacks.re | 49 ++++++++++ src/hazelweb/gui/UndoHistoryList.re | 18 ++-- 5 files changed, 152 insertions(+), 133 deletions(-) create mode 100644 src/hazelweb/cardstacks/CardStacks.re diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 3c67a366fa..68dc269013 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -3,57 +3,16 @@ open GeneralUtil; module ZList = GeneralUtil.ZList; -type cardstacks = list(CardStack.t); -let cardstacks: cardstacks = [ - TutorialCards.cardstack, - RCStudyCards.cardstack, -]; - let init_compute_results = true; type user_newlines = CursorPath.StepsMap.t(unit); type edit_state = Statics.edit_state; -type card_state = { - card: Card.t, - edit_state, -}; - -type cardstack_state = { - cardstack: CardStack.t, - zcards: ZList.t(card_state, card_state), -}; - -type cardstacks_state = ZList.t(cardstack_state, cardstack_state); +type cardstacks_state = CardStacks.cardstacks_state; +type cardstacks = CardStacks.cardstacks; -let mk_cardstack_state = (cardstack: CardStack.t) => { - let card_states = - List.map( - card => - { - card, - edit_state: - card.init_zblock - |> Statics.fix_and_renumber_holes_z(Contexts.empty), - }, - cardstack.cards, - ); - let zcards = - GeneralUtil.Opt.get( - _ => failwith("no cards"), - ZList.split_at(0, card_states), - ); - {cardstack, zcards}; -}; - -let mk_cardstacks_state = cardstacks => { - let cardstack_states = List.map(mk_cardstack_state, cardstacks); - GeneralUtil.Opt.get( - _ => failwith("no cardstacks"), - ZList.split_at(0, cardstack_states), - ); -}; +type undo_history = UndoHistory.t; [@deriving sexp] type result = ( @@ -86,20 +45,6 @@ type result_state = | ResultsDisabled | Result(has_result_state); -type undo_history_entry = { - cardstacks_state, - previous_action: option(Action.t), - elt_id: int, -}; - -type undo_history_group = { - group_entries: ZList.t(undo_history_entry, undo_history_entry), - group_id: int, - is_expanded: bool, -}; - -type undo_history = ZList.t(undo_history_group, undo_history_group); - type t = { cardstacks, cardstacks_state /* these are derived from the cardstack state: */, @@ -118,51 +63,6 @@ type t = { undo_history, }; -let push_edit_state = - (undo_history, cardstacks_state, action: option(Action.t)): undo_history => { - let cur_group = ZList.prj_z(undo_history); - let cur_state = ZList.prj_z(cur_group.group_entries); - if (Action.in_same_history_group(action, cur_state.previous_action)) { - let new_state = { - cardstacks_state, - previous_action: action, - elt_id: cur_state.elt_id + 1, - }; - let group_entries_after_push = ( - [], - new_state, - [ - ZList.prj_z(cur_group.group_entries), - ...ZList.prj_suffix(cur_group.group_entries), - ], - ); - ( - [], - { - group_entries: group_entries_after_push, - group_id: cur_group.group_id, - is_expanded: false, - }, /* initial state of group should be folded*/ - ZList.prj_suffix(undo_history), - ); - } else { - let new_group = { - group_entries: ( - [], - {cardstacks_state, previous_action: action, elt_id: 0}, - [], - ), - group_id: cur_group.group_id + 1, - is_expanded: false, - }; - ( - [], - new_group, - [ZList.prj_z(undo_history), ...ZList.prj_suffix(undo_history)], - ); - }; -}; - let cardstack_state_of = model => ZList.prj_z(model.cardstacks_state); let edit_state_of = model => @@ -358,7 +258,8 @@ let update_edit_state = ((new_zblock, ty, u_gen): edit_state, model: t): t => { }; }; -let update_cardstack_state = (model, cardstack_state) => { +let update_cardstack_state = + (model, cardstack_state: CardStacks.cardstack_state) => { let edit_state = ZList.prj_z(cardstack_state.zcards).edit_state; let result_state = result_state_of_edit_state(edit_state, model.compute_results); @@ -427,17 +328,18 @@ let next_card = model => { }; let init = (): t => { - let cardstacks_state = mk_cardstacks_state(cardstacks); + let cardstacks_state = + CardStacks.mk_cardstacks_state(CardStacks.cardstacks); let edit_state = ZList.prj_z(ZList.prj_z(cardstacks_state).zcards).edit_state; - let undo_history_state = { + let undo_history_state: UndoHistory.undo_history_entry = { cardstacks_state, previous_action: None, elt_id: 0, }; let compute_results = init_compute_results; { - cardstacks, + cardstacks: CardStacks.cardstacks, cardstacks_state, cursor_info: cursor_info_of_edit_state(edit_state), compute_results, @@ -481,7 +383,7 @@ let perform_edit_action = (model: t, a: Action.t): t => { let new_model = model |> update_edit_state(new_edit_state); let new_history = if (UndoHistory.undoable_action(a)) { - push_edit_state( + UndoHistory.push_edit_state( model.undo_history, new_model.cardstacks_state, Some(a), @@ -603,7 +505,7 @@ let redo = (model: t): t => { | None => model.undo_history | Some(new_history) => let cur_group = ZList.prj_z(new_history); - let new_group = { + let new_group: UndoHistory.undo_history_group = { group_entries: ZList.shift_end(cur_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ group_id: cur_group.group_id, is_expanded: true, @@ -627,15 +529,3 @@ let redo = (model: t): t => { let model' = update_cardstacks_state(model, new_cardstacks_state); {...model', undo_history: new_history}; }; - -let set_all_hidden_history = (undo_history, expanded: bool): undo_history => { - let hidden_group = (group: undo_history_group) => { - ...group, - is_expanded: expanded, - }; - ( - List.map(hidden_group, ZList.prj_prefix(undo_history)), - hidden_group(ZList.prj_z(undo_history)), - List.map(hidden_group, ZList.prj_suffix(undo_history)), - ); -}; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 0e8a1e34bc..1b46362e3f 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -1,3 +1,19 @@ +module ZList = GeneralUtil.ZList; + +type undo_history_entry = { + cardstacks_state: CardStacks.cardstacks_state, + previous_action: option(Action.t), + elt_id: int, +}; + +type undo_history_group = { + group_entries: ZList.t(undo_history_entry, undo_history_entry), + group_id: int, + is_expanded: bool, +}; + +type t = ZList.t(undo_history_group, undo_history_group); + let undoable_action = (action: Action.t): bool => { switch (action) { | UpdateApPalette(_) @@ -16,3 +32,65 @@ let undoable_action = (action: Action.t): bool => { | ShiftDown => false }; }; + +let push_edit_state = + ( + undo_history: t, + cardstacks_state: CardStacks.cardstacks_state, + action: option(Action.t), + ) + : t => { + let cur_group = ZList.prj_z(undo_history); + let cur_state = ZList.prj_z(cur_group.group_entries); + if (Action.in_same_history_group(action, cur_state.previous_action)) { + let new_state = { + cardstacks_state, + previous_action: action, + elt_id: cur_state.elt_id + 1, + }; + let group_entries_after_push = ( + [], + new_state, + [ + ZList.prj_z(cur_group.group_entries), + ...ZList.prj_suffix(cur_group.group_entries), + ], + ); + ( + [], + { + group_entries: group_entries_after_push, + group_id: cur_group.group_id, + is_expanded: false, + }, /* initial state of group should be folded*/ + ZList.prj_suffix(undo_history), + ); + } else { + let new_group = { + group_entries: ( + [], + {cardstacks_state, previous_action: action, elt_id: 0}, + [], + ), + group_id: cur_group.group_id + 1, + is_expanded: false, + }; + ( + [], + new_group, + [ZList.prj_z(undo_history), ...ZList.prj_suffix(undo_history)], + ); + }; +}; + +let set_all_hidden_history = (undo_history: t, expanded: bool): t => { + let hidden_group = (group: undo_history_group) => { + ...group, + is_expanded: expanded, + }; + ( + List.map(hidden_group, ZList.prj_prefix(undo_history)), + hidden_group(ZList.prj_z(undo_history)), + List.map(hidden_group, ZList.prj_suffix(undo_history)), + ); +}; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index d9b45bed9e..0b7d633eae 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -374,14 +374,16 @@ let apply_action = { ...model, all_hidden_history_expand: false, - undo_history: Model.set_all_hidden_history(model.undo_history, false), + undo_history: + UndoHistory.set_all_hidden_history(model.undo_history, false), }; } else { { ...model, all_hidden_history_expand: true, - undo_history: Model.set_all_hidden_history(model.undo_history, true), + undo_history: + UndoHistory.set_all_hidden_history(model.undo_history, true), }; } }; -}; \ No newline at end of file +}; diff --git a/src/hazelweb/cardstacks/CardStacks.re b/src/hazelweb/cardstacks/CardStacks.re new file mode 100644 index 0000000000..dabf96a2f3 --- /dev/null +++ b/src/hazelweb/cardstacks/CardStacks.re @@ -0,0 +1,49 @@ +module ZList = GeneralUtil.ZList; + +type cardstacks = list(CardStack.t); +let cardstacks: cardstacks = [ + TutorialCards.cardstack, + RCStudyCards.cardstack, +]; + +type edit_state = Statics.edit_state; + +type card_state = { + card: Card.t, + edit_state, +}; + +type cardstack_state = { + cardstack: CardStack.t, + zcards: ZList.t(card_state, card_state), +}; + +type cardstacks_state = ZList.t(cardstack_state, cardstack_state); + +let mk_cardstack_state = (cardstack: CardStack.t) => { + let card_states = + List.map( + card => + { + card, + edit_state: + card.init_zblock + |> Statics.fix_and_renumber_holes_z(Contexts.empty), + }, + cardstack.cards, + ); + let zcards = + GeneralUtil.Opt.get( + _ => failwith("no cards"), + ZList.split_at(0, card_states), + ); + {cardstack, zcards}; +}; + +let mk_cardstacks_state = cardstacks => { + let cardstack_states = List.map(mk_cardstack_state, cardstacks); + GeneralUtil.Opt.get( + _ => failwith("no cardstacks"), + ZList.split_at(0, cardstack_states), + ); +}; diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryList.re index 791591bcdc..f2611d073e 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryList.re @@ -1,7 +1,7 @@ module Vdom = Virtual_dom.Vdom; module ZList = GeneralUtil.ZList; -type undo_history_group = Model.undo_history_group; -type undo_history_entry = Model.undo_history_entry; +type undo_history_group = UndoHistory.undo_history_group; +type undo_history_entry = UndoHistory.undo_history_entry; let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let history_hidden_entry_view = @@ -35,13 +35,13 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { group_id: int, undo_history_entry: undo_history_entry, ) => { - let history_tab_icon = (group_id: int) => { + let history_tab_icon = (group_id: int) => { let icon_classes = - if (is_expanded) { - ["down-triangle", "history-tab-icon"]; - } else { - ["left-triangle", "history-tab-icon"]; - }; + if (is_expanded) { + ["down-triangle", "history-tab-icon"]; + } else { + ["left-triangle", "history-tab-icon"]; + }; if (has_hidden_part) { /* expand icon*/ Vdom.( @@ -58,7 +58,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else { Vdom.(Node.div([], [])); }; - } + }; switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) | Some(detail_ac) => From dab07cf032d5582685b6be8523517f7f6d8a1050 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 14:37:17 -0500 Subject: [PATCH 35/60] generate id when generate visualization --- src/hazelweb/Model.re | 4 +- src/hazelweb/Update.re | 37 ++----- src/hazelweb/gui/Page.re | 2 +- ...UndoHistoryList.re => UndoHistoryPanel.re} | 102 ++++++++++++------ 4 files changed, 80 insertions(+), 65 deletions(-) rename src/hazelweb/gui/{UndoHistoryList.re => UndoHistoryPanel.re} (79%) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 68dc269013..c5e9b1f6fc 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -473,7 +473,7 @@ let undo = (model: t): t => { ...new_group, group_entries: ZList.shift_begin(new_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ is_expanded: true, - }; /* is_expanded=true because this group should be expanded*/ + }; /* is_expanded=true because the selected group should be expanded*/ ZList.replace_z(new_history, new_group'); } | Some(new_group_entries) => @@ -484,7 +484,7 @@ let undo = (model: t): t => { group_id: cur_group.group_id, is_expanded: true, }, - ) /* is_expanded=true because this group should be expanded*/ + ) /* is_expanded=true because the selected group should be expanded*/ }; }; let cur_group' = ZList.prj_z(new_history); diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 0b7d633eae..11dea62eec 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -304,24 +304,14 @@ let apply_action = model; | Undo => Model.undo(model) | Redo => Model.redo(model) - | ShiftHistory(gp_id, elt_id) => - /*shift to the group with group_id = gp_id, since undo_history append the latest entry to suffix rather than prefix, so shift_to ZList.length(model.undo_history) - gp_id - 1*/ - switch ( - ZList.shift_to( - ZList.length(model.undo_history) - gp_id - 1, - model.undo_history, - ) - ) { + | ShiftHistory(group_id, elt_id) => + /*shift to the group with group_id*/ + switch (ZList.shift_to(group_id, model.undo_history)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => let cur_group = ZList.prj_z(new_history); /*shift to the element with elt_id*/ - switch ( - ZList.shift_to( - ZList.length(cur_group.group_entries) - elt_id - 1, - cur_group.group_entries, - ) - ) { + switch (ZList.shift_to(elt_id, cur_group.group_entries)) { | None => failwith("Impossible because group_entries is non-empty") | Some(new_group_entries) => let new_cardstacks_state = @@ -338,15 +328,11 @@ let apply_action = }; }; } - | ToggleHistoryGroup(gp_id) => - let cur_group = ZList.prj_z(model.undo_history); + | ToggleHistoryGroup(toggle_group_id) => + let (suc_group, _, _) = model.undo_history; + let cur_group_id = List.length(suc_group); /*shift to toggle group and change expanded state*/ - switch ( - ZList.shift_to( - ZList.length(model.undo_history) - gp_id - 1, - model.undo_history, - ) - ) { + switch (ZList.shift_to(toggle_group_id, model.undo_history)) { | None => failwith("Impossible because undo_history is non-empty") | Some(history) => let toggle_target_group = ZList.prj_z(history); @@ -359,12 +345,7 @@ let apply_action = }, ); /*shift back to the current group*/ - switch ( - ZList.shift_to( - ZList.length(model.undo_history) - cur_group.group_id - 1, - after_toggle, - ) - ) { + switch (ZList.shift_to(cur_group_id, after_toggle)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => {...model, undo_history: new_history} }; diff --git a/src/hazelweb/gui/Page.re b/src/hazelweb/gui/Page.re index d1152846c2..c3c2383cf8 100644 --- a/src/hazelweb/gui/Page.re +++ b/src/hazelweb/gui/Page.re @@ -267,7 +267,7 @@ let page_view = [ CursorInspector.view(~inject, model), ContextInspector.view(~inject, model), - UndoHistoryList.view(~inject, model), + UndoHistoryPanel.view(~inject, model), OptionsPanel.view(~inject, model), ], ), diff --git a/src/hazelweb/gui/UndoHistoryList.re b/src/hazelweb/gui/UndoHistoryPanel.re similarity index 79% rename from src/hazelweb/gui/UndoHistoryList.re rename to src/hazelweb/gui/UndoHistoryPanel.re index f2611d073e..5eb5fd0aad 100644 --- a/src/hazelweb/gui/UndoHistoryList.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -4,8 +4,22 @@ type undo_history_group = UndoHistory.undo_history_group; type undo_history_entry = UndoHistory.undo_history_entry; let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { + let rec incr_map_helper = (func_to_list, func_to_base, base, lst) => { + switch (lst) { + | [] => [] + | [head, ...tail] => [ + func_to_list(base, head), + ...incr_map_helper( + func_to_list, + func_to_base, + func_to_base(base), + tail, + ), + ] + }; + }; let history_hidden_entry_view = - (group_id: int, undo_history_entry: undo_history_entry) => { + (group_id: int, elt_id: int, undo_history_entry: undo_history_entry) => { switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ | Some(detail_ac) => @@ -14,12 +28,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["the-hidden-history-entry"]), Attr.on_click(_ => - inject( - Update.Action.ShiftHistory( - group_id, - undo_history_entry.elt_id, - ), - ) + inject(Update.Action.ShiftHistory(group_id, elt_id)) ), ], [Node.text(Action.action_to_display_string(detail_ac))], @@ -33,6 +42,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ~is_expanded: bool, ~has_hidden_part: bool, group_id: int, + elt_id: int, undo_history_entry: undo_history_entry, ) => { let history_tab_icon = (group_id: int) => { @@ -73,12 +83,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["the-history-title-txt"]), Attr.on_click(_ => - inject( - Update.Action.ShiftHistory( - group_id, - undo_history_entry.elt_id, - ), - ) + inject(Update.Action.ShiftHistory(group_id, elt_id)) ), ], [Node.text(Action.action_to_display_string(detail_ac))], @@ -92,7 +97,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let group_view = (~is_cur_group: bool, group: undo_history_group) => { + let group_view = + (~is_cur_group: bool, group_id: int, group: undo_history_group) => { /* if the group containning selected history entry, it should be splited to different css styles */ let suc_his_classes = if (is_cur_group) { @@ -138,7 +144,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { history_title_entry_view( ~is_expanded=group.is_expanded, ~has_hidden_part, - group.group_id, + group_id, + 0, /*elt_id*/ cur_state, ), ], @@ -151,8 +158,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ["hidden-history-entry"] @ prev_his_classes, ), ], - List.map( - history_hidden_entry_view(group.group_id), + incr_map_helper( + history_hidden_entry_view(group_id), + base => base + 1, + 1, prev_states, ), ) @@ -176,7 +185,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { history_title_entry_view( ~is_expanded=group.is_expanded, ~has_hidden_part, - group.group_id, + group_id, + 0, /*elt_id*/ cur_state, ), ], @@ -206,7 +216,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { history_title_entry_view( ~is_expanded=group.is_expanded, ~has_hidden_part=true, - group.group_id, + group_id, + 0, /*elt_id*/ title_entry, ), ], @@ -218,8 +229,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ suc_his_classes), ], - List.map( - history_hidden_entry_view(group.group_id), + incr_map_helper( + history_hidden_entry_view(group_id), + base => base + 1, + 1, suc_states, ), ) @@ -230,7 +243,13 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ cur_his_classes), ], - [history_hidden_entry_view(group.group_id, cur_state)], + [ + history_hidden_entry_view( + group_id, + List.length(suc_states) + 1, + cur_state, + ), + ] /*elt_id= List.length(suc_states)+1*/ ) ), /* the previous history entry */ @@ -239,8 +258,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ prev_his_classes), ], - List.map( - history_hidden_entry_view(group.group_id), + incr_map_helper( + history_hidden_entry_view(group_id), + base => base + 1, + List.length(suc_states) + 2, prev_states, ), ) @@ -264,7 +285,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { history_title_entry_view( ~is_expanded=group.is_expanded, ~has_hidden_part=true, - group.group_id, + group_id, + 0, /*elt_id*/ title_entry, ), ], @@ -277,24 +299,36 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let prev_history_view = history => { + let prev_history_view = (history, group_id_base: int) => { Vdom.( Node.div( [Attr.classes(["the-prev-history"])], - List.map(group_view(~is_cur_group=false), history), + incr_map_helper( + group_view(~is_cur_group=false), + base => base + 1, + group_id_base, + history, + ), ) ); }; - let suc_history_view = history => { + let suc_history_view = (history, group_id_base: int) => { Vdom.( Node.div( [Attr.classes(["the-suc-history"])], - List.map(group_view(~is_cur_group=false), history), + incr_map_helper( + group_view(~is_cur_group=false), + base => base + 1, + group_id_base, + history, + ), ) ); }; - let cur_history_view = (history: undo_history_group) => { - Vdom.(Node.div([], [group_view(~is_cur_group=true, history)])); + let cur_history_view = (history: undo_history_group, group_id_base: int) => { + Vdom.( + Node.div([], [group_view(~is_cur_group=true, group_id_base, history)]) + ); }; let history_view = (model: Model.t) => { let (suc_groups, cur_group, prev_groups) = model.undo_history; @@ -303,9 +337,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Node.div( [Attr.classes(["the-history"])], [ - suc_history_view(suc_groups), - cur_history_view(cur_group), - prev_history_view(prev_groups), + suc_history_view(suc_groups, 0), + cur_history_view(cur_group, List.length(suc_groups)), + prev_history_view(prev_groups, List.length(suc_groups) + 1), ], ) ); From 7dbb4d9ccab3e29c516603d816b3ced82d8d9e65 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 14:42:51 -0500 Subject: [PATCH 36/60] delete id in undo_history --- src/hazelweb/Model.re | 23 ++++------------------- src/hazelweb/UndoHistory.re | 21 +++------------------ 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index c5e9b1f6fc..947b1b5b50 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -335,7 +335,6 @@ let init = (): t => { let undo_history_state: UndoHistory.undo_history_entry = { cardstacks_state, previous_action: None, - elt_id: 0, }; let compute_results = init_compute_results; { @@ -349,11 +348,7 @@ let init = (): t => { is_cell_focused: false, undo_history: ( [], - { - group_entries: ([], undo_history_state, []), - group_id: 0, - is_expanded: false, - }, + {group_entries: ([], undo_history_state, []), is_expanded: false}, [], ), left_sidebar_open: false, @@ -469,8 +464,7 @@ let undo = (model: t): t => { | None => model.undo_history | Some(new_history) => let new_group = ZList.prj_z(new_history); - let new_group' = { - ...new_group, + let new_group': UndoHistory.undo_history_group = { group_entries: ZList.shift_begin(new_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ is_expanded: true, }; /* is_expanded=true because the selected group should be expanded*/ @@ -479,11 +473,7 @@ let undo = (model: t): t => { | Some(new_group_entries) => ZList.replace_z( model.undo_history, - { - group_entries: new_group_entries, - group_id: cur_group.group_id, - is_expanded: true, - }, + {group_entries: new_group_entries, is_expanded: true}, ) /* is_expanded=true because the selected group should be expanded*/ }; }; @@ -507,7 +497,6 @@ let redo = (model: t): t => { let cur_group = ZList.prj_z(new_history); let new_group: UndoHistory.undo_history_group = { group_entries: ZList.shift_end(cur_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ - group_id: cur_group.group_id, is_expanded: true, }; /* is_expanded=true because this group should be expanded when redo*/ ZList.replace_z(new_history, new_group); @@ -515,11 +504,7 @@ let redo = (model: t): t => { | Some(new_group_entries) => ZList.replace_z( model.undo_history, - { - group_entries: new_group_entries, - group_id: cur_group.group_id, - is_expanded: true, - }, + {group_entries: new_group_entries, is_expanded: true}, ) }; }; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 1b46362e3f..3ecd2b833d 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -3,12 +3,10 @@ module ZList = GeneralUtil.ZList; type undo_history_entry = { cardstacks_state: CardStacks.cardstacks_state, previous_action: option(Action.t), - elt_id: int, }; type undo_history_group = { group_entries: ZList.t(undo_history_entry, undo_history_entry), - group_id: int, is_expanded: bool, }; @@ -43,11 +41,7 @@ let push_edit_state = let cur_group = ZList.prj_z(undo_history); let cur_state = ZList.prj_z(cur_group.group_entries); if (Action.in_same_history_group(action, cur_state.previous_action)) { - let new_state = { - cardstacks_state, - previous_action: action, - elt_id: cur_state.elt_id + 1, - }; + let new_state = {cardstacks_state, previous_action: action}; let group_entries_after_push = ( [], new_state, @@ -58,21 +52,12 @@ let push_edit_state = ); ( [], - { - group_entries: group_entries_after_push, - group_id: cur_group.group_id, - is_expanded: false, - }, /* initial state of group should be folded*/ + {group_entries: group_entries_after_push, is_expanded: false}, /* initial state of group should be folded*/ ZList.prj_suffix(undo_history), ); } else { let new_group = { - group_entries: ( - [], - {cardstacks_state, previous_action: action, elt_id: 0}, - [], - ), - group_id: cur_group.group_id + 1, + group_entries: ([], {cardstacks_state, previous_action: action}, []), is_expanded: false, }; ( From 5a210e20643c17e26e4a2a9385f84c2f1706f9d8 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 15:51:33 -0500 Subject: [PATCH 37/60] add some comment --- src/hazelweb/JSUtil.re | 6 ++++ src/hazelweb/UndoHistory.re | 8 ++--- src/hazelweb/Update.re | 2 ++ src/hazelweb/gui/UndoHistoryPanel.re | 54 ++++++++++++++++------------ 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/hazelweb/JSUtil.re b/src/hazelweb/JSUtil.re index 594fe53556..6be9670090 100644 --- a/src/hazelweb/JSUtil.re +++ b/src/hazelweb/JSUtil.re @@ -145,6 +145,12 @@ let set_caret = (anchorNode, offset) => { selection##removeAllRanges; selection##addRange(range); }; +/* This comment part tries to make scrollbar follow the selected entry in history panel + However, "container##scrollTop=scroll_to##offsetTop;" will report an error + though it works in Js + I leave it here and wonder if Cyrus has any idea. + ----Zoe + */ /* let scroll_anchor = (container_id, anchor_id) => { let container = Dom_html.document##getElementById(Js.string(container_id)); let scroll_to = Dom_html.document##getElementById(Js.string(anchor_id)); diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 3ecd2b833d..3df3dc5841 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -39,12 +39,12 @@ let push_edit_state = ) : t => { let cur_group = ZList.prj_z(undo_history); - let cur_state = ZList.prj_z(cur_group.group_entries); - if (Action.in_same_history_group(action, cur_state.previous_action)) { - let new_state = {cardstacks_state, previous_action: action}; + let cur_entry = ZList.prj_z(cur_group.group_entries); + if (Action.in_same_history_group(action, cur_entry.previous_action)) { + let new_entry = {cardstacks_state, previous_action: action}; let group_entries_after_push = ( [], - new_state, + new_entry, [ ZList.prj_z(cur_group.group_entries), ...ZList.prj_suffix(cur_group.group_entries), diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 11dea62eec..4a27e18c2f 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -305,7 +305,9 @@ let apply_action = | Undo => Model.undo(model) | Redo => Model.redo(model) | ShiftHistory(group_id, elt_id) => + /* click history panel to shift to the certain history entry */ /*shift to the group with group_id*/ + switch (ZList.shift_to(group_id, model.undo_history)) { | None => failwith("Impossible because undo_history is non-empty") | Some(new_history) => diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 5eb5fd0aad..4741e1377a 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -4,6 +4,7 @@ type undo_history_group = UndoHistory.undo_history_group; type undo_history_entry = UndoHistory.undo_history_entry; let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { + /* a helper function working as an enhanced version of List.map() */ let rec incr_map_helper = (func_to_list, func_to_base, base, lst) => { switch (lst) { | [] => [] @@ -21,7 +22,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let history_hidden_entry_view = (group_id: int, elt_id: int, undo_history_entry: undo_history_entry) => { switch (undo_history_entry.previous_action) { - | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ + | None => Vdom.(Node.div([], [])) /* entry in initial state should not be displayed */ | Some(detail_ac) => Vdom.( Node.div( @@ -37,6 +38,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; + /* The entry which is always displayed*/ let history_title_entry_view = ( ~is_expanded: bool, @@ -66,11 +68,14 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ); } else { - Vdom.(Node.div([], [])); + /* no expand icon if there is no hidden part */ + Vdom.( + Node.div([], []) + ); }; }; switch (undo_history_entry.previous_action) { - | None => Vdom.(Node.div([], [])) + | None => Vdom.(Node.div([], [])) /* entry in the initial state should not be displayed */ | Some(detail_ac) => Vdom.( Node.div( @@ -119,12 +124,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { []; }; switch (group.group_entries) { - | ([], cur_state, prev_states) => - switch (cur_state.previous_action) { - | None => Vdom.(Node.div([], [])) /* init edit-state should not be displayed */ + | ([], cur_entry, prev_entries) => + switch (cur_entry.previous_action) { + | None => Vdom.(Node.div([], [])) /* the entry in intial state should not be displayed */ | Some(_) => let has_hidden_part = - switch (prev_states) { + switch (prev_entries) { | [] => false | _ => true }; @@ -133,6 +138,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Node.div( [Attr.classes(["the-history-group"])], [ + /* title entry */ Vdom.( Node.div( [ @@ -146,11 +152,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ~has_hidden_part, group_id, 0, /*elt_id*/ - cur_state, + cur_entry, ), ], ) ), + /* hidden entries */ Vdom.( Node.div( [ @@ -161,8 +168,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { incr_map_helper( history_hidden_entry_view(group_id), base => base + 1, - 1, - prev_states, + 1, /* base elt_id is 1, because there is a title entry with elt_id=0 ahead */ + prev_entries, ), ) ), @@ -170,6 +177,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ); } else { + /* if the group is not expanded, only title entry is displayed */ Vdom.( Node.div( [Attr.classes(["the-history-group"])], @@ -187,7 +195,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ~has_hidden_part, group_id, 0, /*elt_id*/ - cur_state, + cur_entry, ), ], ) @@ -198,7 +206,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; } - | ([title_entry, ...suc_states], cur_state, prev_states) => + | ([title_entry, ...suc_entries], cur_entry, prev_entries) => if (group.is_expanded) { Vdom.( Node.div( @@ -232,8 +240,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { incr_map_helper( history_hidden_entry_view(group_id), base => base + 1, - 1, - suc_states, + 1, /* base elt_id is 1, because there is a title entry with elt_id=0 ahead */ + suc_entries, ), ) ), @@ -246,10 +254,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ history_hidden_entry_view( group_id, - List.length(suc_states) + 1, - cur_state, + List.length(suc_entries) + 1, /* elt_id */ + cur_entry, ), - ] /*elt_id= List.length(suc_states)+1*/ + ], ) ), /* the previous history entry */ @@ -261,8 +269,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { incr_map_helper( history_hidden_entry_view(group_id), base => base + 1, - List.length(suc_states) + 2, - prev_states, + List.length(suc_entries) + 2, /* base elt_id */ + prev_entries, ), ) ), @@ -299,7 +307,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; - let prev_history_view = (history, group_id_base: int) => { + let prev_history_view = + (history: list(UndoHistory.undo_history_group), group_id_base: int) => { Vdom.( Node.div( [Attr.classes(["the-prev-history"])], @@ -312,7 +321,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ); }; - let suc_history_view = (history, group_id_base: int) => { + let suc_history_view = + (history: list(UndoHistory.undo_history_group), group_id_base: int) => { Vdom.( Node.div( [Attr.classes(["the-suc-history"])], @@ -346,7 +356,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let action = ZList.prj_z(cur_group.group_entries).previous_action; switch (action) { | None => - /*if init state is only history entry */ + /*if the init entry is only history entry */ if (List.length(suc_groups) <= 1) { Vdom.( Node.div( From 5a4ecfb5a0aba068793ce91d4d8d4e94c4c4ed05 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 21 Jan 2020 15:56:40 -0500 Subject: [PATCH 38/60] history panel --- src/hazelweb/Model.re | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 947b1b5b50..981677ec58 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -465,7 +465,7 @@ let undo = (model: t): t => { | Some(new_history) => let new_group = ZList.prj_z(new_history); let new_group': UndoHistory.undo_history_group = { - group_entries: ZList.shift_begin(new_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ + group_entries: ZList.shift_begin(new_group.group_entries), /*pointer may be in the wrong position after clicking an arbitrary entry in the history panel*/ is_expanded: true, }; /* is_expanded=true because the selected group should be expanded*/ ZList.replace_z(new_history, new_group'); @@ -496,7 +496,7 @@ let redo = (model: t): t => { | Some(new_history) => let cur_group = ZList.prj_z(new_history); let new_group: UndoHistory.undo_history_group = { - group_entries: ZList.shift_end(cur_group.group_entries), /*pointer may be in the wrong position after clicking history panel*/ + group_entries: ZList.shift_end(cur_group.group_entries), /*pointer may be in the wrong position after clicking an arbitrary entry in the history panel*/ is_expanded: true, }; /* is_expanded=true because this group should be expanded when redo*/ ZList.replace_z(new_history, new_group); From 4bc742903de16f48c7b11d4124c7e474b4fded81 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 28 Jan 2020 21:42:23 -0500 Subject: [PATCH 39/60] fix bug after merging dev, start to try new grouping --- src/hazelcore/semantics/Action.re | 33 +++++++-------------------- src/hazelcore/util/ZList.re | 2 +- src/hazelweb/Model.re | 22 ++++++++++-------- src/hazelweb/UndoHistory.re | 2 -- src/hazelweb/Update.re | 4 ++-- src/hazelweb/cardstacks/CardStacks.re | 2 -- src/hazelweb/gui/UndoHistoryPanel.re | 1 - 7 files changed, 23 insertions(+), 43 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index fa40c9b665..8966793345 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -16,7 +16,7 @@ type operator_shape = | SCons | SAnd | SOr; -let op_shape_to_string = (op: op_shape) => { +let operator_shape_to_string = (op: operator_shape) => { switch (op) { | SMinus => "-" | SPlus => "+" @@ -65,13 +65,10 @@ type t = let shape_to_display_string = (shape: shape): string => { switch (shape) { | SParenthesized => "parentheize" - | SNum => "type Num" - | SBool => "type Bool" | SList => "type List" + | SChar(str) => "edit: " ++ str | SAsc => "type inference" - | SVar(varstr, _) => "edit var: " ++ varstr | SLam => "add lambada" - | SNumLit(value, _) => "edit number: " ++ string_of_int(value) | SListNil => "add []" | SInj(direction) => switch (direction) { @@ -81,10 +78,9 @@ let shape_to_display_string = (shape: shape): string => { | SLet => "bulid 'let'" | SLine => "add new line[s]" | SCase => "add case" - | SOp(op) => "add operator " ++ op_shape_to_string(op) + | SOp(op) => "add operator " ++ operator_shape_to_string(op) | SApPalette(_) => "appalette?" /* pattern-only shapes */ - | SWild => "wild?" }; }; @@ -99,34 +95,24 @@ let action_to_display_string = (action: t) => { | MoveLeft | MoveRight | MoveToNextHole - | MoveToPrevHole - | ShiftLeft - | ShiftRight - | ShiftUp - | ShiftDown => "will not show in undo_history" + | MoveToPrevHole => "will not show in undo_history" }; }; let is_same_shape = (shape_1: shape, shape_2: shape): bool => { switch (shape_1, shape_2) { - | (SVar(_, _), SVar(_, _)) - | (SNumLit(_, _), SNumLit(_, _)) | (SLine, SLine) => true + | (SChar(_), _) | (SParenthesized, _) - | (SNum, _) - | (SBool, _) | (SList, _) | (SAsc, _) - | (SVar(_, _), _) | (SLam, _) - | (SNumLit(_, _), _) | (SListNil, _) | (SInj(_), _) | (SLet, _) | (SLine, _) | (SCase, _) | (SOp(_), _) - | (SApPalette(_), _) - | (SWild, _) => false + | (SApPalette(_), _) => false }; }; let in_same_history_group = (action_1: option(t), action_2: option(t)): bool => { @@ -149,11 +135,8 @@ let in_same_history_group = (action_1: option(t), action_2: option(t)): bool => | (MoveLeft, _) | (MoveRight, _) | (MoveToNextHole, _) - | (MoveToPrevHole, _) - | (ShiftLeft, _) - | (ShiftRight, _) - | (ShiftUp, _) - | (ShiftDown, _) => failwith("not undoable actions, will not be matched") + | (MoveToPrevHole, _) => + failwith("not undoable actions, will not be matched") } }; }; diff --git a/src/hazelcore/util/ZList.re b/src/hazelcore/util/ZList.re index 3f3fa81bf6..2eb5a613f7 100644 --- a/src/hazelcore/util/ZList.re +++ b/src/hazelcore/util/ZList.re @@ -134,4 +134,4 @@ let shift_to = (n: int, xs: t('a, 'a)): option(t('a, 'a)) => { let (prefix, z, suffix) = xs; let lst = prefix @ [z, ...suffix]; split_at(n, lst); -}; \ No newline at end of file +}; diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 692e14dcac..f36c7c832c 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -371,13 +371,14 @@ let undo = (model: t): t => { group_entries: ZList.shift_begin(new_group.group_entries), /*pointer may be in the wrong position after clicking an arbitrary entry in the history panel*/ is_expanded: true, }; /* is_expanded=true because the selected group should be expanded*/ - ZList.replace_z(new_history, new_group'); + ZList.replace_z(new_group', new_history); } | Some(new_group_entries) => - ZList.replace_z( - model.undo_history, - {group_entries: new_group_entries, is_expanded: true}, - ) /* is_expanded=true because the selected group should be expanded*/ + let new_group: UndoHistory.undo_history_group = { + group_entries: new_group_entries, + is_expanded: true, + }; + ZList.replace_z(new_group, model.undo_history); /* is_expanded=true because the selected group should be expanded*/ }; }; let cur_group' = ZList.prj_z(new_history); @@ -402,13 +403,14 @@ let redo = (model: t): t => { group_entries: ZList.shift_end(cur_group.group_entries), /*pointer may be in the wrong position after clicking an arbitrary entry in the history panel*/ is_expanded: true, }; /* is_expanded=true because this group should be expanded when redo*/ - ZList.replace_z(new_history, new_group); + ZList.replace_z(new_group, new_history); } | Some(new_group_entries) => - ZList.replace_z( - model.undo_history, - {group_entries: new_group_entries, is_expanded: true}, - ) + let new_group: UndoHistory.undo_history_group = { + group_entries: new_group_entries, + is_expanded: true, + }; + ZList.replace_z(new_group, model.undo_history); /* is_expanded=true because the selected group should be expanded*/ }; }; let cur_group' = ZList.prj_z(new_history); diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index b2f11cd816..490ad95937 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -1,5 +1,3 @@ -module ZList = GeneralUtil.ZList; - type undo_history_entry = { cardstacks_state: CardStacks.cardstacks_state, previous_action: option(Action.t), diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index 6dbd3f1f52..e389c52b1e 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -220,8 +220,8 @@ let apply_action = ...new_model, undo_history: ZList.replace_z( - new_history, {...cur_group, group_entries: new_group_entries}, + new_history, ), }; }; @@ -236,11 +236,11 @@ let apply_action = let toggle_target_group = ZList.prj_z(history); let after_toggle = ZList.replace_z( - history, { ...toggle_target_group, is_expanded: !toggle_target_group.is_expanded, }, + history, ); /*shift back to the current group*/ switch (ZList.shift_to(cur_group_id, after_toggle)) { diff --git a/src/hazelweb/cardstacks/CardStacks.re b/src/hazelweb/cardstacks/CardStacks.re index 012a2a2f8d..cd118b3876 100644 --- a/src/hazelweb/cardstacks/CardStacks.re +++ b/src/hazelweb/cardstacks/CardStacks.re @@ -1,5 +1,3 @@ -module ZList = GeneralUtil.ZList; - type cardstacks = list(CardStack.t); let cardstacks: cardstacks = [ TutorialCards.cardstack, diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 4741e1377a..1446fa210b 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -1,5 +1,4 @@ module Vdom = Virtual_dom.Vdom; -module ZList = GeneralUtil.ZList; type undo_history_group = UndoHistory.undo_history_group; type undo_history_entry = UndoHistory.undo_history_entry; From 326fd53142552a51d7f664e8142014c04ceae430 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 31 Jan 2020 20:34:23 -0500 Subject: [PATCH 40/60] extract cursor_term --- src/hazelcore/semantics/CursorInfo.re | 78 ++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index 60fcdc8253..ccf986feb9 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -80,11 +80,19 @@ type typed = | OnLine | OnRule; +type cursor_term = + | Exp(CursorPosition.t, UHExp.t) + | Pat(CursorPosition.t, UHPat.t) + | Typ(CursorPosition.t, UHTyp.t) + | Line(UHExp.line) + | Op(CursorPosition.t, UHExp.operator); + // TODO refactor into variants // based on term family and shape -[@deriving sexp] +//[@deriving sexp] type t = { typed, + cursor_term:option(cursor_term), ctx: Contexts.t, // hack while merging uses: option(UsageAnalysis.uses_list), @@ -92,6 +100,74 @@ type t = { let mk = (~uses=?, typed, ctx) => {typed, ctx, uses}; +let rec extract_cursor_exp_term = (exp: ZExp.t): option(cursor_term) => { + switch (exp) { + | ZE2(zblock) => extract_from_zline(ZList.prj_z(zblock)) + | ZE1(zopseq) => extract_from_zopseq(zopseq) + | ZE0(zoperand) => extract_from_zoprand(zoprand) + }; +} +and extract_from_zline = (zline: ZExp.zline): option(cursor_term) => { + switch (zline) { + | CursorL(_, _) => None /* cursor in line is not editable */ + | ExpLineZ(zopseq) => extract_from_zopseq(zopseq) + | LetLineZP(zpat, _, _) => extract_cursor_pat_term(zpat) + | LetLineZA(_, ztyp, _) => extract_cursor_type_term(ztyp) + | LetLineZE(_, _, zexp) => extract_cursor_exp_term(zexp) + }; +} +and extract_from_zoprand = (zoprand: ZExp.zoprand): option(cursor_term) => { + switch (zoperand) { + | CursorE(cursor_pos, operand) => option(cursor_pos, E0(operand)) + | ParenthesizedZ(zexp) => extract_cursor_exp_term(exp_inner) + | LamZP(_, zpat, _, _) => extract_cursor_pat_term(zpat) + | LamZA(_, _, ztyp, _) => extract_cursor_type_term(ztyp) + | LamZE(_, _, _, zexp) + | InjZ(_, _, zexp) + | CaseZE(_, zexp, _, _) => extract_cursor_exp_term(zexp) + | CaseZR(_, _, zrules, _) => None /*??????*/ + | CaseZA(_, _, _, ztyp) => extract_cursor_type_term(ztyp) + | ApPaletteZ(_, _, _, _) => failwith("not oprand with cursor") /*TBD???*/ + }; +} +and extract_from_zoprator = (zoperator: ZExp.zoperator): option(cursor_term) => { + let (cursor_pos, uop) = zoperator; + option(Op(cursor_pos, uop)); +} +and extract_from_zopseq = (zopseq: ZExp.zopseq): option(cursor_term) => { + switch (zopseq) { + | ZOpSeq(_, zseq) => + switch (zseq) { + | ZOperand(zoperand, _) => extract_from_zoprand(zoperand) + | ZOperator(zoperator, _) => extract_from_zoprator(zoperator) + } + }; +} +and extract_cursor_pat_term = (zpat: Zpat.t): option(cursor_term) => { + switch (zpat) { + | ZP1(zopseq) => extract_from_zopseq(zopseq) + | ZP0(zpat_operand) => + switch (zpat_operand) { + | CursorP(cursor_pos, upat_operand) => + option(cursor_pos, P0(upat_operand)) + | ParenthesizedZ(zpat_inner) + | InjZ(_, _, zpat_inner) => extract_cursor_pat_term(zpat_inner) + } + }; +} +and extract_cursor_type_term = (ztyp: ZTyp.t): option(cursor_term) => { + switch (ztyp) { + | ZT1(zopseq) => extract_from_zopseq(zopseq) + | ZT0(ztyp_operand) => + switch (ztyp_operand) { + | CursorT(cursor_pos, utyp_operand) => + option(cursor_pos, T0(utyp_operand)) + | ParenthesizedZ(ztyp_inner) + | ListZ(ztyp_inner) => extract_cursor_type_term(ztyp_inner) + } + }; +}; + module Typ = { let cursor_info = (~steps as _, ctx: Contexts.t, _: ZTyp.t): option(t) => Some(mk(OnType, ctx)); From 3fa023025a19c605ca91b10e65d436591d7c5434 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 31 Jan 2020 22:08:06 -0500 Subject: [PATCH 41/60] fix extract cursor term --- src/hazelcore/semantics/CursorInfo.re | 96 ++++++++++++++++++--------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index ccf986feb9..4f0abd294e 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -84,42 +84,50 @@ type cursor_term = | Exp(CursorPosition.t, UHExp.t) | Pat(CursorPosition.t, UHPat.t) | Typ(CursorPosition.t, UHTyp.t) - | Line(UHExp.line) - | Op(CursorPosition.t, UHExp.operator); + | Line(UHExp.line) /* may be deleted TBD???*/ + | ExpOp(CursorPosition.t, UHExp.operator) + | PatOp(CursorPosition.t, UHPat.operator) + | TypOp(CursorPosition.t, UHTyp.operator); // TODO refactor into variants // based on term family and shape //[@deriving sexp] type t = { typed, - cursor_term:option(cursor_term), + cursor_term: option(cursor_term), ctx: Contexts.t, // hack while merging uses: option(UsageAnalysis.uses_list), }; -let mk = (~uses=?, typed, ctx) => {typed, ctx, uses}; +let mk = (~uses=?, typed, cursor_term, ctx) => { + typed, + cursor_term, + ctx, + uses, +}; let rec extract_cursor_exp_term = (exp: ZExp.t): option(cursor_term) => { switch (exp) { | ZE2(zblock) => extract_from_zline(ZList.prj_z(zblock)) - | ZE1(zopseq) => extract_from_zopseq(zopseq) - | ZE0(zoperand) => extract_from_zoprand(zoprand) + | ZE1(zopseq) => extract_from_zexp_opseq(zopseq) + | ZE0(zoperand) => extract_from_zexp_operand(zoperand) }; } and extract_from_zline = (zline: ZExp.zline): option(cursor_term) => { switch (zline) { | CursorL(_, _) => None /* cursor in line is not editable */ - | ExpLineZ(zopseq) => extract_from_zopseq(zopseq) + | ExpLineZ(zopseq) => extract_from_zexp_opseq(zopseq) | LetLineZP(zpat, _, _) => extract_cursor_pat_term(zpat) | LetLineZA(_, ztyp, _) => extract_cursor_type_term(ztyp) | LetLineZE(_, _, zexp) => extract_cursor_exp_term(zexp) }; } -and extract_from_zoprand = (zoprand: ZExp.zoprand): option(cursor_term) => { - switch (zoperand) { - | CursorE(cursor_pos, operand) => option(cursor_pos, E0(operand)) - | ParenthesizedZ(zexp) => extract_cursor_exp_term(exp_inner) +and extract_from_zexp_operand = + (zexp_operand: ZExp.zoperand): option(cursor_term) => { + switch (zexp_operand) { + | CursorE(cursor_pos, operand) => Some(Exp(cursor_pos, E0(operand))) + | ParenthesizedZ(zexp) => extract_cursor_exp_term(zexp) | LamZP(_, zpat, _, _) => extract_cursor_pat_term(zpat) | LamZA(_, _, ztyp, _) => extract_cursor_type_term(ztyp) | LamZE(_, _, _, zexp) @@ -130,41 +138,63 @@ and extract_from_zoprand = (zoprand: ZExp.zoprand): option(cursor_term) => { | ApPaletteZ(_, _, _, _) => failwith("not oprand with cursor") /*TBD???*/ }; } -and extract_from_zoprator = (zoperator: ZExp.zoperator): option(cursor_term) => { - let (cursor_pos, uop) = zoperator; - option(Op(cursor_pos, uop)); -} -and extract_from_zopseq = (zopseq: ZExp.zopseq): option(cursor_term) => { +and extract_from_zexp_opseq = (zopseq: ZExp.zopseq): option(cursor_term) => { switch (zopseq) { | ZOpSeq(_, zseq) => switch (zseq) { - | ZOperand(zoperand, _) => extract_from_zoprand(zoperand) - | ZOperator(zoperator, _) => extract_from_zoprator(zoperator) + | ZOperand(zoperand, _) => extract_from_zexp_operand(zoperand) + | ZOperator(zoperator, _) => + let (cursor_pos, uop) = zoperator; + Some(ExpOp(cursor_pos, uop)); } }; } -and extract_cursor_pat_term = (zpat: Zpat.t): option(cursor_term) => { +and extract_cursor_pat_term = (zpat: ZPat.t): option(cursor_term) => { switch (zpat) { - | ZP1(zopseq) => extract_from_zopseq(zopseq) - | ZP0(zpat_operand) => - switch (zpat_operand) { - | CursorP(cursor_pos, upat_operand) => - option(cursor_pos, P0(upat_operand)) - | ParenthesizedZ(zpat_inner) - | InjZ(_, _, zpat_inner) => extract_cursor_pat_term(zpat_inner) + | ZP1(zpat_opseq) => + switch (zpat_opseq) { + | ZOpSeq(_, zseq) => + switch (zseq) { + | ZOperand(zpat_operand, _) => extract_from_zpat_operand(zpat_operand) + | ZOperator(zpat_operator, _) => + let (cursor_pos, uop) = zpat_operator; + Some(PatOp(cursor_pos, uop)); + } } + | ZP0(zpat_operand) => extract_from_zpat_operand(zpat_operand) + }; +} +and extract_from_zpat_operand = + (zpat_operand: ZPat.zoperand): option(cursor_term) => { + switch (zpat_operand) { + | CursorP(cursor_pos, upat_operand) => + Some(Pat(cursor_pos, P0(upat_operand))) + | ParenthesizedZ(zpat) + | InjZ(_, _, zpat) => extract_cursor_pat_term(zpat) }; } and extract_cursor_type_term = (ztyp: ZTyp.t): option(cursor_term) => { switch (ztyp) { - | ZT1(zopseq) => extract_from_zopseq(zopseq) - | ZT0(ztyp_operand) => - switch (ztyp_operand) { - | CursorT(cursor_pos, utyp_operand) => - option(cursor_pos, T0(utyp_operand)) - | ParenthesizedZ(ztyp_inner) - | ListZ(ztyp_inner) => extract_cursor_type_term(ztyp_inner) + | ZT1(ztyp_opseq) => + switch (ztyp_opseq) { + | ZOpSeq(_, zseq) => + switch (zseq) { + | ZOperand(ztyp_operand, _) => extract_from_ztyp_operand(ztyp_operand) + | ZOperator(ztyp_operator, _) => + let (cursor_pos, uop) = ztyp_operator; + Some(TypOp(cursor_pos, uop)); + } } + | ZT0(ztyp_operand) => extract_from_ztyp_operand(ztyp_operand) + }; +} +and extract_from_ztyp_operand = + (ztyp_operand: ZTyp.zoperand): option(cursor_term) => { + switch (ztyp_operand) { + | CursorT(cursor_pos, utyp_operand) => + Some(Typ(cursor_pos, T0(utyp_operand))) + | ParenthesizedZ(ztyp) + | ListZ(ztyp) => extract_cursor_type_term(ztyp) }; }; From 08cd1acc899c614976f92609b0992bb5f840cb26 Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 31 Jan 2020 22:32:45 -0500 Subject: [PATCH 42/60] start to change undohistory data type --- src/hazelcore/semantics/CursorInfo.re | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index 4f0abd294e..73865e7263 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -100,12 +100,7 @@ type t = { uses: option(UsageAnalysis.uses_list), }; -let mk = (~uses=?, typed, cursor_term, ctx) => { - typed, - cursor_term, - ctx, - uses, -}; +let mk = (~uses=?, typed, ctx) => {typed, cursor_term: None, ctx, uses}; /*TBD?????*/ let rec extract_cursor_exp_term = (exp: ZExp.t): option(cursor_term) => { switch (exp) { @@ -133,7 +128,7 @@ and extract_from_zexp_operand = | LamZE(_, _, _, zexp) | InjZ(_, _, zexp) | CaseZE(_, zexp, _, _) => extract_cursor_exp_term(zexp) - | CaseZR(_, _, zrules, _) => None /*??????*/ + | CaseZR(_, _, _, _) => None /*?????? TBD CaseZR(_, _, zrules, _)*/ | CaseZA(_, _, _, ztyp) => extract_cursor_type_term(ztyp) | ApPaletteZ(_, _, _, _) => failwith("not oprand with cursor") /*TBD???*/ }; @@ -197,10 +192,10 @@ and extract_from_ztyp_operand = | ListZ(ztyp) => extract_cursor_type_term(ztyp) }; }; - +/* TBD ????? */ module Typ = { let cursor_info = (~steps as _, ctx: Contexts.t, _: ZTyp.t): option(t) => - Some(mk(OnType, ctx)); + Some(mk(OnType, ctx)); /*let's syn cursor_term in another func first to simplify code */ }; /* From 81b13a5dce2fc663aec4a9ace3c7cc562edcfdf3 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 08:26:12 -0500 Subject: [PATCH 43/60] update cursor_term --- src/hazelcore/semantics/CursorInfo.re | 10 ++++++++-- src/hazelweb/Model.re | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index 73865e7263..b4d6e76958 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -100,8 +100,6 @@ type t = { uses: option(UsageAnalysis.uses_list), }; -let mk = (~uses=?, typed, ctx) => {typed, cursor_term: None, ctx, uses}; /*TBD?????*/ - let rec extract_cursor_exp_term = (exp: ZExp.t): option(cursor_term) => { switch (exp) { | ZE2(zblock) => extract_from_zline(ZList.prj_z(zblock)) @@ -192,6 +190,14 @@ and extract_from_ztyp_operand = | ListZ(ztyp) => extract_cursor_type_term(ztyp) }; }; + +let update_cursor_term = (exp: ZExp.t, cursor_info: t): t => { + ...cursor_info, + cursor_term: extract_cursor_exp_term(exp), +}; + +let mk = (~uses=?, typed, ctx) => {typed, cursor_term: None, ctx, uses}; /*TBD?????*/ + /* TBD ????? */ module Typ = { let cursor_info = (~steps as _, ctx: Contexts.t, _: ZTyp.t): option(t) => diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index f36c7c832c..285ca325e6 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -278,7 +278,10 @@ let perform_edit_action = (model: t, a: Action.t): t => { | Failed => raise(FailedAction) | CursorEscaped(_) => raise(CursorEscaped) | Succeeded(new_edit_state) => + let (zexp, _, _) = new_edit_state; let new_model = model |> update_edit_state(new_edit_state); + let new_cursor_info = + CursorInfo.update_cursor_term(zexp, new_model.cursor_info); let new_history = if (UndoHistory.undoable_action(a)) { UndoHistory.push_edit_state( @@ -289,7 +292,7 @@ let perform_edit_action = (model: t, a: Action.t): t => { } else { model.undo_history; }; - {...new_model, undo_history: new_history}; + {...new_model, cursor_info: new_cursor_info, undo_history: new_history}; }; }; From 9328b87d73d6ecdffb9c88b579c0e181f02a79f0 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 16:36:55 -0500 Subject: [PATCH 44/60] remove cursor_term form CursorInfo and add to UndoHistory --- src/hazelcore/semantics/Action.re | 30 +----- src/hazelcore/semantics/CursorInfo.re | 14 ++- src/hazelweb/Model.re | 38 ++++---- src/hazelweb/UndoHistory.re | 135 +++++++++++++++++++------- src/hazelweb/cardstacks/CardStacks.re | 1 - 5 files changed, 129 insertions(+), 89 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index 8966793345..dca504cacb 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -100,8 +100,8 @@ let action_to_display_string = (action: t) => { }; let is_same_shape = (shape_1: shape, shape_2: shape): bool => { switch (shape_1, shape_2) { - | (SLine, SLine) => true - | (SChar(_), _) + | (SLine, SLine) + | (SChar(_), _) => true | (SParenthesized, _) | (SList, _) | (SAsc, _) @@ -115,31 +115,7 @@ let is_same_shape = (shape_1: shape, shape_2: shape): bool => { | (SApPalette(_), _) => false }; }; -let in_same_history_group = (action_1: option(t), action_2: option(t)): bool => { - switch (action_1, action_2) { - | (None, _) - | (_, None) => false - | (Some(detail_action_1), Some(detail_action_2)) => - switch (detail_action_1, detail_action_2) { - | (UpdateApPalette(_), UpdateApPalette(_)) - | (Delete, Delete) - | (Backspace, Backspace) => true - | (Construct(shape_1), Construct(shape_2)) => - is_same_shape(shape_1, shape_2) - | (UpdateApPalette(_), _) - | (Delete, _) - | (Backspace, _) - | (Construct(_), _) => false - | (MoveTo(_), _) - | (MoveToBefore(_), _) - | (MoveLeft, _) - | (MoveRight, _) - | (MoveToNextHole, _) - | (MoveToPrevHole, _) => - failwith("not undoable actions, will not be matched") - } - }; -}; + module Outcome = { type t('success) = | Succeeded('success) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index b4d6e76958..e1c3bec543 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -94,7 +94,6 @@ type cursor_term = //[@deriving sexp] type t = { typed, - cursor_term: option(cursor_term), ctx: Contexts.t, // hack while merging uses: option(UsageAnalysis.uses_list), @@ -191,17 +190,16 @@ and extract_from_ztyp_operand = }; }; -let update_cursor_term = (exp: ZExp.t, cursor_info: t): t => { - ...cursor_info, - cursor_term: extract_cursor_exp_term(exp), -}; +/* let update_cursor_term = (exp: ZExp.t, cursor_info: t): t => { + ...cursor_info, + cursor_term: extract_cursor_exp_term(exp), + }; */ -let mk = (~uses=?, typed, ctx) => {typed, cursor_term: None, ctx, uses}; /*TBD?????*/ +let mk = (~uses=?, typed, ctx) => {typed, ctx, uses}; -/* TBD ????? */ module Typ = { let cursor_info = (~steps as _, ctx: Contexts.t, _: ZTyp.t): option(t) => - Some(mk(OnType, ctx)); /*let's syn cursor_term in another func first to simplify code */ + Some(mk(OnType, ctx)); }; /* diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 285ca325e6..36d19b38d0 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -242,6 +242,7 @@ let init = (): t => { let undo_history_state: UndoHistory.undo_history_entry = { cardstacks_state, previous_action: None, + cursor_term: UndoHistory.get_cursor_term(cardstacks_state), }; let compute_results = init_compute_results; { @@ -254,7 +255,11 @@ let init = (): t => { is_cell_focused: false, undo_history: ( [], - {group_entries: ([], undo_history_state, []), is_expanded: false}, + { + group_entries: ([], undo_history_state, []), + is_expanded: false, + is_complete: true, + }, [], ), left_sidebar_open: false, @@ -278,21 +283,14 @@ let perform_edit_action = (model: t, a: Action.t): t => { | Failed => raise(FailedAction) | CursorEscaped(_) => raise(CursorEscaped) | Succeeded(new_edit_state) => - let (zexp, _, _) = new_edit_state; let new_model = model |> update_edit_state(new_edit_state); - let new_cursor_info = - CursorInfo.update_cursor_term(zexp, new_model.cursor_info); let new_history = - if (UndoHistory.undoable_action(a)) { - UndoHistory.push_edit_state( - model.undo_history, - new_model.cardstacks_state, - Some(a), - ); - } else { - model.undo_history; - }; - {...new_model, cursor_info: new_cursor_info, undo_history: new_history}; + UndoHistory.push_edit_state( + model.undo_history, + new_model.cardstacks_state, + Some(a), + ); + {...new_model, undo_history: new_history}; }; }; @@ -373,6 +371,7 @@ let undo = (model: t): t => { let new_group': UndoHistory.undo_history_group = { group_entries: ZList.shift_begin(new_group.group_entries), /*pointer may be in the wrong position after clicking an arbitrary entry in the history panel*/ is_expanded: true, + is_complete: new_group.is_complete, }; /* is_expanded=true because the selected group should be expanded*/ ZList.replace_z(new_group', new_history); } @@ -380,6 +379,7 @@ let undo = (model: t): t => { let new_group: UndoHistory.undo_history_group = { group_entries: new_group_entries, is_expanded: true, + is_complete: cur_group.is_complete, }; ZList.replace_z(new_group, model.undo_history); /* is_expanded=true because the selected group should be expanded*/ }; @@ -401,17 +401,19 @@ let redo = (model: t): t => { switch (ZList.shift_prev(model.undo_history)) { | None => model.undo_history | Some(new_history) => - let cur_group = ZList.prj_z(new_history); - let new_group: UndoHistory.undo_history_group = { - group_entries: ZList.shift_end(cur_group.group_entries), /*pointer may be in the wrong position after clicking an arbitrary entry in the history panel*/ + let new_group = ZList.prj_z(new_history); + let new_group': UndoHistory.undo_history_group = { + group_entries: ZList.shift_end(new_group.group_entries), /*pointer may be in the wrong position after clicking an arbitrary entry in the history panel*/ is_expanded: true, + is_complete: new_group.is_complete, }; /* is_expanded=true because this group should be expanded when redo*/ - ZList.replace_z(new_group, new_history); + ZList.replace_z(new_group', new_history); } | Some(new_group_entries) => let new_group: UndoHistory.undo_history_group = { group_entries: new_group_entries, is_expanded: true, + is_complete: cur_group.is_complete, }; ZList.replace_z(new_group, model.undo_history); /* is_expanded=true because the selected group should be expanded*/ }; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 490ad95937..ed3da5f0e0 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -1,27 +1,69 @@ +type cursor_term = CursorInfo.cursor_term; type undo_history_entry = { cardstacks_state: CardStacks.cardstacks_state, previous_action: option(Action.t), + cursor_term: option(cursor_term), +}; + +let get_cursor_term = + (cardstacks_state: CardStacks.cardstacks_state): option(cursor_term) => { + let edit_state = + ZList.prj_z(ZList.prj_z(cardstacks_state).zcards).edit_state; + let (zexp, _, _) = edit_state; + CursorInfo.extract_cursor_exp_term(zexp); }; type undo_history_group = { group_entries: ZList.t(undo_history_entry, undo_history_entry), is_expanded: bool, + is_complete: bool, }; type t = ZList.t(undo_history_group, undo_history_group); -let undoable_action = (action: Action.t): bool => { +let undoable_action = (action: option(Action.t)): bool => { switch (action) { - | UpdateApPalette(_) - | Delete - | Backspace - | Construct(_) => true - | MoveTo(_) - | MoveToBefore(_) - | MoveLeft - | MoveRight - | MoveToNextHole - | MoveToPrevHole => false + | None => failwith("Impossible, no None action will be detected") + | Some(action') => + switch (action') { + | UpdateApPalette(_) + | Delete + | Backspace + | Construct(_) => true + | MoveTo(_) + | MoveToBefore(_) + | MoveLeft + | MoveRight + | MoveToNextHole + | MoveToPrevHole => false + } + }; +}; + +let in_same_history_group = + (action_1: option(Action.t), action_2: option(Action.t)): bool => { + switch (action_1, action_2) { + | (None, _) + | (_, None) => false + | (Some(detail_action_1), Some(detail_action_2)) => + switch (detail_action_1, detail_action_2) { + | (UpdateApPalette(_), UpdateApPalette(_)) + | (Delete, Delete) + | (Backspace, Backspace) => true + | (Construct(shape_1), Construct(shape_2)) => + Action.is_same_shape(shape_1, shape_2) + | (UpdateApPalette(_), _) + | (Delete, _) + | (Backspace, _) + | (Construct(_), _) => false + | (MoveTo(_), _) + | (MoveToBefore(_), _) + | (MoveLeft, _) + | (MoveRight, _) + | (MoveToNextHole, _) + | (MoveToPrevHole, _) => + failwith("not undoable actions, will not be matched") + } }; }; @@ -34,31 +76,54 @@ let push_edit_state = : t => { let cur_group = ZList.prj_z(undo_history); let cur_entry = ZList.prj_z(cur_group.group_entries); - if (Action.in_same_history_group(action, cur_entry.previous_action)) { - let new_entry = {cardstacks_state, previous_action: action}; - let group_entries_after_push = ( - [], - new_entry, - [ - ZList.prj_z(cur_group.group_entries), - ...ZList.prj_suffix(cur_group.group_entries), - ], - ); - ( - [], - {group_entries: group_entries_after_push, is_expanded: false}, /* initial state of group should be folded*/ - ZList.prj_suffix(undo_history), - ); - } else { - let new_group = { - group_entries: ([], {cardstacks_state, previous_action: action}, []), - is_expanded: false, + if (undoable_action(cur_entry.previous_action)) { + if (!cur_group.is_complete + && in_same_history_group(action, cur_entry.previous_action)) { + let new_entry = { + cardstacks_state, + previous_action: action, + cursor_term: get_cursor_term(cardstacks_state), + }; + let group_entries_after_push = ( + [], + new_entry, + [ + ZList.prj_z(cur_group.group_entries), + ...ZList.prj_suffix(cur_group.group_entries), + ], + ); + ( + [], + { + group_entries: group_entries_after_push, + is_expanded: false, + is_complete: false, + }, /* initial state of group should be folded*/ + ZList.prj_suffix(undo_history), + ); + } else { + let new_group = { + group_entries: ( + [], + { + cardstacks_state, + previous_action: action, + cursor_term: get_cursor_term(cardstacks_state), + }, + [], + ), + is_expanded: false, + is_complete: false, + }; + ( + [], + new_group, + [ZList.prj_z(undo_history), ...ZList.prj_suffix(undo_history)], + ); }; - ( - [], - new_group, - [ZList.prj_z(undo_history), ...ZList.prj_suffix(undo_history)], - ); + } else { + let new_group = {...cur_group, is_complete: true}; + ZList.replace_z(new_group, undo_history); }; }; diff --git a/src/hazelweb/cardstacks/CardStacks.re b/src/hazelweb/cardstacks/CardStacks.re index cd118b3876..b796398f76 100644 --- a/src/hazelweb/cardstacks/CardStacks.re +++ b/src/hazelweb/cardstacks/CardStacks.re @@ -16,7 +16,6 @@ type cardstack_state = { }; type cardstacks_state = ZList.t(cardstack_state, cardstack_state); - let mk_cardstack_state = (cardstack: CardStack.t) => { let card_states = List.map( From e3bb4ad0d4fc2debe7f65a5c034d065e81e57f94 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 16:43:57 -0500 Subject: [PATCH 45/60] finish grouping, start to build a func of display string --- src/hazelweb/UndoHistory.re | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index ed3da5f0e0..b6141eb886 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -23,7 +23,7 @@ type t = ZList.t(undo_history_group, undo_history_group); let undoable_action = (action: option(Action.t)): bool => { switch (action) { - | None => failwith("Impossible, no None action will be detected") + | None => failwith("Impossible, no None action will be pushed into history") | Some(action') => switch (action') { | UpdateApPalette(_) @@ -76,7 +76,7 @@ let push_edit_state = : t => { let cur_group = ZList.prj_z(undo_history); let cur_entry = ZList.prj_z(cur_group.group_entries); - if (undoable_action(cur_entry.previous_action)) { + if (undoable_action(action)) { if (!cur_group.is_complete && in_same_history_group(action, cur_entry.previous_action)) { let new_entry = { From 8070b5876fbdf1c8fb9f87bd3c7047d9caf872e8 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 18:49:02 -0500 Subject: [PATCH 46/60] display string draft --- src/hazelcore/semantics/Action.re | 35 --------- src/hazelcore/semantics/CursorInfo.re | 14 ++-- src/hazelweb/gui/UndoHistoryPanel.re | 102 +++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 47 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index dca504cacb..99171e983e 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -62,42 +62,7 @@ type t = | Backspace | Construct(shape); -let shape_to_display_string = (shape: shape): string => { - switch (shape) { - | SParenthesized => "parentheize" - | SList => "type List" - | SChar(str) => "edit: " ++ str - | SAsc => "type inference" - | SLam => "add lambada" - | SListNil => "add []" - | SInj(direction) => - switch (direction) { - | L => "inject left" - | R => "inject right" - } - | SLet => "bulid 'let'" - | SLine => "add new line[s]" - | SCase => "add case" - | SOp(op) => "add operator " ++ operator_shape_to_string(op) - | SApPalette(_) => "appalette?" - /* pattern-only shapes */ - }; -}; -let action_to_display_string = (action: t) => { - switch (action) { - | UpdateApPalette(_) => "updatePlate?" - | Delete => "delete" - | Backspace => "backspace" - | Construct(shape) => shape_to_display_string(shape) - | MoveTo(_) - | MoveToBefore(_) - | MoveLeft - | MoveRight - | MoveToNextHole - | MoveToPrevHole => "will not show in undo_history" - }; -}; let is_same_shape = (shape_1: shape, shape_2: shape): bool => { switch (shape_1, shape_2) { | (SLine, SLine) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index e1c3bec543..1a57fee2df 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -81,9 +81,9 @@ type typed = | OnRule; type cursor_term = - | Exp(CursorPosition.t, UHExp.t) - | Pat(CursorPosition.t, UHPat.t) - | Typ(CursorPosition.t, UHTyp.t) + | Exp(CursorPosition.t, UHExp.operand) + | Pat(CursorPosition.t, UHPat.operand) + | Typ(CursorPosition.t, UHTyp.operand) | Line(UHExp.line) /* may be deleted TBD???*/ | ExpOp(CursorPosition.t, UHExp.operator) | PatOp(CursorPosition.t, UHPat.operator) @@ -118,7 +118,7 @@ and extract_from_zline = (zline: ZExp.zline): option(cursor_term) => { and extract_from_zexp_operand = (zexp_operand: ZExp.zoperand): option(cursor_term) => { switch (zexp_operand) { - | CursorE(cursor_pos, operand) => Some(Exp(cursor_pos, E0(operand))) + | CursorE(cursor_pos, operand) => Some(Exp(cursor_pos, operand)) | ParenthesizedZ(zexp) => extract_cursor_exp_term(zexp) | LamZP(_, zpat, _, _) => extract_cursor_pat_term(zpat) | LamZA(_, _, ztyp, _) => extract_cursor_type_term(ztyp) @@ -159,8 +159,7 @@ and extract_cursor_pat_term = (zpat: ZPat.t): option(cursor_term) => { and extract_from_zpat_operand = (zpat_operand: ZPat.zoperand): option(cursor_term) => { switch (zpat_operand) { - | CursorP(cursor_pos, upat_operand) => - Some(Pat(cursor_pos, P0(upat_operand))) + | CursorP(cursor_pos, upat_operand) => Some(Pat(cursor_pos, upat_operand)) | ParenthesizedZ(zpat) | InjZ(_, _, zpat) => extract_cursor_pat_term(zpat) }; @@ -183,8 +182,7 @@ and extract_cursor_type_term = (ztyp: ZTyp.t): option(cursor_term) => { and extract_from_ztyp_operand = (ztyp_operand: ZTyp.zoperand): option(cursor_term) => { switch (ztyp_operand) { - | CursorT(cursor_pos, utyp_operand) => - Some(Typ(cursor_pos, T0(utyp_operand))) + | CursorT(cursor_pos, utyp_operand) => Some(Typ(cursor_pos, utyp_operand)) | ParenthesizedZ(ztyp) | ListZ(ztyp) => extract_cursor_type_term(ztyp) }; diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 1446fa210b..3277ded986 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -18,11 +18,101 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ] }; }; + let display_string_of_cursor_term = + (cursor_term: option(CursorInfo.cursor_term)): string => { + switch (cursor_term) { + | None => failwith("Imposiible, undisplayed undo history entry") + | Some(cursor_term') => + switch (cursor_term') { + | Exp(_, exp) => + switch (exp) { + | EmptyHole(meta_var) => + "edit empty hole: " ++ string_of_int(meta_var) + | Var(_, _, var_str) => "edit var: " ++ var_str + | NumLit(_, num) => "edit number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "add an empty list" + | Lam(_, _, _, _) => "add a lambada function" + | Inj(_, inj_side, _) => + "add an injection: " ++ InjSide.to_string(inj_side) + | Case(_, _, _, _) => "edit case match" + | Parenthesized(_) => "add ()" + | ApPalette(_, _, _, _) => "I don't know its meaning" + } + | Pat(_, pat) => + switch (pat) { + | EmptyHole(meta_var) => + "edit empty hole: " ++ string_of_int(meta_var) + | Wild(_) => "I don't know its meaning" + | Var(_, _, var_str) => "edit var: " ++ var_str + | NumLit(_, num) => "edit number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "add an empty list" + | Parenthesized(_) => "add ()" + | Inj(_, inj_side, _) => + "add an injection: " ++ InjSide.to_string(inj_side) + } + | Typ(_, typ) => + switch (typ) { + | Hole => "type: Hole" + | Unit => "type: Unit" + | Num => "type: Num" + | Bool => "type: Bool" + | Parenthesized(_) => "type: (?)" + | List(_) => "type: [?]" + } + | Line(_) => "TBD aline" + | ExpOp(_, op) => "edit operator: " ++ UHExp.string_of_operator(op) + | PatOp(_, op) => "edit operator: " ++ UHPat.string_of_operator(op) + | TypOp(_, op) => "edit operator: " ++ UHTyp.string_of_operator(op) + } + }; + }; + let display_string_of_history_entry = + (undo_history_entry: undo_history_entry): string => { + let action = undo_history_entry.previous_action; + let cursor_term = undo_history_entry.cursor_term; + switch (action) { + | None => failwith("Imposiible, undisplayed undo history entry") + | Some(action') => + switch (action') { + | MoveTo(_) + | MoveToBefore(_) + | MoveLeft + | MoveRight + | MoveToNextHole + | MoveToPrevHole + | UpdateApPalette(_) => + failwith("not undoable actions, will not be matched") + | Delete + | Backspace => display_string_of_cursor_term(cursor_term) + | Construct(shape) => + switch (shape) { + | SParenthesized => "parentheize" + | SList => "type List" + | SChar(_) => display_string_of_cursor_term(cursor_term) + | SAsc => "type inference" + | SLam => "add lambada" + | SListNil => "add []" + | SInj(direction) => + switch (direction) { + | L => "inject left" + | R => "inject right" + } + | SLet => "bulid 'let'" + | SLine => "add new line[s]" + | SCase => "add case" + | SOp(op) => "add operator " ++ Action.operator_shape_to_string(op) + | SApPalette(_) => "appalette?" + } + } + }; + }; let history_hidden_entry_view = (group_id: int, elt_id: int, undo_history_entry: undo_history_entry) => { switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) /* entry in initial state should not be displayed */ - | Some(detail_ac) => + | Some(_) => Vdom.( Node.div( [ @@ -31,7 +121,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { inject(Update.Action.ShiftHistory(group_id, elt_id)) ), ], - [Node.text(Action.action_to_display_string(detail_ac))], + [Node.text(display_string_of_history_entry(undo_history_entry))], ) ) }; @@ -75,7 +165,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) /* entry in the initial state should not be displayed */ - | Some(detail_ac) => + | Some(_) => Vdom.( Node.div( [Attr.classes(["the-history-title"])], @@ -90,7 +180,11 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { inject(Update.Action.ShiftHistory(group_id, elt_id)) ), ], - [Node.text(Action.action_to_display_string(detail_ac))], + [ + Node.text( + display_string_of_history_entry(undo_history_entry), + ), + ], ), history_tab_icon(group_id), ], From 3944994a4a51242e066fb81c7579865dd055f980 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 19:49:19 -0500 Subject: [PATCH 47/60] zline need to be modified --- src/hazelcore/semantics/Action.re | 1 - src/hazelweb/gui/UndoHistoryPanel.re | 160 +++++++++++++++++++-------- 2 files changed, 116 insertions(+), 45 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index 99171e983e..0887a659c0 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -62,7 +62,6 @@ type t = | Backspace | Construct(shape); - let is_same_shape = (shape_1: shape, shape_2: shape): bool => { switch (shape_1, shape_2) { | (SLine, SLine) diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 3277ded986..1e12ee6601 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -18,53 +18,125 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ] }; }; + let back_delete_view = + ( + action: Action.t, + cursor_pos: option(CursorPosition.t), + otherwise_str: option(string), + ) + : string => { + switch (cursor_pos) { + | None => + if (action == Backspace) { + "backspace"; + } else if (action == Delete) { + "delete"; + } else { + switch (otherwise_str) { + | None => failwith("Imposiible, undisplayed undo history entry") + | Some(str) => str + }; + } + | Some(cursor_pos') => + switch (cursor_pos') { + | OnText(pos_index) => + if (action == Backspace && pos_index == 0) { + "backspace"; + } else if (action == Delete) { + "delete"; + } else { + switch (otherwise_str) { + | None => failwith("Imposiible, undisplayed undo history entry") + | Some(str) => str + }; + } + | OnDelim(deli, side) => + switch (side) { + | Before => string_of_int(deli) ++ ", before" + | After => string_of_int(deli) ++ ", after" + } + | OnOp(_) => + if (action == Backspace) { + "backspace"; + } else if (action == Delete) { + "delete"; + } else { + switch (otherwise_str) { + | None => failwith("Imposiible, undisplayed undo history entry") + | Some(str) => str + }; + } + } + }; + }; let display_string_of_cursor_term = - (cursor_term: option(CursorInfo.cursor_term)): string => { + (cursor_term: option(CursorInfo.cursor_term), action: Action.t): string => { switch (cursor_term) { - | None => failwith("Imposiible, undisplayed undo history entry") + | None => back_delete_view(action, None, None) | Some(cursor_term') => switch (cursor_term') { - | Exp(_, exp) => - switch (exp) { - | EmptyHole(meta_var) => - "edit empty hole: " ++ string_of_int(meta_var) - | Var(_, _, var_str) => "edit var: " ++ var_str - | NumLit(_, num) => "edit number: " ++ string_of_int(num) - | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) - | ListNil(_) => "add an empty list" - | Lam(_, _, _, _) => "add a lambada function" - | Inj(_, inj_side, _) => - "add an injection: " ++ InjSide.to_string(inj_side) - | Case(_, _, _, _) => "edit case match" - | Parenthesized(_) => "add ()" - | ApPalette(_, _, _, _) => "I don't know its meaning" - } - | Pat(_, pat) => - switch (pat) { - | EmptyHole(meta_var) => - "edit empty hole: " ++ string_of_int(meta_var) - | Wild(_) => "I don't know its meaning" - | Var(_, _, var_str) => "edit var: " ++ var_str - | NumLit(_, num) => "edit number: " ++ string_of_int(num) - | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) - | ListNil(_) => "add an empty list" - | Parenthesized(_) => "add ()" - | Inj(_, inj_side, _) => - "add an injection: " ++ InjSide.to_string(inj_side) - } - | Typ(_, typ) => - switch (typ) { - | Hole => "type: Hole" - | Unit => "type: Unit" - | Num => "type: Num" - | Bool => "type: Bool" - | Parenthesized(_) => "type: (?)" - | List(_) => "type: [?]" - } + | Exp(cursor_pos, exp) => + let exp_str = + switch (exp) { + | EmptyHole(meta_var) => + "edit empty hole: " ++ string_of_int(meta_var) + | Var(_, _, var_str) => "edit var: " ++ var_str + | NumLit(_, num) => "edit number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "add an empty list" + | Lam(_, _, _, _) => "add a lambada function" + | Inj(_, inj_side, _) => + "add an injection: " ++ InjSide.to_string(inj_side) + | Case(_, _, _, _) => "edit case match" + | Parenthesized(_) => "add ()" + | ApPalette(_, _, _, _) => "I don't know its meaning" + }; + back_delete_view(action, Some(cursor_pos), Some(exp_str)); + | Pat(cursor_pos, pat) => + let pat_str = + switch (pat) { + | EmptyHole(meta_var) => + "edit empty hole: " ++ string_of_int(meta_var) + | Wild(_) => "I don't know its meaning" + | Var(_, _, var_str) => "edit var: " ++ var_str + | NumLit(_, num) => "edit number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "add an empty list" + | Parenthesized(_) => "add ()" + | Inj(_, inj_side, _) => + "add an injection: " ++ InjSide.to_string(inj_side) + }; + back_delete_view(action, Some(cursor_pos), Some(pat_str)); + | Typ(cursor_pos, typ) => + let typ_str = + switch (typ) { + | Hole => "type: Hole" + | Unit => "type: Unit" + | Num => "type: Num" + | Bool => "type: Bool" + | Parenthesized(_) => "type: (?)" + | List(_) => "type: [?]" + }; + back_delete_view(action, Some(cursor_pos), Some(typ_str)); | Line(_) => "TBD aline" - | ExpOp(_, op) => "edit operator: " ++ UHExp.string_of_operator(op) - | PatOp(_, op) => "edit operator: " ++ UHPat.string_of_operator(op) - | TypOp(_, op) => "edit operator: " ++ UHTyp.string_of_operator(op) + | ExpOp(cursor_pos, op) => + back_delete_view( + action, + Some(cursor_pos), + Some("edit operator: " ++ UHExp.string_of_operator(op)), + ) + | PatOp(cursor_pos, op) => + back_delete_view( + action, + Some(cursor_pos), + Some("edit operator: " ++ UHPat.string_of_operator(op)), + ) + | TypOp(cursor_pos, op) => + back_delete_view( + action, + Some(cursor_pos), + Some("edit operator: " ++ UHTyp.string_of_operator(op)), + ) } }; }; @@ -85,12 +157,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | UpdateApPalette(_) => failwith("not undoable actions, will not be matched") | Delete - | Backspace => display_string_of_cursor_term(cursor_term) + | Backspace => display_string_of_cursor_term(cursor_term, action') | Construct(shape) => switch (shape) { | SParenthesized => "parentheize" | SList => "type List" - | SChar(_) => display_string_of_cursor_term(cursor_term) + | SChar(_) => display_string_of_cursor_term(cursor_term, action') | SAsc => "type inference" | SLam => "add lambada" | SListNil => "add []" From ba0adb8ce9ba6f3fa0381650a799339e3d37bf97 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 23:33:47 -0500 Subject: [PATCH 48/60] finish basic new version of panel, start to improve empty lines grouping --- src/hazelcore/semantics/CursorInfo.re | 44 ++++++++-- src/hazelcore/semantics/UHExp.re | 28 +++++++ src/hazelcore/semantics/UHPat.re | 17 ++++ src/hazelweb/UndoHistory.re | 39 +++++---- src/hazelweb/gui/UndoHistoryPanel.re | 114 +++++++++++++------------- 5 files changed, 156 insertions(+), 86 deletions(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index 1a57fee2df..8d820a7eb1 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -84,10 +84,11 @@ type cursor_term = | Exp(CursorPosition.t, UHExp.operand) | Pat(CursorPosition.t, UHPat.operand) | Typ(CursorPosition.t, UHTyp.operand) - | Line(UHExp.line) /* may be deleted TBD???*/ | ExpOp(CursorPosition.t, UHExp.operator) | PatOp(CursorPosition.t, UHPat.operator) - | TypOp(CursorPosition.t, UHTyp.operator); + | TypOp(CursorPosition.t, UHTyp.operator) + | Line(CursorPosition.t, UHExp.line) /* may be deleted TBD???*/ + | Rule(CursorPosition.t, UHExp.rule); // TODO refactor into variants // based on term family and shape @@ -108,7 +109,7 @@ let rec extract_cursor_exp_term = (exp: ZExp.t): option(cursor_term) => { } and extract_from_zline = (zline: ZExp.zline): option(cursor_term) => { switch (zline) { - | CursorL(_, _) => None /* cursor in line is not editable */ + | CursorL(cursor_pos, uex_line) => Some(Line(cursor_pos, uex_line)) | ExpLineZ(zopseq) => extract_from_zexp_opseq(zopseq) | LetLineZP(zpat, _, _) => extract_cursor_pat_term(zpat) | LetLineZA(_, ztyp, _) => extract_cursor_type_term(ztyp) @@ -125,11 +126,19 @@ and extract_from_zexp_operand = | LamZE(_, _, _, zexp) | InjZ(_, _, zexp) | CaseZE(_, zexp, _, _) => extract_cursor_exp_term(zexp) - | CaseZR(_, _, _, _) => None /*?????? TBD CaseZR(_, _, zrules, _)*/ + | CaseZR(_, _, zrules, _) => extract_from_zrules(zrules) | CaseZA(_, _, _, ztyp) => extract_cursor_type_term(ztyp) | ApPaletteZ(_, _, _, _) => failwith("not oprand with cursor") /*TBD???*/ }; } +and extract_from_zrules = (zrules: ZExp.zrules): option(cursor_term) => { + let zrule = ZList.prj_z(zrules); + switch (zrule) { + | CursorR(cursor_pos, uex_rule) => Some(Rule(cursor_pos, uex_rule)) + | RuleZP(zpat, _) => extract_cursor_pat_term(zpat) + | RuleZE(_, zexp) => extract_cursor_exp_term(zexp) + }; +} and extract_from_zexp_opseq = (zopseq: ZExp.zopseq): option(cursor_term) => { switch (zopseq) { | ZOpSeq(_, zseq) => @@ -188,10 +197,29 @@ and extract_from_ztyp_operand = }; }; -/* let update_cursor_term = (exp: ZExp.t, cursor_info: t): t => { - ...cursor_info, - cursor_term: extract_cursor_exp_term(exp), - }; */ +let is_same_cursor_term = + (cursor_term_1: option(cursor_term), cursor_term_2: option(cursor_term)) + : bool => { + switch (cursor_term_1, cursor_term_2) { + | (None, _) + | (_, None) => false + | (Some(cur1), Some(cur2)) => + switch (cur1, cur2) { + | (Exp(_, op1), Exp(_, op2)) => UHExp.is_same_operand(op1, op2) + | (Pat(_, op1), Pat(_, op2)) => UHPat.is_same_operand(op1, op2) + | (Line(_, line1), Line(_, line2)) => + UHExp.can_group_lines(line1, line2) + | (Exp(_, _), _) + | (Pat(_, _), _) + | (Typ(_, _), _) + | (ExpOp(_, _), _) + | (PatOp(_, _), _) + | (TypOp(_, _), _) + | (Line(_, _), _) + | (Rule(_, _), _) => false + } + }; +}; let mk = (~uses=?, typed, ctx) => {typed, ctx, uses}; diff --git a/src/hazelcore/semantics/UHExp.re b/src/hazelcore/semantics/UHExp.re index a32c465cd8..745d62ff1e 100644 --- a/src/hazelcore/semantics/UHExp.re +++ b/src/hazelcore/semantics/UHExp.re @@ -449,3 +449,31 @@ let text_operand = u_gen, ); }; + +let is_same_operand = (op1: operand, op2: operand): bool => { + switch (op1, op2) { + | (EmptyHole(metavar1), EmptyHole(metavar2)) => metavar1 == metavar2 + | (Var(_, _, _), Var(_, _, _)) + | (NumLit(_, _), NumLit(_, _)) + | (BoolLit(_, _), BoolLit(_, _)) => true + | (EmptyHole(_), _) + | (Var(_, _, _), _) + | (NumLit(_, _), _) + | (BoolLit(_, _), _) + | (ListNil(_), _) + | (Lam(_, _, _, _), _) + | (Inj(_, _, _), _) + | (Case(_, _, _, _), _) + | (Parenthesized(_), _) + | (ApPalette(_, _, _, _), _) => false + }; +}; + +let can_group_lines = (line1: line, line2: line): bool => { + switch (line1, line2) { + | (EmptyLine, EmptyLine) => true + | (EmptyLine, _) + | (LetLine(_, _, _), _) + | (ExpLine(_), _) => false + }; +}; diff --git a/src/hazelcore/semantics/UHPat.re b/src/hazelcore/semantics/UHPat.re index 2ba3a48bcf..1d827585c5 100644 --- a/src/hazelcore/semantics/UHPat.re +++ b/src/hazelcore/semantics/UHPat.re @@ -228,3 +228,20 @@ let text_operand = u_gen, ); }; + +let is_same_operand = (op1: operand, op2: operand): bool => { + switch (op1, op2) { + | (EmptyHole(metavar1), EmptyHole(metavar2)) => metavar1 == metavar2 + | (Var(_, _, _), Var(_, _, _)) + | (NumLit(_, _), NumLit(_, _)) + | (BoolLit(_, _), BoolLit(_, _)) => true + | (EmptyHole(_), _) + | (Wild(_), _) + | (Var(_, _, _), _) + | (NumLit(_, _), _) + | (BoolLit(_, _), _) + | (ListNil(_), _) + | (Parenthesized(_), _) + | (Inj(_, _, _), _) => false + }; +}; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index b6141eb886..7d872e9dd8 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -41,17 +41,25 @@ let undoable_action = (action: option(Action.t)): bool => { }; let in_same_history_group = - (action_1: option(Action.t), action_2: option(Action.t)): bool => { - switch (action_1, action_2) { + (entry_1: undo_history_entry, entry_2: undo_history_entry): bool => { + switch (entry_1.previous_action, entry_2.previous_action) { | (None, _) | (_, None) => false | (Some(detail_action_1), Some(detail_action_2)) => switch (detail_action_1, detail_action_2) { | (UpdateApPalette(_), UpdateApPalette(_)) | (Delete, Delete) - | (Backspace, Backspace) => true + | (Backspace, Backspace) => + CursorInfo.is_same_cursor_term(entry_1.cursor_term, entry_2.cursor_term) | (Construct(shape_1), Construct(shape_2)) => - Action.is_same_shape(shape_1, shape_2) + if (Action.is_same_shape(shape_1, shape_2)) { + CursorInfo.is_same_cursor_term( + entry_1.cursor_term, + entry_2.cursor_term, + ); + } else { + false; + } | (UpdateApPalette(_), _) | (Delete, _) | (Backspace, _) @@ -77,13 +85,12 @@ let push_edit_state = let cur_group = ZList.prj_z(undo_history); let cur_entry = ZList.prj_z(cur_group.group_entries); if (undoable_action(action)) { - if (!cur_group.is_complete - && in_same_history_group(action, cur_entry.previous_action)) { - let new_entry = { - cardstacks_state, - previous_action: action, - cursor_term: get_cursor_term(cardstacks_state), - }; + let new_entry = { + cardstacks_state, + previous_action: action, + cursor_term: get_cursor_term(cardstacks_state), + }; + if (!cur_group.is_complete && in_same_history_group(cur_entry, new_entry)) { let group_entries_after_push = ( [], new_entry, @@ -103,15 +110,7 @@ let push_edit_state = ); } else { let new_group = { - group_entries: ( - [], - { - cardstacks_state, - previous_action: action, - cursor_term: get_cursor_term(cardstacks_state), - }, - [], - ), + group_entries: ([], new_entry, []), is_expanded: false, is_complete: false, }; diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 1e12ee6601..d41e5d4139 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -25,46 +25,37 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { otherwise_str: option(string), ) : string => { - switch (cursor_pos) { - | None => - if (action == Backspace) { - "backspace"; - } else if (action == Delete) { - "delete"; - } else { - switch (otherwise_str) { - | None => failwith("Imposiible, undisplayed undo history entry") - | Some(str) => str - }; - } - | Some(cursor_pos') => - switch (cursor_pos') { - | OnText(pos_index) => - if (action == Backspace && pos_index == 0) { - "backspace"; - } else if (action == Delete) { - "delete"; - } else { - switch (otherwise_str) { - | None => failwith("Imposiible, undisplayed undo history entry") - | Some(str) => str - }; - } - | OnDelim(deli, side) => - switch (side) { - | Before => string_of_int(deli) ++ ", before" - | After => string_of_int(deli) ++ ", after" - } - | OnOp(_) => + switch (otherwise_str) { + | None => failwith("Imposiible, undisplayed undo history entry") + | Some(str) => + switch (cursor_pos) { + | None => if (action == Backspace) { "backspace"; } else if (action == Delete) { "delete"; } else { - switch (otherwise_str) { - | None => failwith("Imposiible, undisplayed undo history entry") - | Some(str) => str - }; + str; + } + | Some(cursor_pos') => + switch (cursor_pos') { + | OnText(_) => "edittext " ++ str + | OnDelim(_, side) => + switch (side) { + | Before => + if (action == Delete) { + "selectdelib " ++ str; + } else { + "editdelib " ++ str; + } + | After => + if (action == Backspace) { + "selectdelia " ++ str; + } else { + "editdelia " ++ str; + } + } + | OnOp(_) => "addop " ++ str } } }; @@ -78,33 +69,31 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Exp(cursor_pos, exp) => let exp_str = switch (exp) { - | EmptyHole(meta_var) => - "edit empty hole: " ++ string_of_int(meta_var) - | Var(_, _, var_str) => "edit var: " ++ var_str - | NumLit(_, num) => "edit number: " ++ string_of_int(num) - | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) - | ListNil(_) => "add an empty list" - | Lam(_, _, _, _) => "add a lambada function" + | EmptyHole(meta_var) => "hole: " ++ string_of_int(meta_var) + | Var(_, _, var_str) => "var: " ++ var_str + | NumLit(_, num) => "number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "empty list" + | Lam(_, _, _, _) => "lambada function" | Inj(_, inj_side, _) => - "add an injection: " ++ InjSide.to_string(inj_side) - | Case(_, _, _, _) => "edit case match" - | Parenthesized(_) => "add ()" + "injection: " ++ InjSide.to_string(inj_side) + | Case(_, _, _, _) => "case match" + | Parenthesized(_) => "()" | ApPalette(_, _, _, _) => "I don't know its meaning" }; back_delete_view(action, Some(cursor_pos), Some(exp_str)); | Pat(cursor_pos, pat) => let pat_str = switch (pat) { - | EmptyHole(meta_var) => - "edit empty hole: " ++ string_of_int(meta_var) + | EmptyHole(meta_var) => "empty hole: " ++ string_of_int(meta_var) | Wild(_) => "I don't know its meaning" - | Var(_, _, var_str) => "edit var: " ++ var_str - | NumLit(_, num) => "edit number: " ++ string_of_int(num) - | BoolLit(_, bool_val) => "edit bool: " ++ string_of_bool(bool_val) - | ListNil(_) => "add an empty list" - | Parenthesized(_) => "add ()" + | Var(_, _, var_str) => "var: " ++ var_str + | NumLit(_, num) => "number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "empty list" + | Parenthesized(_) => "()" | Inj(_, inj_side, _) => - "add an injection: " ++ InjSide.to_string(inj_side) + "injection: " ++ InjSide.to_string(inj_side) }; back_delete_view(action, Some(cursor_pos), Some(pat_str)); | Typ(cursor_pos, typ) => @@ -118,25 +107,34 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | List(_) => "type: [?]" }; back_delete_view(action, Some(cursor_pos), Some(typ_str)); - | Line(_) => "TBD aline" | ExpOp(cursor_pos, op) => back_delete_view( action, Some(cursor_pos), - Some("edit operator: " ++ UHExp.string_of_operator(op)), + Some("operator: " ++ UHExp.string_of_operator(op)), ) | PatOp(cursor_pos, op) => back_delete_view( action, Some(cursor_pos), - Some("edit operator: " ++ UHPat.string_of_operator(op)), + Some("operator: " ++ UHPat.string_of_operator(op)), ) | TypOp(cursor_pos, op) => back_delete_view( action, Some(cursor_pos), - Some("edit operator: " ++ UHTyp.string_of_operator(op)), + Some("operator: " ++ UHTyp.string_of_operator(op)), ) + | Line(cursor_pos, line_content) => + let line_str = + switch (line_content) { + | EmptyLine => "empty line" + | LetLine(_, _, _) => "let binding" + | ExpLine(_) => "epression line" + }; + back_delete_view(action, Some(cursor_pos), Some(line_str)); + | Rule(cursor_pos, _) => + back_delete_view(action, Some(cursor_pos), Some("rule")) } }; }; @@ -172,7 +170,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | R => "inject right" } | SLet => "bulid 'let'" - | SLine => "add new line[s]" + | SLine => "add new lines" | SCase => "add case" | SOp(op) => "add operator " ++ Action.operator_shape_to_string(op) | SApPalette(_) => "appalette?" From e56b25feb197753b31a52dcc451176d0780ffd0a Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 23:56:11 -0500 Subject: [PATCH 49/60] fix empty line group --- src/hazelweb/gui/UndoHistoryPanel.re | 59 ++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index d41e5d4139..29f324067b 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -23,6 +23,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { action: Action.t, cursor_pos: option(CursorPosition.t), otherwise_str: option(string), + is_empty_line: bool, ) : string => { switch (otherwise_str) { @@ -39,20 +40,25 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } | Some(cursor_pos') => switch (cursor_pos') { - | OnText(_) => "edittext " ++ str + | OnText(_) => + if (is_empty_line) { + "clear the line"; + } else { + "edit " ++ str; + } | OnDelim(_, side) => switch (side) { | Before => if (action == Delete) { - "selectdelib " ++ str; + "select " ++ str; } else { - "editdelib " ++ str; + "edit " ++ str; } | After => if (action == Backspace) { - "selectdelia " ++ str; + "select " ++ str; } else { - "editdelia " ++ str; + "edit " ++ str; } } | OnOp(_) => "addop " ++ str @@ -63,7 +69,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let display_string_of_cursor_term = (cursor_term: option(CursorInfo.cursor_term), action: Action.t): string => { switch (cursor_term) { - | None => back_delete_view(action, None, None) + | None => back_delete_view(action, None, None, false) | Some(cursor_term') => switch (cursor_term') { | Exp(cursor_pos, exp) => @@ -81,7 +87,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Parenthesized(_) => "()" | ApPalette(_, _, _, _) => "I don't know its meaning" }; - back_delete_view(action, Some(cursor_pos), Some(exp_str)); + back_delete_view(action, Some(cursor_pos), Some(exp_str), false); | Pat(cursor_pos, pat) => let pat_str = switch (pat) { @@ -95,7 +101,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Inj(_, inj_side, _) => "injection: " ++ InjSide.to_string(inj_side) }; - back_delete_view(action, Some(cursor_pos), Some(pat_str)); + back_delete_view(action, Some(cursor_pos), Some(pat_str), false); | Typ(cursor_pos, typ) => let typ_str = switch (typ) { @@ -106,35 +112,54 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Parenthesized(_) => "type: (?)" | List(_) => "type: [?]" }; - back_delete_view(action, Some(cursor_pos), Some(typ_str)); + back_delete_view(action, Some(cursor_pos), Some(typ_str), false); | ExpOp(cursor_pos, op) => back_delete_view( action, Some(cursor_pos), Some("operator: " ++ UHExp.string_of_operator(op)), + false, ) | PatOp(cursor_pos, op) => back_delete_view( action, Some(cursor_pos), Some("operator: " ++ UHPat.string_of_operator(op)), + false, ) | TypOp(cursor_pos, op) => back_delete_view( action, Some(cursor_pos), Some("operator: " ++ UHTyp.string_of_operator(op)), + false, ) | Line(cursor_pos, line_content) => - let line_str = - switch (line_content) { - | EmptyLine => "empty line" - | LetLine(_, _, _) => "let binding" - | ExpLine(_) => "epression line" - }; - back_delete_view(action, Some(cursor_pos), Some(line_str)); + switch (line_content) { + | EmptyLine => + back_delete_view( + action, + Some(cursor_pos), + Some("empty line"), + true, + ) + | LetLine(_, _, _) => + back_delete_view( + action, + Some(cursor_pos), + Some("let binding"), + false, + ) + | ExpLine(_) => + back_delete_view( + action, + Some(cursor_pos), + Some("epression line"), + false, + ) + } | Rule(cursor_pos, _) => - back_delete_view(action, Some(cursor_pos), Some("rule")) + back_delete_view(action, Some(cursor_pos), Some("rule"), false) } }; }; From e8340a4848766c04995d904af25d6f199be98f6a Mon Sep 17 00:00:00 2001 From: Zoe Date: Sat, 1 Feb 2020 23:57:58 -0500 Subject: [PATCH 50/60] try to display 'clear lines' when cursor at the front of the hole and press backspace --- src/hazelweb/gui/UndoHistoryPanel.re | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 29f324067b..d5a3ad7866 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -18,6 +18,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ] }; }; + /* special case for backspace and delete */ let back_delete_view = ( action: Action.t, From e4450b171707d6ed8069a684cd76f771dcbfaddf Mon Sep 17 00:00:00 2001 From: Zoe Date: Sun, 2 Feb 2020 11:33:27 -0500 Subject: [PATCH 51/60] clear some unused functions and comments --- src/hazelcore/semantics/Action.re | 17 ----------------- src/hazelweb/gui/UndoHistoryPanel.re | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index 0887a659c0..becedfa7f5 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -16,23 +16,6 @@ type operator_shape = | SCons | SAnd | SOr; -let operator_shape_to_string = (op: operator_shape) => { - switch (op) { - | SMinus => "-" - | SPlus => "+" - | STimes => "*" - | SLessThan => "<" - | SGreaterThan => ">" - | SEquals => "=" - | SSpace => "[space]" - | SComma => "," - | SArrow => "=>" - | SVBar => "[VBar]" - | SCons => "::" - | SAnd => "&" - | SOr => "|" - }; -}; [@deriving sexp] type shape = diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index d5a3ad7866..feb8707ce7 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -62,7 +62,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { "edit " ++ str; } } - | OnOp(_) => "addop " ++ str + | OnOp(_) => "add " ++ str } } }; @@ -92,7 +92,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Pat(cursor_pos, pat) => let pat_str = switch (pat) { - | EmptyHole(meta_var) => "empty hole: " ++ string_of_int(meta_var) + | EmptyHole(meta_var) => "hole: " ++ string_of_int(meta_var) | Wild(_) => "I don't know its meaning" | Var(_, _, var_str) => "var: " ++ var_str | NumLit(_, num) => "number: " ++ string_of_int(num) @@ -160,7 +160,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) } | Rule(cursor_pos, _) => - back_delete_view(action, Some(cursor_pos), Some("rule"), false) + back_delete_view( + action, + Some(cursor_pos), + Some("match rule"), + false, + ) } }; }; @@ -184,21 +189,21 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Backspace => display_string_of_cursor_term(cursor_term, action') | Construct(shape) => switch (shape) { - | SParenthesized => "parentheize" + | SParenthesized => "add ( )" | SList => "type List" - | SChar(_) => display_string_of_cursor_term(cursor_term, action') | SAsc => "type inference" | SLam => "add lambada" - | SListNil => "add []" + | SListNil => "add [ ]" | SInj(direction) => switch (direction) { | L => "inject left" | R => "inject right" } - | SLet => "bulid 'let'" + | SLet => "add let binding" | SLine => "add new lines" | SCase => "add case" - | SOp(op) => "add operator " ++ Action.operator_shape_to_string(op) + | SChar(_) + | SOp(_) => display_string_of_cursor_term(cursor_term, action') | SApPalette(_) => "appalette?" } } From c4affdffbf43719c2d1ae9dc4dc4dd0335409084 Mon Sep 17 00:00:00 2001 From: Zoe Date: Sun, 2 Feb 2020 12:08:53 -0500 Subject: [PATCH 52/60] add some comments --- src/hazelcore/semantics/Action.re | 3 +- src/hazelcore/semantics/CursorInfo.re | 2 +- src/hazelweb/UndoHistory.re | 32 ++++-- src/hazelweb/Update.re | 21 ++-- src/hazelweb/gui/UndoHistoryPanel.re | 139 +++++++++++++++----------- 5 files changed, 119 insertions(+), 78 deletions(-) diff --git a/src/hazelcore/semantics/Action.re b/src/hazelcore/semantics/Action.re index becedfa7f5..322a6795c5 100644 --- a/src/hazelcore/semantics/Action.re +++ b/src/hazelcore/semantics/Action.re @@ -45,7 +45,8 @@ type t = | Backspace | Construct(shape); -let is_same_shape = (shape_1: shape, shape_2: shape): bool => { +/* group entries in undo_history if their shapes are similar */ +let can_group_shape = (shape_1: shape, shape_2: shape): bool => { switch (shape_1, shape_2) { | (SLine, SLine) | (SChar(_), _) => true diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index 8d820a7eb1..b2a8369866 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -197,7 +197,7 @@ and extract_from_ztyp_operand = }; }; -let is_same_cursor_term = +let can_group_cursor_term = (cursor_term_1: option(cursor_term), cursor_term_2: option(cursor_term)) : bool => { switch (cursor_term_1, cursor_term_2) { diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 7d872e9dd8..81d65d9630 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -16,6 +16,9 @@ let get_cursor_term = type undo_history_group = { group_entries: ZList.t(undo_history_entry, undo_history_entry), is_expanded: bool, + /* [is_complete: bool] if any cursor-moving action interupts the current edit, + the current group becomes complete. + Next action will start a new group */ is_complete: bool, }; @@ -23,7 +26,10 @@ type t = ZList.t(undo_history_group, undo_history_group); let undoable_action = (action: option(Action.t)): bool => { switch (action) { - | None => failwith("Impossible, no None action will be pushed into history") + | None => + failwith( + "Impossible match. None of None-action will be pushed into history", + ) | Some(action') => switch (action') { | UpdateApPalette(_) @@ -50,10 +56,14 @@ let in_same_history_group = | (UpdateApPalette(_), UpdateApPalette(_)) | (Delete, Delete) | (Backspace, Backspace) => - CursorInfo.is_same_cursor_term(entry_1.cursor_term, entry_2.cursor_term) + CursorInfo.can_group_cursor_term( + entry_1.cursor_term, + entry_2.cursor_term, + ) | (Construct(shape_1), Construct(shape_2)) => - if (Action.is_same_shape(shape_1, shape_2)) { - CursorInfo.is_same_cursor_term( + /* if shapes are similar, then continue to check if they have similar cursor_term */ + if (Action.can_group_shape(shape_1, shape_2)) { + CursorInfo.can_group_cursor_term( entry_1.cursor_term, entry_2.cursor_term, ); @@ -70,7 +80,9 @@ let in_same_history_group = | (MoveRight, _) | (MoveToNextHole, _) | (MoveToPrevHole, _) => - failwith("not undoable actions, will not be matched") + failwith( + "Impossible match. Not undoable actions will not be added into history", + ) } }; }; @@ -91,6 +103,7 @@ let push_edit_state = cursor_term: get_cursor_term(cardstacks_state), }; if (!cur_group.is_complete && in_same_history_group(cur_entry, new_entry)) { + /* group the new entry into the current group */ let group_entries_after_push = ( [], new_entry, @@ -105,10 +118,11 @@ let push_edit_state = group_entries: group_entries_after_push, is_expanded: false, is_complete: false, - }, /* initial state of group should be folded*/ + }, /* initial expanded-state of a group should be folded*/ ZList.prj_suffix(undo_history), ); } else { + /* start a new group */ let new_group = { group_entries: ([], new_entry, []), is_expanded: false, @@ -121,8 +135,10 @@ let push_edit_state = ); }; } else { - let new_group = {...cur_group, is_complete: true}; - ZList.replace_z(new_group, undo_history); + /* if any cursor-moving action interupts the current edit, + the current group becomes complete. */ + let cur_group' = {...cur_group, is_complete: true}; + ZList.replace_z(cur_group', undo_history); }; }; diff --git a/src/hazelweb/Update.re b/src/hazelweb/Update.re index e389c52b1e..888e47404c 100644 --- a/src/hazelweb/Update.re +++ b/src/hazelweb/Update.re @@ -200,15 +200,14 @@ let apply_action = model; | Undo => Model.undo(model) | Redo => Model.redo(model) + /* click the history panel to shift to the certain history entry */ | ShiftHistory(group_id, elt_id) => - /* click history panel to shift to the certain history entry */ - /*shift to the group with group_id*/ - + /* shift to the group with group_id */ switch (ZList.shift_to(group_id, model.undo_history)) { - | None => failwith("Impossible because undo_history is non-empty") + | None => failwith("Impossible match, because undo_history is non-empty") | Some(new_history) => let cur_group = ZList.prj_z(new_history); - /*shift to the element with elt_id*/ + /* shift to the element with elt_id */ switch (ZList.shift_to(elt_id, cur_group.group_entries)) { | None => failwith("Impossible because group_entries is non-empty") | Some(new_group_entries) => @@ -227,13 +226,14 @@ let apply_action = }; } | ToggleHistoryGroup(toggle_group_id) => - let (suc_group, _, _) = model.undo_history; - let cur_group_id = List.length(suc_group); - /*shift to toggle group and change expanded state*/ + let (suc_groups, _, _) = model.undo_history; + let cur_group_id = List.length(suc_groups); + /*shift to the toggle-target group and change its expanded state*/ switch (ZList.shift_to(toggle_group_id, model.undo_history)) { - | None => failwith("Impossible because undo_history is non-empty") + | None => failwith("Impossible match, because undo_history is non-empty") | Some(history) => let toggle_target_group = ZList.prj_z(history); + /* change expanded state of the toggle target group after toggling */ let after_toggle = ZList.replace_z( { @@ -244,7 +244,8 @@ let apply_action = ); /*shift back to the current group*/ switch (ZList.shift_to(cur_group_id, after_toggle)) { - | None => failwith("Impossible because undo_history is non-empty") + | None => + failwith("Impossible match, because undo_history is non-empty") | Some(new_history) => {...model, undo_history: new_history} }; }; diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index feb8707ce7..ebf36c9309 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -4,12 +4,12 @@ type undo_history_entry = UndoHistory.undo_history_entry; let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { /* a helper function working as an enhanced version of List.map() */ - let rec incr_map_helper = (func_to_list, func_to_base, base, lst) => { + let rec list_map_helper_func = (func_to_list, func_to_base, base, lst) => { switch (lst) { | [] => [] | [head, ...tail] => [ func_to_list(base, head), - ...incr_map_helper( + ...list_map_helper_func( func_to_list, func_to_base, func_to_base(base), @@ -18,8 +18,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ] }; }; - /* special case for backspace and delete */ - let back_delete_view = + /* special display case for backspace and delete */ + let filter_with_backspace_delete = ( action: Action.t, cursor_pos: option(CursorPosition.t), @@ -28,7 +28,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) : string => { switch (otherwise_str) { - | None => failwith("Imposiible, undisplayed undo history entry") + | None => failwith("Imposiible match, undisplayed undo history entry") | Some(str) => switch (cursor_pos) { | None => @@ -37,7 +37,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } else if (action == Delete) { "delete"; } else { - str; + "edit " ++ str; } | Some(cursor_pos') => switch (cursor_pos') { @@ -62,7 +62,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { "edit " ++ str; } } - | OnOp(_) => "add " ++ str + | OnOp(_) => "edit " ++ str } } }; @@ -70,7 +70,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let display_string_of_cursor_term = (cursor_term: option(CursorInfo.cursor_term), action: Action.t): string => { switch (cursor_term) { - | None => back_delete_view(action, None, None, false) + | None => filter_with_backspace_delete(action, None, None, false) | Some(cursor_term') => switch (cursor_term') { | Exp(cursor_pos, exp) => @@ -85,10 +85,15 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Inj(_, inj_side, _) => "injection: " ++ InjSide.to_string(inj_side) | Case(_, _, _, _) => "case match" - | Parenthesized(_) => "()" + | Parenthesized(_) => "( )" | ApPalette(_, _, _, _) => "I don't know its meaning" }; - back_delete_view(action, Some(cursor_pos), Some(exp_str), false); + filter_with_backspace_delete( + action, + Some(cursor_pos), + Some(exp_str), + false, + ); | Pat(cursor_pos, pat) => let pat_str = switch (pat) { @@ -98,11 +103,16 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | NumLit(_, num) => "number: " ++ string_of_int(num) | BoolLit(_, bool_val) => "bool: " ++ string_of_bool(bool_val) | ListNil(_) => "empty list" - | Parenthesized(_) => "()" + | Parenthesized(_) => "( )" | Inj(_, inj_side, _) => "injection: " ++ InjSide.to_string(inj_side) }; - back_delete_view(action, Some(cursor_pos), Some(pat_str), false); + filter_with_backspace_delete( + action, + Some(cursor_pos), + Some(pat_str), + false, + ); | Typ(cursor_pos, typ) => let typ_str = switch (typ) { @@ -113,23 +123,28 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Parenthesized(_) => "type: (?)" | List(_) => "type: [?]" }; - back_delete_view(action, Some(cursor_pos), Some(typ_str), false); + filter_with_backspace_delete( + action, + Some(cursor_pos), + Some(typ_str), + false, + ); | ExpOp(cursor_pos, op) => - back_delete_view( + filter_with_backspace_delete( action, Some(cursor_pos), Some("operator: " ++ UHExp.string_of_operator(op)), false, ) | PatOp(cursor_pos, op) => - back_delete_view( + filter_with_backspace_delete( action, Some(cursor_pos), Some("operator: " ++ UHPat.string_of_operator(op)), false, ) | TypOp(cursor_pos, op) => - back_delete_view( + filter_with_backspace_delete( action, Some(cursor_pos), Some("operator: " ++ UHTyp.string_of_operator(op)), @@ -138,21 +153,21 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Line(cursor_pos, line_content) => switch (line_content) { | EmptyLine => - back_delete_view( + filter_with_backspace_delete( action, Some(cursor_pos), Some("empty line"), true, ) | LetLine(_, _, _) => - back_delete_view( + filter_with_backspace_delete( action, Some(cursor_pos), Some("let binding"), false, ) | ExpLine(_) => - back_delete_view( + filter_with_backspace_delete( action, Some(cursor_pos), Some("epression line"), @@ -160,7 +175,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) } | Rule(cursor_pos, _) => - back_delete_view( + filter_with_backspace_delete( action, Some(cursor_pos), Some("match rule"), @@ -169,12 +184,13 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } }; }; + let display_string_of_history_entry = (undo_history_entry: undo_history_entry): string => { let action = undo_history_entry.previous_action; let cursor_term = undo_history_entry.cursor_term; switch (action) { - | None => failwith("Imposiible, undisplayed undo history entry") + | None => failwith("Imposiible match, undisplayed undo history entry") | Some(action') => switch (action') { | MoveTo(_) @@ -184,7 +200,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | MoveToNextHole | MoveToPrevHole | UpdateApPalette(_) => - failwith("not undoable actions, will not be matched") + failwith("Imposiible match, not undoable actions will not be matched") | Delete | Backspace => display_string_of_cursor_term(cursor_term, action') | Construct(shape) => @@ -209,6 +225,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } }; }; + let history_hidden_entry_view = (group_id: int, elt_id: int, undo_history_entry: undo_history_entry) => { switch (undo_history_entry.previous_action) { @@ -228,6 +245,32 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; + let history_entry_tab_icon = + (group_id: int, has_hidden_part: bool, is_expanded: bool) => { + let icon_classes = + if (is_expanded) { + ["down-triangle", "history-tab-icon"]; + } else { + ["left-triangle", "history-tab-icon"]; + }; + if (has_hidden_part) { + /* expand icon*/ + Vdom.( + Node.div( + [ + Attr.classes(icon_classes), + Attr.on_click(_ => + inject(Update.Action.ToggleHistoryGroup(group_id)) + ), + ], + [], + ) + ); + } else { + /* no expand icon if there is no hidden part */ + Vdom.(Node.div([], [])); + }; + }; /* The entry which is always displayed*/ let history_title_entry_view = ( @@ -237,33 +280,6 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { elt_id: int, undo_history_entry: undo_history_entry, ) => { - let history_tab_icon = (group_id: int) => { - let icon_classes = - if (is_expanded) { - ["down-triangle", "history-tab-icon"]; - } else { - ["left-triangle", "history-tab-icon"]; - }; - if (has_hidden_part) { - /* expand icon*/ - Vdom.( - Node.div( - [ - Attr.classes(icon_classes), - Attr.on_click(_ => - inject(Update.Action.ToggleHistoryGroup(group_id)) - ), - ], - [], - ) - ); - } else { - /* no expand icon if there is no hidden part */ - Vdom.( - Node.div([], []) - ); - }; - }; switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) /* entry in the initial state should not be displayed */ | Some(_) => @@ -287,7 +303,11 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ), ], ), - history_tab_icon(group_id), + history_entry_tab_icon( + group_id, + has_hidden_part, + is_expanded, + ), ], ), ], @@ -298,7 +318,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let group_view = (~is_cur_group: bool, group_id: int, group: undo_history_group) => { - /* if the group containning selected history entry, it should be splited to different css styles */ + /* if the group containning selected history entry, it should be splited into different css styles */ let suc_his_classes = if (is_cur_group) { ["the-suc-history"]; @@ -359,10 +379,10 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ["hidden-history-entry"] @ prev_his_classes, ), ], - incr_map_helper( + list_map_helper_func( history_hidden_entry_view(group_id), base => base + 1, - 1, /* base elt_id is 1, because there is a title entry with elt_id=0 ahead */ + 1, /* base elt_id is 1, because there is a title entry with elt_id=0 before */ prev_entries, ), ) @@ -431,7 +451,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ suc_his_classes), ], - incr_map_helper( + list_map_helper_func( history_hidden_entry_view(group_id), base => base + 1, 1, /* base elt_id is 1, because there is a title entry with elt_id=0 ahead */ @@ -460,7 +480,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Attr.classes(["hidden-history-entry"] @ prev_his_classes), ], - incr_map_helper( + list_map_helper_func( history_hidden_entry_view(group_id), base => base + 1, List.length(suc_entries) + 2, /* base elt_id */ @@ -506,7 +526,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.( Node.div( [Attr.classes(["the-prev-history"])], - incr_map_helper( + list_map_helper_func( group_view(~is_cur_group=false), base => base + 1, group_id_base, @@ -520,7 +540,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.( Node.div( [Attr.classes(["the-suc-history"])], - incr_map_helper( + list_map_helper_func( group_view(~is_cur_group=false), base => base + 1, group_id_base, @@ -550,7 +570,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let action = ZList.prj_z(cur_group.group_entries).previous_action; switch (action) { | None => - /*if the init entry is only history entry */ + /*if the initial entry is the only history entry */ if (List.length(suc_groups) <= 1) { Vdom.( Node.div( @@ -604,6 +624,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ], ) ); + let expand_button = (all_hidden_history_expand: bool) => { let icon_classes = if (all_hidden_history_expand) { @@ -621,6 +642,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ) ); }; + let button_bar_view = (all_hidden_history_expand: bool) => Vdom.( Node.div( @@ -628,6 +650,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [undo_button, redo_button, expand_button(all_hidden_history_expand)], ) ); + Vdom.( Node.div( [Attr.classes(["panel", "context-inspector-panel"])], From 6e40072ec5e3d7482b5320ccdf9e34e2e76199a6 Mon Sep 17 00:00:00 2001 From: Zoe Date: Mon, 3 Feb 2020 11:53:04 -0500 Subject: [PATCH 53/60] final check, add some comments --- src/hazelcore/semantics/CursorInfo.re | 10 +++++----- src/hazelcore/semantics/UHExp.re | 6 +++--- src/hazelcore/semantics/UHPat.re | 2 +- src/hazelweb/Model.re | 4 ++-- src/hazelweb/UndoHistory.re | 23 ++++++++++++----------- src/hazelweb/gui/SvgShapes.re | 3 --- src/hazelweb/gui/UndoHistoryPanel.re | 11 ++++++----- 7 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index b2a8369866..21612369ec 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -87,12 +87,12 @@ type cursor_term = | ExpOp(CursorPosition.t, UHExp.operator) | PatOp(CursorPosition.t, UHPat.operator) | TypOp(CursorPosition.t, UHTyp.operator) - | Line(CursorPosition.t, UHExp.line) /* may be deleted TBD???*/ + | Line(CursorPosition.t, UHExp.line) | Rule(CursorPosition.t, UHExp.rule); // TODO refactor into variants // based on term family and shape -//[@deriving sexp] +[@deriving sexp] type t = { typed, ctx: Contexts.t, @@ -128,7 +128,7 @@ and extract_from_zexp_operand = | CaseZE(_, zexp, _, _) => extract_cursor_exp_term(zexp) | CaseZR(_, _, zrules, _) => extract_from_zrules(zrules) | CaseZA(_, _, _, ztyp) => extract_cursor_type_term(ztyp) - | ApPaletteZ(_, _, _, _) => failwith("not oprand with cursor") /*TBD???*/ + | ApPaletteZ(_, _, _, _) => failwith("ApPalette is not implemented") }; } and extract_from_zrules = (zrules: ZExp.zrules): option(cursor_term) => { @@ -205,8 +205,8 @@ let can_group_cursor_term = | (_, None) => false | (Some(cur1), Some(cur2)) => switch (cur1, cur2) { - | (Exp(_, op1), Exp(_, op2)) => UHExp.is_same_operand(op1, op2) - | (Pat(_, op1), Pat(_, op2)) => UHPat.is_same_operand(op1, op2) + | (Exp(_, op1), Exp(_, op2)) => UHExp.can_group_operand(op1, op2) + | (Pat(_, op1), Pat(_, op2)) => UHPat.can_group_operand(op1, op2) | (Line(_, line1), Line(_, line2)) => UHExp.can_group_lines(line1, line2) | (Exp(_, _), _) diff --git a/src/hazelcore/semantics/UHExp.re b/src/hazelcore/semantics/UHExp.re index 745d62ff1e..bf6f7e4823 100644 --- a/src/hazelcore/semantics/UHExp.re +++ b/src/hazelcore/semantics/UHExp.re @@ -450,7 +450,7 @@ let text_operand = ); }; -let is_same_operand = (op1: operand, op2: operand): bool => { +let can_group_operand = (op1: operand, op2: operand): bool => { switch (op1, op2) { | (EmptyHole(metavar1), EmptyHole(metavar2)) => metavar1 == metavar2 | (Var(_, _, _), Var(_, _, _)) @@ -464,8 +464,8 @@ let is_same_operand = (op1: operand, op2: operand): bool => { | (Lam(_, _, _, _), _) | (Inj(_, _, _), _) | (Case(_, _, _, _), _) - | (Parenthesized(_), _) - | (ApPalette(_, _, _, _), _) => false + | (Parenthesized(_), _) => false + | (ApPalette(_, _, _, _), _) => failwith("ApPalette is not implemented") }; }; diff --git a/src/hazelcore/semantics/UHPat.re b/src/hazelcore/semantics/UHPat.re index 1d827585c5..2c16c9e1e8 100644 --- a/src/hazelcore/semantics/UHPat.re +++ b/src/hazelcore/semantics/UHPat.re @@ -229,7 +229,7 @@ let text_operand = ); }; -let is_same_operand = (op1: operand, op2: operand): bool => { +let can_group_operand = (op1: operand, op2: operand): bool => { switch (op1, op2) { | (EmptyHole(metavar1), EmptyHole(metavar2)) => metavar1 == metavar2 | (Var(_, _, _), Var(_, _, _)) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 36d19b38d0..407fdfafba 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -239,7 +239,7 @@ let init = (): t => { CardStacks.mk_cardstacks_state(CardStacks.cardstacks); let edit_state = ZList.prj_z(ZList.prj_z(cardstacks_state).zcards).edit_state; - let undo_history_state: UndoHistory.undo_history_entry = { + let undo_history_entry: UndoHistory.undo_history_entry = { cardstacks_state, previous_action: None, cursor_term: UndoHistory.get_cursor_term(cardstacks_state), @@ -256,7 +256,7 @@ let init = (): t => { undo_history: ( [], { - group_entries: ([], undo_history_state, []), + group_entries: ([], undo_history_entry, []), is_expanded: false, is_complete: true, }, diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 81d65d9630..ca45e29ad1 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -5,14 +5,6 @@ type undo_history_entry = { cursor_term: option(cursor_term), }; -let get_cursor_term = - (cardstacks_state: CardStacks.cardstacks_state): option(cursor_term) => { - let edit_state = - ZList.prj_z(ZList.prj_z(cardstacks_state).zcards).edit_state; - let (zexp, _, _) = edit_state; - CursorInfo.extract_cursor_exp_term(zexp); -}; - type undo_history_group = { group_entries: ZList.t(undo_history_entry, undo_history_entry), is_expanded: bool, @@ -24,6 +16,14 @@ type undo_history_group = { type t = ZList.t(undo_history_group, undo_history_group); +let get_cursor_term = + (cardstacks_state: CardStacks.cardstacks_state): option(cursor_term) => { + let edit_state = + ZList.prj_z(ZList.prj_z(cardstacks_state).zcards).edit_state; + let (zexp, _, _) = edit_state; + CursorInfo.extract_cursor_exp_term(zexp); +}; + let undoable_action = (action: option(Action.t)): bool => { switch (action) { | None => @@ -32,7 +32,8 @@ let undoable_action = (action: option(Action.t)): bool => { ) | Some(action') => switch (action') { - | UpdateApPalette(_) + | UpdateApPalette(_) => + failwith("ApPalette is not implemented in undo_history") | Delete | Backspace | Construct(_) => true @@ -53,7 +54,6 @@ let in_same_history_group = | (_, None) => false | (Some(detail_action_1), Some(detail_action_2)) => switch (detail_action_1, detail_action_2) { - | (UpdateApPalette(_), UpdateApPalette(_)) | (Delete, Delete) | (Backspace, Backspace) => CursorInfo.can_group_cursor_term( @@ -70,7 +70,8 @@ let in_same_history_group = } else { false; } - | (UpdateApPalette(_), _) + | (UpdateApPalette(_), _) => + failwith("ApPalette is not implemented in undo_history") | (Delete, _) | (Backspace, _) | (Construct(_), _) => false diff --git a/src/hazelweb/gui/SvgShapes.re b/src/hazelweb/gui/SvgShapes.re index 6046619461..33746584e1 100644 --- a/src/hazelweb/gui/SvgShapes.re +++ b/src/hazelweb/gui/SvgShapes.re @@ -5,7 +5,6 @@ open Incr_dom; */ let icon = (classes, _points) => Vdom.Node.div([Vdom.Attr.classes(classes)], []); - /* TODO dunno how to get svg stuff working with incr_dom Tyxml.( Svg.svg( @@ -22,5 +21,3 @@ let left_arrow = (classes: list(string), _: unit) => icon(classes, [(0.0, 50.0), (100.0, 0.0), (100.0, 100.0)]); let right_arrow = (classes: list(string), _: unit) => icon(classes, [(0.0, 0.0), (100.0, 50.0), (0.0, 100.0)]); -/* let down_arrow = (classes: list(string), _: unit) => - icon(classes, [(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)]); */ diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index ebf36c9309..13b3cefd5e 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -86,7 +86,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { "injection: " ++ InjSide.to_string(inj_side) | Case(_, _, _, _) => "case match" | Parenthesized(_) => "( )" - | ApPalette(_, _, _, _) => "I don't know its meaning" + | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") }; filter_with_backspace_delete( action, @@ -198,9 +198,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | MoveLeft | MoveRight | MoveToNextHole - | MoveToPrevHole - | UpdateApPalette(_) => - failwith("Imposiible match, not undoable actions will not be matched") + | MoveToPrevHole => + failwith("Imposiible match, noone of undoable actions will be matched") + | UpdateApPalette(_) => failwith("ApPalette is not implemented") | Delete | Backspace => display_string_of_cursor_term(cursor_term, action') | Construct(shape) => @@ -220,7 +220,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | SCase => "add case" | SChar(_) | SOp(_) => display_string_of_cursor_term(cursor_term, action') - | SApPalette(_) => "appalette?" + | SApPalette(_) => failwith("ApPalette is not implemented") } } }; @@ -271,6 +271,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.(Node.div([], [])); }; }; + /* The entry which is always displayed*/ let history_title_entry_view = ( From bb2538062af110bbd6fa0caee4dddd5e0dd7d400 Mon Sep 17 00:00:00 2001 From: Cyrus Omar Date: Tue, 4 Feb 2020 14:18:18 -0500 Subject: [PATCH 54/60] formatting --- src/hazelweb/gui/UndoHistoryPanel.re | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 13b3cefd5e..7f450ae0f0 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -199,7 +199,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | MoveRight | MoveToNextHole | MoveToPrevHole => - failwith("Imposiible match, noone of undoable actions will be matched") + failwith( + "Imposiible match, noone of undoable actions will be matched", + ) | UpdateApPalette(_) => failwith("ApPalette is not implemented") | Delete | Backspace => display_string_of_cursor_term(cursor_term, action') @@ -271,7 +273,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.(Node.div([], [])); }; }; - + /* The entry which is always displayed*/ let history_title_entry_view = ( From f2b332ba205c8aa0735aa0adc749d98560595903 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 4 Feb 2020 15:13:34 -0500 Subject: [PATCH 55/60] construct keyword --- src/hazelweb/Model.re | 4 +- src/hazelweb/UndoHistory.re | 19 +++++---- src/hazelweb/gui/UndoHistoryPanel.re | 64 +++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/hazelweb/Model.re b/src/hazelweb/Model.re index 407fdfafba..88a230e43e 100644 --- a/src/hazelweb/Model.re +++ b/src/hazelweb/Model.re @@ -242,7 +242,8 @@ let init = (): t => { let undo_history_entry: UndoHistory.undo_history_entry = { cardstacks_state, previous_action: None, - cursor_term: UndoHistory.get_cursor_term(cardstacks_state), + previous_cursor_term: None, + current_cursor_term: UndoHistory.get_cursor_term(cardstacks_state), }; let compute_results = init_compute_results; { @@ -287,6 +288,7 @@ let perform_edit_action = (model: t, a: Action.t): t => { let new_history = UndoHistory.push_edit_state( model.undo_history, + model.cardstacks_state, new_model.cardstacks_state, Some(a), ); diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index ca45e29ad1..8338bb556c 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -2,7 +2,8 @@ type cursor_term = CursorInfo.cursor_term; type undo_history_entry = { cardstacks_state: CardStacks.cardstacks_state, previous_action: option(Action.t), - cursor_term: option(cursor_term), + previous_cursor_term: option(cursor_term), + current_cursor_term: option(cursor_term), }; type undo_history_group = { @@ -57,15 +58,15 @@ let in_same_history_group = | (Delete, Delete) | (Backspace, Backspace) => CursorInfo.can_group_cursor_term( - entry_1.cursor_term, - entry_2.cursor_term, + entry_1.current_cursor_term, + entry_2.current_cursor_term, ) | (Construct(shape_1), Construct(shape_2)) => /* if shapes are similar, then continue to check if they have similar cursor_term */ if (Action.can_group_shape(shape_1, shape_2)) { CursorInfo.can_group_cursor_term( - entry_1.cursor_term, - entry_2.cursor_term, + entry_1.current_cursor_term, + entry_2.current_cursor_term, ); } else { false; @@ -91,7 +92,8 @@ let in_same_history_group = let push_edit_state = ( undo_history: t, - cardstacks_state: CardStacks.cardstacks_state, + prev_cardstacks_state: CardStacks.cardstacks_state, + cur_cardstacks_state: CardStacks.cardstacks_state, action: option(Action.t), ) : t => { @@ -99,9 +101,10 @@ let push_edit_state = let cur_entry = ZList.prj_z(cur_group.group_entries); if (undoable_action(action)) { let new_entry = { - cardstacks_state, + cardstacks_state: cur_cardstacks_state, previous_action: action, - cursor_term: get_cursor_term(cardstacks_state), + previous_cursor_term: get_cursor_term(prev_cardstacks_state), + current_cursor_term: get_cursor_term(cur_cardstacks_state), }; if (!cur_group.is_complete && in_same_history_group(cur_entry, new_entry)) { /* group the new entry into the current group */ diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 13b3cefd5e..87c2a8dc35 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -188,7 +188,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let display_string_of_history_entry = (undo_history_entry: undo_history_entry): string => { let action = undo_history_entry.previous_action; - let cursor_term = undo_history_entry.cursor_term; + let prev_cursor_term = undo_history_entry.previous_cursor_term; + let cur_cursor_term = undo_history_entry.current_cursor_term; switch (action) { | None => failwith("Imposiible match, undisplayed undo history entry") | Some(action') => @@ -199,10 +200,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | MoveRight | MoveToNextHole | MoveToPrevHole => - failwith("Imposiible match, noone of undoable actions will be matched") + failwith( + "Imposiible match, noone of undoable actions will be matched", + ) | UpdateApPalette(_) => failwith("ApPalette is not implemented") | Delete - | Backspace => display_string_of_cursor_term(cursor_term, action') + | Backspace => display_string_of_cursor_term(cur_cursor_term, action') | Construct(shape) => switch (shape) { | SParenthesized => "add ( )" @@ -218,8 +221,57 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | SLet => "add let binding" | SLine => "add new lines" | SCase => "add case" - | SChar(_) - | SOp(_) => display_string_of_cursor_term(cursor_term, action') + | SChar(_) => display_string_of_cursor_term(cur_cursor_term, action') + | SOp(shape) => + switch (shape) { + | SMinus + | SPlus + | STimes + | SLessThan + | SGreaterThan + | SEquals + | SComma + | SArrow + | SVBar + | SCons + | SAnd + | SOr => display_string_of_cursor_term(cur_cursor_term, action') + | SSpace => + switch (prev_cursor_term) { + | None => display_string_of_cursor_term(cur_cursor_term, action') + | Some(cursor_term') => + switch (cursor_term') { + | Exp(_, uexp_operand) => + switch (uexp_operand) { + | Var(_, InVarHole(Keyword(k), _), _) => + switch (k) { + | Let => "construct let binding" + | Case => "construct case match" + } + | EmptyHole(_) + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) + | Lam(_, _, _, _) + | Inj(_, _, _) + | Case(_, _, _, _) + | Parenthesized(_) => + display_string_of_cursor_term(cur_cursor_term, action') + | ApPalette(_, _, _, _) => + failwith("ApPalette is not implemented") + } + | Pat(_, _) + | Typ(_, _) + | ExpOp(_, _) + | PatOp(_, _) + | TypOp(_, _) + | Line(_, _) + | Rule(_, _) => + display_string_of_cursor_term(cur_cursor_term, action') + } + } + } | SApPalette(_) => failwith("ApPalette is not implemented") } } @@ -271,7 +323,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { Vdom.(Node.div([], [])); }; }; - + /* The entry which is always displayed*/ let history_title_entry_view = ( From 34bb4fac94a4ba101a3dc6d3f836b3bc88662704 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 4 Feb 2020 18:45:59 -0500 Subject: [PATCH 56/60] display delete content --- src/hazelcore/semantics/CursorInfo.re | 17 +- src/hazelcore/semantics/UHExp.re | 15 ++ src/hazelcore/semantics/UHPat.re | 13 ++ src/hazelcore/semantics/UHTyp.re | 11 + src/hazelweb/UndoHistory.re | 33 +-- src/hazelweb/gui/UndoHistoryPanel.re | 287 +++++++++++--------------- 6 files changed, 189 insertions(+), 187 deletions(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index 21612369ec..1d3f740449 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -220,7 +220,22 @@ let can_group_cursor_term = } }; }; - +let is_hole = (cursor_term: option(cursor_term)): bool => { + switch (cursor_term) { + | None => false + | Some(cursor_term') => + switch (cursor_term') { + | Exp(_, exp) => UHExp.operand_is_hole(exp) + | Pat(_, pat) => UHPat.operand_is_hole(pat) + | Typ(_, typ) => UHTyp.operand_is_hole(typ) + | ExpOp(_, _) + | PatOp(_, _) + | TypOp(_, _) + | Line(_, _) + | Rule(_, _) => false + } + }; +}; let mk = (~uses=?, typed, ctx) => {typed, ctx, uses}; module Typ = { diff --git a/src/hazelcore/semantics/UHExp.re b/src/hazelcore/semantics/UHExp.re index bf6f7e4823..24a6750fb0 100644 --- a/src/hazelcore/semantics/UHExp.re +++ b/src/hazelcore/semantics/UHExp.re @@ -477,3 +477,18 @@ let can_group_lines = (line1: line, line2: line): bool => { | (ExpLine(_), _) => false }; }; + +let operand_is_hole = (op: operand): bool => { + switch (op) { + | EmptyHole(_) => true + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) + | Lam(_, _, _, _) + | Inj(_, _, _) + | Case(_, _, _, _) + | Parenthesized(_) => false + | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") + }; +}; diff --git a/src/hazelcore/semantics/UHPat.re b/src/hazelcore/semantics/UHPat.re index 2c16c9e1e8..175a587951 100644 --- a/src/hazelcore/semantics/UHPat.re +++ b/src/hazelcore/semantics/UHPat.re @@ -245,3 +245,16 @@ let can_group_operand = (op1: operand, op2: operand): bool => { | (Inj(_, _, _), _) => false }; }; + +let operand_is_hole = (op: operand): bool => { + switch (op) { + | EmptyHole(_) => true + | Wild(_) + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) + | Parenthesized(_) + | Inj(_, _, _) => false + }; +}; diff --git a/src/hazelcore/semantics/UHTyp.re b/src/hazelcore/semantics/UHTyp.re index 7a39e8cd2c..1e08ff722c 100644 --- a/src/hazelcore/semantics/UHTyp.re +++ b/src/hazelcore/semantics/UHTyp.re @@ -148,3 +148,14 @@ let child_indices_opseq: opseq => list(int) = fun | OpSeq(_, seq) => seq |> Seq.length |> ListUtil.range; let child_indices = child_indices_opseq; + +let operand_is_hole = (op: operand) => { + switch (op) { + | Hole => true + | Unit + | Num + | Bool + | Parenthesized(_) + | List(_) => false + }; +}; diff --git a/src/hazelweb/UndoHistory.re b/src/hazelweb/UndoHistory.re index 8338bb556c..70902c3804 100644 --- a/src/hazelweb/UndoHistory.re +++ b/src/hazelweb/UndoHistory.re @@ -49,8 +49,8 @@ let undoable_action = (action: option(Action.t)): bool => { }; let in_same_history_group = - (entry_1: undo_history_entry, entry_2: undo_history_entry): bool => { - switch (entry_1.previous_action, entry_2.previous_action) { + (~prev_entry: undo_history_entry, ~cur_entry: undo_history_entry): bool => { + switch (prev_entry.previous_action, cur_entry.previous_action) { | (None, _) | (_, None) => false | (Some(detail_action_1), Some(detail_action_2)) => @@ -58,15 +58,15 @@ let in_same_history_group = | (Delete, Delete) | (Backspace, Backspace) => CursorInfo.can_group_cursor_term( - entry_1.current_cursor_term, - entry_2.current_cursor_term, + prev_entry.current_cursor_term, + cur_entry.current_cursor_term, ) | (Construct(shape_1), Construct(shape_2)) => /* if shapes are similar, then continue to check if they have similar cursor_term */ if (Action.can_group_shape(shape_1, shape_2)) { CursorInfo.can_group_cursor_term( - entry_1.current_cursor_term, - entry_2.current_cursor_term, + prev_entry.current_cursor_term, + cur_entry.current_cursor_term, ); } else { false; @@ -97,23 +97,24 @@ let push_edit_state = action: option(Action.t), ) : t => { - let cur_group = ZList.prj_z(undo_history); - let cur_entry = ZList.prj_z(cur_group.group_entries); + let prev_group = ZList.prj_z(undo_history); + let prev_entry = ZList.prj_z(prev_group.group_entries); if (undoable_action(action)) { - let new_entry = { + let cur_entry = { cardstacks_state: cur_cardstacks_state, previous_action: action, previous_cursor_term: get_cursor_term(prev_cardstacks_state), current_cursor_term: get_cursor_term(cur_cardstacks_state), }; - if (!cur_group.is_complete && in_same_history_group(cur_entry, new_entry)) { + if (!prev_group.is_complete + && in_same_history_group(~prev_entry, ~cur_entry)) { /* group the new entry into the current group */ let group_entries_after_push = ( [], - new_entry, + cur_entry, [ - ZList.prj_z(cur_group.group_entries), - ...ZList.prj_suffix(cur_group.group_entries), + ZList.prj_z(prev_group.group_entries), + ...ZList.prj_suffix(prev_group.group_entries), ], ); ( @@ -128,7 +129,7 @@ let push_edit_state = } else { /* start a new group */ let new_group = { - group_entries: ([], new_entry, []), + group_entries: ([], cur_entry, []), is_expanded: false, is_complete: false, }; @@ -141,8 +142,8 @@ let push_edit_state = } else { /* if any cursor-moving action interupts the current edit, the current group becomes complete. */ - let cur_group' = {...cur_group, is_complete: true}; - ZList.replace_z(cur_group', undo_history); + let prev_group' = {...prev_group, is_complete: true}; + ZList.replace_z(prev_group', undo_history); }; }; diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 87c2a8dc35..b22033619d 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -18,178 +18,90 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { ] }; }; - /* special display case for backspace and delete */ - let filter_with_backspace_delete = - ( - action: Action.t, - cursor_pos: option(CursorPosition.t), - otherwise_str: option(string), - is_empty_line: bool, - ) - : string => { - switch (otherwise_str) { - | None => failwith("Imposiible match, undisplayed undo history entry") - | Some(str) => - switch (cursor_pos) { - | None => - if (action == Backspace) { - "backspace"; - } else if (action == Delete) { - "delete"; - } else { - "edit " ++ str; - } - | Some(cursor_pos') => - switch (cursor_pos') { - | OnText(_) => - if (is_empty_line) { - "clear the line"; - } else { - "edit " ++ str; - } - | OnDelim(_, side) => - switch (side) { - | Before => - if (action == Delete) { - "select " ++ str; - } else { - "edit " ++ str; - } - | After => - if (action == Backspace) { - "select " ++ str; - } else { - "edit " ++ str; - } - } - | OnOp(_) => "edit " ++ str - } - } + + let exp_str = (exp: UHExp.operand): string => { + switch (exp) { + | EmptyHole(meta_var) => "hole: " ++ string_of_int(meta_var) + | Var(_, _, var_str) => "var: " ++ var_str + | NumLit(_, num) => "number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "empty list" + | Lam(_, _, _, _) => "lambada function" + | Inj(_, inj_side, _) => "injection: " ++ InjSide.to_string(inj_side) + | Case(_, _, _, _) => "case match" + | Parenthesized(_) => "( )" + | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") + }; + }; + + let pat_str = (pat: UHPat.operand): string => { + switch (pat) { + | EmptyHole(meta_var) => "hole: " ++ string_of_int(meta_var) + | Wild(_) => "I don't know its meaning" + | Var(_, _, var_str) => "var: " ++ var_str + | NumLit(_, num) => "number: " ++ string_of_int(num) + | BoolLit(_, bool_val) => "bool: " ++ string_of_bool(bool_val) + | ListNil(_) => "empty list" + | Parenthesized(_) => "( )" + | Inj(_, inj_side, _) => "injection: " ++ InjSide.to_string(inj_side) + }; + }; + + let typ_str = (typ: UHTyp.operand): string => { + switch (typ) { + | Hole => "type: Hole" + | Unit => "type: Unit" + | Num => "type: Num" + | Bool => "type: Bool" + | Parenthesized(_) => "type: (?)" + | List(_) => "type: [?]" }; }; - let display_string_of_cursor_term = - (cursor_term: option(CursorInfo.cursor_term), action: Action.t): string => { + + let display_string_of_cursor = + (cursor_term: option(CursorInfo.cursor_term)) => { switch (cursor_term) { - | None => filter_with_backspace_delete(action, None, None, false) + | None => + failwith("Imposiible match, the inital state will not be displayed") | Some(cursor_term') => switch (cursor_term') { - | Exp(cursor_pos, exp) => - let exp_str = - switch (exp) { - | EmptyHole(meta_var) => "hole: " ++ string_of_int(meta_var) - | Var(_, _, var_str) => "var: " ++ var_str - | NumLit(_, num) => "number: " ++ string_of_int(num) - | BoolLit(_, bool_val) => "bool: " ++ string_of_bool(bool_val) - | ListNil(_) => "empty list" - | Lam(_, _, _, _) => "lambada function" - | Inj(_, inj_side, _) => - "injection: " ++ InjSide.to_string(inj_side) - | Case(_, _, _, _) => "case match" - | Parenthesized(_) => "( )" - | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") - }; - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some(exp_str), - false, - ); - | Pat(cursor_pos, pat) => - let pat_str = - switch (pat) { - | EmptyHole(meta_var) => "hole: " ++ string_of_int(meta_var) - | Wild(_) => "I don't know its meaning" - | Var(_, _, var_str) => "var: " ++ var_str - | NumLit(_, num) => "number: " ++ string_of_int(num) - | BoolLit(_, bool_val) => "bool: " ++ string_of_bool(bool_val) - | ListNil(_) => "empty list" - | Parenthesized(_) => "( )" - | Inj(_, inj_side, _) => - "injection: " ++ InjSide.to_string(inj_side) - }; - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some(pat_str), - false, - ); - | Typ(cursor_pos, typ) => - let typ_str = - switch (typ) { - | Hole => "type: Hole" - | Unit => "type: Unit" - | Num => "type: Num" - | Bool => "type: Bool" - | Parenthesized(_) => "type: (?)" - | List(_) => "type: [?]" - }; - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some(typ_str), - false, - ); - | ExpOp(cursor_pos, op) => - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some("operator: " ++ UHExp.string_of_operator(op)), - false, - ) - | PatOp(cursor_pos, op) => - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some("operator: " ++ UHPat.string_of_operator(op)), - false, - ) - | TypOp(cursor_pos, op) => - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some("operator: " ++ UHTyp.string_of_operator(op)), - false, - ) - | Line(cursor_pos, line_content) => + | Exp(_, exp) => exp_str(exp) + | Pat(_, pat) => pat_str(pat) + | Typ(_, typ) => typ_str(typ) + | ExpOp(_, op) => UHExp.string_of_operator(op) + | PatOp(_, op) => UHPat.string_of_operator(op) + | TypOp(_, op) => UHTyp.string_of_operator(op) + | Line(_, line_content) => switch (line_content) { - | EmptyLine => - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some("empty line"), - true, - ) - | LetLine(_, _, _) => - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some("let binding"), - false, - ) - | ExpLine(_) => - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some("epression line"), - false, - ) + | EmptyLine => "empty line" + | LetLine(_, _, _) => "let binding" + | ExpLine(_) => "expression line" } - | Rule(cursor_pos, _) => - filter_with_backspace_delete( - action, - Some(cursor_pos), - Some("match rule"), - false, - ) + | Rule(_, _) => "match rule" } }; }; - let display_string_of_history_entry = (undo_history_entry: undo_history_entry): string => { let action = undo_history_entry.previous_action; let prev_cursor_term = undo_history_entry.previous_cursor_term; let cur_cursor_term = undo_history_entry.current_cursor_term; + let prev_cursor_pos = + switch (prev_cursor_term) { + | None => + failwith("Imposiible match, the inital state will not be displayed") + | Some(prev_cursor) => + switch (prev_cursor) { + | Exp(cursor_pos, _) + | Pat(cursor_pos, _) + | Typ(cursor_pos, _) + | ExpOp(cursor_pos, _) + | PatOp(cursor_pos, _) + | TypOp(cursor_pos, _) + | Line(cursor_pos, _) + | Rule(cursor_pos, _) => cursor_pos + } + }; switch (action) { | None => failwith("Imposiible match, undisplayed undo history entry") | Some(action') => @@ -200,12 +112,46 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | MoveRight | MoveToNextHole | MoveToPrevHole => - failwith( - "Imposiible match, noone of undoable actions will be matched", - ) + failwith("Imposiible match, none of undoable actions will be matched") | UpdateApPalette(_) => failwith("ApPalette is not implemented") - | Delete - | Backspace => display_string_of_cursor_term(cur_cursor_term, action') + | Delete => + switch (prev_cursor_pos) { + | OnText(_) => "edit " ++ display_string_of_cursor(cur_cursor_term) + | OnDelim(_, side) => + switch (side) { + | Before => + if (CursorInfo.is_hole(prev_cursor_term)) { + "move the cursor in " ++ display_string_of_cursor(prev_cursor_term); + } else { + "clear " ++ display_string_of_cursor(prev_cursor_term); + } + | After => "edit " ++ display_string_of_cursor(cur_cursor_term) + } + | OnOp(side) => + switch (side) { + | Before => "clear " ++ display_string_of_cursor(prev_cursor_term) + | After => "edit " ++ display_string_of_cursor(cur_cursor_term) + } + } + | Backspace => + switch (prev_cursor_pos) { + | OnText(_) => "edit " ++ display_string_of_cursor(cur_cursor_term) + | OnDelim(_, side) => + switch (side) { + | Before => "edit " ++ display_string_of_cursor(cur_cursor_term) + | After => + if (CursorInfo.is_hole(prev_cursor_term)) { + "move the cursor in " ++ display_string_of_cursor(prev_cursor_term); + } else { + "clear " ++ display_string_of_cursor(prev_cursor_term); + } + } + | OnOp(side) => + switch (side) { + | Before => "edit " ++ display_string_of_cursor(cur_cursor_term) + | After => "clear " ++ display_string_of_cursor(prev_cursor_term) + } + } | Construct(shape) => switch (shape) { | SParenthesized => "add ( )" @@ -221,9 +167,9 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | SLet => "add let binding" | SLine => "add new lines" | SCase => "add case" - | SChar(_) => display_string_of_cursor_term(cur_cursor_term, action') - | SOp(shape) => - switch (shape) { + | SChar(_) => "edit " ++ display_string_of_cursor(cur_cursor_term) + | SOp(shape') => + switch (shape') { | SMinus | SPlus | STimes @@ -235,10 +181,11 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | SVBar | SCons | SAnd - | SOr => display_string_of_cursor_term(cur_cursor_term, action') + | SOr => "edit " ++ display_string_of_cursor(cur_cursor_term) | SSpace => switch (prev_cursor_term) { - | None => display_string_of_cursor_term(cur_cursor_term, action') + | None => + failwith("Imposiible match, undisplayed undo history entry") | Some(cursor_term') => switch (cursor_term') { | Exp(_, uexp_operand) => @@ -257,7 +204,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Inj(_, _, _) | Case(_, _, _, _) | Parenthesized(_) => - display_string_of_cursor_term(cur_cursor_term, action') + "edit " ++ display_string_of_cursor(cur_cursor_term) | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") } @@ -268,16 +215,16 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | TypOp(_, _) | Line(_, _) | Rule(_, _) => - display_string_of_cursor_term(cur_cursor_term, action') + "edit " ++ display_string_of_cursor(cur_cursor_term) } } } + | SApPalette(_) => failwith("ApPalette is not implemented") } } }; }; - let history_hidden_entry_view = (group_id: int, elt_id: int, undo_history_entry: undo_history_entry) => { switch (undo_history_entry.previous_action) { From 9bec2340321c4b971b624e58cd5f489e5aa423e6 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 4 Feb 2020 19:22:38 -0500 Subject: [PATCH 57/60] change dom div --- src/hazelweb/gui/UndoHistoryPanel.re | 146 +++++++++++++++++++-------- 1 file changed, 104 insertions(+), 42 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index b22033619d..cc3e9a1e52 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -81,7 +81,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } }; }; - let display_string_of_history_entry = + let string_of_history_entry = (undo_history_entry: undo_history_entry): string => { let action = undo_history_entry.previous_action; let prev_cursor_term = undo_history_entry.previous_cursor_term; @@ -121,7 +121,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { switch (side) { | Before => if (CursorInfo.is_hole(prev_cursor_term)) { - "move the cursor in " ++ display_string_of_cursor(prev_cursor_term); + "move the cursor in " + ++ display_string_of_cursor(prev_cursor_term); } else { "clear " ++ display_string_of_cursor(prev_cursor_term); } @@ -141,7 +142,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Before => "edit " ++ display_string_of_cursor(cur_cursor_term) | After => if (CursorInfo.is_hole(prev_cursor_term)) { - "move the cursor in " ++ display_string_of_cursor(prev_cursor_term); + "move the cursor in " + ++ display_string_of_cursor(prev_cursor_term); } else { "clear " ++ display_string_of_cursor(prev_cursor_term); } @@ -225,22 +227,82 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } }; }; + + let display_string_of_history_entry = + (undo_history_entry: undo_history_entry): option(string) => { + let cur_cursor_term = undo_history_entry.current_cursor_term; + switch (cur_cursor_term) { + | None => failwith("Imposiible match, undisplayed undo history entry") + | Some(cursor_term) => + switch (cursor_term) { + | Exp(cursor_pos, exp) => + switch (exp) { + | EmptyHole(_) + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) => Some(string_of_history_entry(undo_history_entry)) + | Case(_, _, _, _) + | Lam(_, _, _, _) + | Parenthesized(_) + | Inj(_, _, _) => + switch (cursor_pos) { + | OnDelim(_, _) => None + | OnOp(_) + | OnText(_) => Some(string_of_history_entry(undo_history_entry)) + } + | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") + } + | Pat(cursor_pos, pat) => + switch (pat) { + | EmptyHole(_) + | Wild(_) + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) => Some(string_of_history_entry(undo_history_entry)) + | Parenthesized(_) + | Inj(_, _, _) => + switch (cursor_pos) { + | OnDelim(_, _) => None + | OnOp(_) + | OnText(_) => Some(string_of_history_entry(undo_history_entry)) + } + } + | Typ(_, _) + | ExpOp(_, _) + | PatOp(_, _) + | TypOp(_, _) => Some(string_of_history_entry(undo_history_entry)) + | Line(cursor_pos, _) + | Rule(cursor_pos, _) => + switch (cursor_pos) { + | OnDelim(_, _) => None + | OnOp(_) + | OnText(_) => Some(string_of_history_entry(undo_history_entry)) + } + } + }; + }; let history_hidden_entry_view = (group_id: int, elt_id: int, undo_history_entry: undo_history_entry) => { switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) /* entry in initial state should not be displayed */ | Some(_) => - Vdom.( - Node.div( - [ - Attr.classes(["the-hidden-history-entry"]), - Attr.on_click(_ => - inject(Update.Action.ShiftHistory(group_id, elt_id)) - ), - ], - [Node.text(display_string_of_history_entry(undo_history_entry))], + switch (display_string_of_history_entry(undo_history_entry)) { + | None => Vdom.(Node.div([], [])) + | Some(str) => + Vdom.( + Node.div( + [ + Attr.classes(["the-hidden-history-entry"]), + Attr.on_click(_ => + inject(Update.Action.ShiftHistory(group_id, elt_id)) + ), + ], + [Node.text(str)], + ) ) - ) + } }; }; @@ -283,36 +345,36 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { switch (undo_history_entry.previous_action) { | None => Vdom.(Node.div([], [])) /* entry in the initial state should not be displayed */ | Some(_) => - Vdom.( - Node.div( - [Attr.classes(["the-history-title"])], - [ - Node.div( - [Attr.classes(["the-history-title-entry"])], - [ - Node.span( - [ - Attr.classes(["the-history-title-txt"]), - Attr.on_click(_ => - inject(Update.Action.ShiftHistory(group_id, elt_id)) - ), - ], - [ - Node.text( - display_string_of_history_entry(undo_history_entry), - ), - ], - ), - history_entry_tab_icon( - group_id, - has_hidden_part, - is_expanded, - ), - ], - ), - ], + switch (display_string_of_history_entry(undo_history_entry)) { + | None => Vdom.(Node.div([], [])) + | Some(str) => + Vdom.( + Node.div( + [Attr.classes(["the-history-title"])], + [ + Node.div( + [Attr.classes(["the-history-title-entry"])], + [ + Node.span( + [ + Attr.classes(["the-history-title-txt"]), + Attr.on_click(_ => + inject(Update.Action.ShiftHistory(group_id, elt_id)) + ), + ], + [Node.text(str)], + ), + history_entry_tab_icon( + group_id, + has_hidden_part, + is_expanded, + ), + ], + ), + ], + ) ) - ) + } }; }; From 3375dcb52a560038bdd6ef1c4e2d939c8f67eba4 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 4 Feb 2020 21:03:32 -0500 Subject: [PATCH 58/60] fix some display case --- src/hazelcore/semantics/CursorInfo.re | 13 ++ src/hazelweb/gui/UndoHistoryPanel.re | 279 ++++++++++++++++---------- 2 files changed, 182 insertions(+), 110 deletions(-) diff --git a/src/hazelcore/semantics/CursorInfo.re b/src/hazelcore/semantics/CursorInfo.re index 1d3f740449..58d6a8f233 100644 --- a/src/hazelcore/semantics/CursorInfo.re +++ b/src/hazelcore/semantics/CursorInfo.re @@ -236,6 +236,19 @@ let is_hole = (cursor_term: option(cursor_term)): bool => { } }; }; + +let get_cursor_pos = (cursor_term: cursor_term) => { + switch (cursor_term) { + | Exp(cursor_pos, _) + | Pat(cursor_pos, _) + | Typ(cursor_pos, _) + | ExpOp(cursor_pos, _) + | PatOp(cursor_pos, _) + | TypOp(cursor_pos, _) + | Line(cursor_pos, _) + | Rule(cursor_pos, _) => cursor_pos + }; +}; let mk = (~uses=?, typed, ctx) => {typed, ctx, uses}; module Typ = { diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index cc3e9a1e52..080b3e9b98 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -57,7 +57,6 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | List(_) => "type: [?]" }; }; - let display_string_of_cursor = (cursor_term: option(CursorInfo.cursor_term)) => { switch (cursor_term) { @@ -81,29 +80,58 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } }; }; + let can_delete_typ_inf = (cursor_term: option(CursorInfo.cursor_term)) => { + switch (cursor_term) { + | None => + failwith("Imposiible match, the inital state will not be displayed") + | Some(cursor_term') => + switch (cursor_term') { + | Exp(_, exp) => + switch (exp) { + | EmptyHole(_) + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) + | Inj(_, _, _) + | Case(_, _, _, _) + | Parenthesized(_) => false + | Lam(_, _, _, _) => true + | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") + } + | Pat(_, _) + | Typ(_, _) + | ExpOp(_, _) + | PatOp(_, _) + | TypOp(_, _) => false + | Line(_, line_content) => + switch (line_content) { + | EmptyLine + | ExpLine(_) => false + | LetLine(_, _, _) => true + } + | Rule(_, _) => false + } + }; + }; let string_of_history_entry = - (undo_history_entry: undo_history_entry): string => { + (undo_history_entry: undo_history_entry): option(string) => { let action = undo_history_entry.previous_action; let prev_cursor_term = undo_history_entry.previous_cursor_term; let cur_cursor_term = undo_history_entry.current_cursor_term; + let cur_cursor_pos = + switch (cur_cursor_term) { + | None => failwith("Imposiible match, cur_cursor is never None") + | Some(cursor_term') => CursorInfo.get_cursor_pos(cursor_term') + }; let prev_cursor_pos = switch (prev_cursor_term) { | None => failwith("Imposiible match, the inital state will not be displayed") - | Some(prev_cursor) => - switch (prev_cursor) { - | Exp(cursor_pos, _) - | Pat(cursor_pos, _) - | Typ(cursor_pos, _) - | ExpOp(cursor_pos, _) - | PatOp(cursor_pos, _) - | TypOp(cursor_pos, _) - | Line(cursor_pos, _) - | Rule(cursor_pos, _) => cursor_pos - } + | Some(cursor_term') => CursorInfo.get_cursor_pos(cursor_term') }; switch (action) { - | None => failwith("Imposiible match, undisplayed undo history entry") + | None => None | Some(action') => switch (action') { | MoveTo(_) @@ -116,60 +144,98 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | UpdateApPalette(_) => failwith("ApPalette is not implemented") | Delete => switch (prev_cursor_pos) { - | OnText(_) => "edit " ++ display_string_of_cursor(cur_cursor_term) - | OnDelim(_, side) => + | OnText(_) => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) + | OnDelim(num, side) => switch (side) { | Before => if (CursorInfo.is_hole(prev_cursor_term)) { - "move the cursor in " - ++ display_string_of_cursor(prev_cursor_term); + None; + } else if (num == 1 && can_delete_typ_inf(prev_cursor_term)) { + Some( + "clear type inference of " + ++ display_string_of_cursor(prev_cursor_term), + ); } else { - "clear " ++ display_string_of_cursor(prev_cursor_term); + Some(" clear " ++ display_string_of_cursor(prev_cursor_term)); + } + | After => + switch (cur_cursor_pos) { + | OnText(_) => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) + | OnOp(side) + | OnDelim(_, side) => + switch (side) { + | Before => None + | After => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) + } } - | After => "edit " ++ display_string_of_cursor(cur_cursor_term) } | OnOp(side) => switch (side) { - | Before => "clear " ++ display_string_of_cursor(prev_cursor_term) - | After => "edit " ++ display_string_of_cursor(cur_cursor_term) + | Before => + Some("clear " ++ display_string_of_cursor(prev_cursor_term)) + | After => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) } } | Backspace => switch (prev_cursor_pos) { - | OnText(_) => "edit " ++ display_string_of_cursor(cur_cursor_term) - | OnDelim(_, side) => + | OnText(_) => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) + | OnDelim(num, side) => switch (side) { - | Before => "edit " ++ display_string_of_cursor(cur_cursor_term) + | Before => + switch (cur_cursor_pos) { + | OnText(_) => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) + | OnOp(side) + | OnDelim(_, side) => + switch (side) { + | Before => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) + | After => None + } + } + | After => if (CursorInfo.is_hole(prev_cursor_term)) { - "move the cursor in " - ++ display_string_of_cursor(prev_cursor_term); + None; + } else if (num == 1 && can_delete_typ_inf(prev_cursor_term)) { + Some( + "clear type inference of " + ++ display_string_of_cursor(prev_cursor_term), + ); } else { - "clear " ++ display_string_of_cursor(prev_cursor_term); + Some(" clear " ++ display_string_of_cursor(prev_cursor_term)); } } | OnOp(side) => switch (side) { - | Before => "edit " ++ display_string_of_cursor(cur_cursor_term) - | After => "clear " ++ display_string_of_cursor(prev_cursor_term) + | Before => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) + | After => + Some("clear " ++ display_string_of_cursor(prev_cursor_term)) } } | Construct(shape) => switch (shape) { - | SParenthesized => "add ( )" - | SList => "type List" - | SAsc => "type inference" - | SLam => "add lambada" - | SListNil => "add [ ]" + | SParenthesized => Some("add ( )") + | SList => Some("type List") + | SAsc => Some("type inference") + | SLam => Some("add lambada") + | SListNil => Some("add [ ]") | SInj(direction) => switch (direction) { - | L => "inject left" - | R => "inject right" + | L => Some("inject left") + | R => Some("inject right") } - | SLet => "add let binding" - | SLine => "add new lines" - | SCase => "add case" - | SChar(_) => "edit " ++ display_string_of_cursor(cur_cursor_term) + | SLet => Some("add let binding") + | SLine => Some("add new lines") + | SCase => Some("add case") + | SChar(_) => + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) | SOp(shape') => switch (shape') { | SMinus @@ -183,7 +249,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | SVBar | SCons | SAnd - | SOr => "edit " ++ display_string_of_cursor(cur_cursor_term) + | SOr => Some("edit " ++ display_string_of_cursor(cur_cursor_term)) | SSpace => switch (prev_cursor_term) { | None => @@ -194,8 +260,8 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { switch (uexp_operand) { | Var(_, InVarHole(Keyword(k), _), _) => switch (k) { - | Let => "construct let binding" - | Case => "construct case match" + | Let => Some("construct let binding") + | Case => Some("construct case match") } | EmptyHole(_) | Var(_, _, _) @@ -206,7 +272,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Inj(_, _, _) | Case(_, _, _, _) | Parenthesized(_) => - "edit " ++ display_string_of_cursor(cur_cursor_term) + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") } @@ -217,7 +283,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | TypOp(_, _) | Line(_, _) | Rule(_, _) => - "edit " ++ display_string_of_cursor(cur_cursor_term) + Some("edit " ++ display_string_of_cursor(cur_cursor_term)) } } } @@ -231,55 +297,59 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { let display_string_of_history_entry = (undo_history_entry: undo_history_entry): option(string) => { let cur_cursor_term = undo_history_entry.current_cursor_term; - switch (cur_cursor_term) { - | None => failwith("Imposiible match, undisplayed undo history entry") - | Some(cursor_term) => - switch (cursor_term) { - | Exp(cursor_pos, exp) => - switch (exp) { - | EmptyHole(_) - | Var(_, _, _) - | NumLit(_, _) - | BoolLit(_, _) - | ListNil(_) => Some(string_of_history_entry(undo_history_entry)) - | Case(_, _, _, _) - | Lam(_, _, _, _) - | Parenthesized(_) - | Inj(_, _, _) => - switch (cursor_pos) { - | OnDelim(_, _) => None - | OnOp(_) - | OnText(_) => Some(string_of_history_entry(undo_history_entry)) + switch (string_of_history_entry(undo_history_entry)) { + | None => None + | Some(str) => + switch (cur_cursor_term) { + | None => failwith("Imposiible match, undisplayed undo history entry") + | Some(cursor_term) => + switch (cursor_term) { + | Exp(cursor_pos, exp) => + switch (exp) { + | EmptyHole(_) + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) => Some(str) + | Case(_, _, _, _) + | Lam(_, _, _, _) + | Parenthesized(_) + | Inj(_, _, _) => + switch (cursor_pos) { + | OnDelim(_, _) => None + | OnOp(_) + | OnText(_) => Some(str) + } + | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") } - | ApPalette(_, _, _, _) => failwith("ApPalette is not implemented") - } - | Pat(cursor_pos, pat) => - switch (pat) { - | EmptyHole(_) - | Wild(_) - | Var(_, _, _) - | NumLit(_, _) - | BoolLit(_, _) - | ListNil(_) => Some(string_of_history_entry(undo_history_entry)) - | Parenthesized(_) - | Inj(_, _, _) => + | Pat(cursor_pos, pat) => + switch (pat) { + | EmptyHole(_) + | Wild(_) + | Var(_, _, _) + | NumLit(_, _) + | BoolLit(_, _) + | ListNil(_) => Some(str) + | Parenthesized(_) + | Inj(_, _, _) => + switch (cursor_pos) { + | OnDelim(_, _) => None + | OnOp(_) + | OnText(_) => Some(str) + } + } + | Typ(_, _) + | ExpOp(_, _) + | PatOp(_, _) + | TypOp(_, _) => Some(str) + | Line(cursor_pos, _) + | Rule(cursor_pos, _) => switch (cursor_pos) { | OnDelim(_, _) => None | OnOp(_) - | OnText(_) => Some(string_of_history_entry(undo_history_entry)) + | OnText(_) => Some(str) } } - | Typ(_, _) - | ExpOp(_, _) - | PatOp(_, _) - | TypOp(_, _) => Some(string_of_history_entry(undo_history_entry)) - | Line(cursor_pos, _) - | Rule(cursor_pos, _) => - switch (cursor_pos) { - | OnDelim(_, _) => None - | OnOp(_) - | OnText(_) => Some(string_of_history_entry(undo_history_entry)) - } } }; }; @@ -350,7 +420,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Some(str) => Vdom.( Node.div( - [Attr.classes(["the-history-title"])], + [ + Attr.classes([ + "the-history-title", + "always-display-history-entry", + ]), + ], [ Node.div( [Attr.classes(["the-history-title-entry"])], @@ -417,11 +492,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { /* title entry */ Vdom.( Node.div( - [ - Attr.classes( - ["always-display-history-entry"] @ cur_his_classes, - ), - ], + [Attr.classes(cur_his_classes)], [ history_title_entry_view( ~is_expanded=group.is_expanded, @@ -460,11 +531,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Vdom.( Node.div( - [ - Attr.classes( - ["always-display-history-entry"] @ cur_his_classes, - ), - ], + [Attr.classes(cur_his_classes)], [ history_title_entry_view( ~is_expanded=group.is_expanded, @@ -491,11 +558,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { /* the history title entry */ Vdom.( Node.div( - [ - Attr.classes( - ["always-display-history-entry"] @ suc_his_classes, - ), - ], + [Attr.classes(suc_his_classes)], [ history_title_entry_view( ~is_expanded=group.is_expanded, @@ -560,11 +623,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { [ Vdom.( Node.div( - [ - Attr.classes( - ["always-display-history-entry"] @ suc_his_classes, - ), - ], + [Attr.classes(suc_his_classes)], [ history_title_entry_view( ~is_expanded=group.is_expanded, From eb894a28451c5d7acff4d3c5e34ca858694ce640 Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 4 Feb 2020 21:17:05 -0500 Subject: [PATCH 59/60] delete some comments --- src/hazelweb/gui/UndoHistoryPanel.re | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 080b3e9b98..84a6c3fb0c 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -53,12 +53,12 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { | Unit => "type: Unit" | Num => "type: Num" | Bool => "type: Bool" - | Parenthesized(_) => "type: (?)" - | List(_) => "type: [?]" + | Parenthesized(_) => "( )" + | List(_) => "[ ]" }; }; let display_string_of_cursor = - (cursor_term: option(CursorInfo.cursor_term)) => { + (cursor_term: option(CursorInfo.cursor_term)):string => { switch (cursor_term) { | None => failwith("Imposiible match, the inital state will not be displayed") @@ -152,6 +152,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { if (CursorInfo.is_hole(prev_cursor_term)) { None; } else if (num == 1 && can_delete_typ_inf(prev_cursor_term)) { + /* num==1 is the position of ':' in an expression */ Some( "clear type inference of " ++ display_string_of_cursor(prev_cursor_term), @@ -220,6 +221,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } } | Construct(shape) => + /* match for keyword */ switch (shape) { | SParenthesized => Some("add ( )") | SList => Some("type List") From 9c0d470bbb6aa63a415457d5eb4b9e19f096a7ab Mon Sep 17 00:00:00 2001 From: Cyrus Omar Date: Wed, 5 Feb 2020 20:39:08 -0500 Subject: [PATCH 60/60] refmt --- src/hazelweb/gui/UndoHistoryPanel.re | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hazelweb/gui/UndoHistoryPanel.re b/src/hazelweb/gui/UndoHistoryPanel.re index 84a6c3fb0c..c30d404a24 100644 --- a/src/hazelweb/gui/UndoHistoryPanel.re +++ b/src/hazelweb/gui/UndoHistoryPanel.re @@ -58,7 +58,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { }; }; let display_string_of_cursor = - (cursor_term: option(CursorInfo.cursor_term)):string => { + (cursor_term: option(CursorInfo.cursor_term)): string => { switch (cursor_term) { | None => failwith("Imposiible match, the inital state will not be displayed") @@ -221,7 +221,7 @@ let view = (~inject: Update.Action.t => Vdom.Event.t, model: Model.t) => { } } | Construct(shape) => - /* match for keyword */ + /* match for keyword */ switch (shape) { | SParenthesized => Some("add ( )") | SList => Some("type List")