diff --git a/src/core/editor/Zigg.re b/src/core/editor/Zigg.re index 5baa196d..5df9e8dc 100644 --- a/src/core/editor/Zigg.re +++ b/src/core/editor/Zigg.re @@ -35,12 +35,12 @@ let tokens = ({up, top, dn}: t) => List.rev(Slope.tokens(dn)), ]); -// let flatten = ({up, top, dn}: t) => -// List.concat([ -// Slope.Up.flatten(up), -// Wald.flatten(top), -// Slope.Dn.flatten(dn), -// ]); +let flatten = ({up, top, dn}: t) => + List.concat([ + Slope.Up.flatten(up), + Cell.flatten_wald(top), + Slope.Dn.flatten(dn), + ]); let face = (~side: Dir.t, zigg: t) => { let (s_d, top, _) = orient(side, zigg); diff --git a/src/core/editor/Zipper.re b/src/core/editor/Zipper.re index a7c04cd3..3118f05e 100644 --- a/src/core/editor/Zipper.re +++ b/src/core/editor/Zipper.re @@ -308,3 +308,14 @@ let mk_button = (~cur: Cursor.t, ctx) => let ctx = Ctx.(button(cons((dn', up'), tl))); unzip_exn(cell, ~ctx); }; + +let selection_str = (cur: Cursor.Base.t('tok)): option(string) => + switch (cur) { + | Point(_) => None + | Select({range, _}) => + range + |> Zigg.flatten + |> List.map((x: Token.t) => x.text) + |> String.concat("") + |> Option.some + }; diff --git a/src/core/structure/Cell.re b/src/core/structure/Cell.re index cb965bcb..15e14e14 100644 --- a/src/core/structure/Cell.re +++ b/src/core/structure/Cell.re @@ -58,6 +58,19 @@ include Base; [@deriving (sexp, yojson)] type t = Base.t(Token.t); +let rec flatten: Base.t(Token.t) => list('a) = + c => + switch (c.meld) { + | None => [] + | Some(m) => flatten_meld(m) + } +and flatten_wald = (W(wald): Wald.t(_)) => + Chain.map(Fun.id, flatten, wald) + |> Chain.to_list(x => [x], Fun.id) + |> List.flatten +and flatten_meld = (M(p_left, wald, p_right): Meld.t(_)) => + flatten(p_left) @ flatten_wald(wald) @ flatten(p_right); + let dirty = mk(~marks=Marks.dirty, ()); let rec pp = (out, {marks, meld}: t) => { let pp_meld = Meld.pp(pp, Token.pp); diff --git a/src/core/structure/Slope.re b/src/core/structure/Slope.re index 1c65798a..fef83963 100644 --- a/src/core/structure/Slope.re +++ b/src/core/structure/Slope.re @@ -16,6 +16,7 @@ include Base; type t = Base.t(Token.t); let tokens = List.concat_map(Terr.tokens); + // let link = (w: Wald.t, c: Rel.t(_), slope: t) => // switch (c) { // | Neq(c) => [Terr.Base.{cell: c, wald: Wald.rev(w)}, ...slope] @@ -124,16 +125,17 @@ let merge_hd = (~onto: Dir.t, t: Token.t, slope: t): option(t) => module Dn = { [@deriving (show({with_path: false}), sexp, yojson)] type t = list(Terr.R.t); - // let flatten = List.concat_map(Terr.R.flatten); let roll = roll(~onto=L); let unroll = unroll(~from=L); let pull = pull(~from=L); + let flatten = slope => slope |> List.rev |> List.concat_map(Terr.R.flatten); }; module Up = { [@deriving (show({with_path: false}), sexp, yojson)] type t = list(Terr.L.t); - // let flatten = List.concat_map(Terr.L.flatten); + let roll = roll(~onto=R); let unroll = unroll(~from=R); let pull = pull(~from=R); + let flatten = List.concat_map(Terr.L.flatten); }; diff --git a/src/core/structure/Terr.re b/src/core/structure/Terr.re index b52d1583..2e3dd448 100644 --- a/src/core/structure/Terr.re +++ b/src/core/structure/Terr.re @@ -76,13 +76,15 @@ module L = { // L2R: wald cell [@deriving (show({with_path: false}), sexp, yojson)] type t = Base.t(Token.t); - // let flatten = ({wald, cell}: t) => - // Wald.flatten(wald) @ Cell.flatten(cell); + + let flatten = (terr: t) => + Cell.flatten_wald(terr.wald) @ Cell.flatten(terr.cell); }; module R = { // L2R: cell wald [@deriving (show({with_path: false}), sexp, yojson)] type t = Base.t(Token.t); - // let flatten = ({cell, wald}: t) => - // Cell.flatten(cell) @ Wald.flatten(Wald.rev(wald)); + + let flatten = (terr: t) => + Cell.flatten(terr.cell) @ Cell.flatten_wald(Wald.rev(terr.wald)); }; diff --git a/src/web/Store.re b/src/web/Store.re index f6ccfe97..38f7d63a 100644 --- a/src/web/Store.re +++ b/src/web/Store.re @@ -25,21 +25,24 @@ let save_syntax_key: int => string = let save_syntax = (save_idx: int, z: Zipper.t) => LocalStorage.set(save_syntax_key(save_idx), z |> serialize); -let editor_defaults = [ - serialize(Zipper.empty), - serialize(parse(Data.t0_transcribe)), - serialize(parse(Data.t0_modify)), - serialize(parse(Data.t1_transcribe)), - serialize(parse(Data.t1_modify)), - serialize(parse(Data.t2_transcribe)), - serialize(parse(Data.t2_modify)), - serialize(parse(Data.t3_transcribe)), - serialize(parse(Data.t3_modify)), - // serialize(parse("case 7\n| x => 7")), - // serialize(parse("let (a, b) =\n(8*9<6, 17==6) in\n(a,(a, b))")), - // serialize(parse("let f = fun z -> 9 in f(9)")), +let tasks = [ + Data.t0_transcribe, + Data.t0_modify, + Data.t1_transcribe, + Data.t1_modify, + Data.t2_transcribe, + Data.t2_modify, + Data.t3_transcribe, + Data.t3_modify, + // (("case 7\n| x => 7")), + // (("let (a, b) =\n(8*9<6, 17==6) in\n(a,(a, b))")), + // (("let f = fun z -> 9 in f(9)")), ]; +let editor_defaults = + [serialize(Zipper.empty)] + @ List.map(task => serialize(parse(task)), tasks); + let load_default_syntax: int => Zipper.t = save_idx => switch (List.nth_opt(editor_defaults, save_idx)) { diff --git a/src/web/style.css b/src/web/style.css index c8657bbf..54400faa 100644 --- a/src/web/style.css +++ b/src/web/style.css @@ -588,4 +588,13 @@ svg { 50% { opacity: 0%; } +} + +#clipboard-shim { + position: fixed; + opacity: 0.01; + width: 1em; + height: 1em; + top: -1em; + left: -1em; } \ No newline at end of file diff --git a/src/web/util/Dom.re b/src/web/util/Dom.re index 049b8042..2de1a3c9 100644 --- a/src/web/util/Dom.re +++ b/src/web/util/Dom.re @@ -8,3 +8,35 @@ let get_elem_by_id = id => { //print_endline(id); }); }; + +let clipboard_shim_id = "clipboard-shim"; + +let focus_clipboard_shim = () => get_elem_by_id(clipboard_shim_id)##focus; + +open Virtual_dom.Vdom; +let clipboard_shim = { + Node.textarea(~attrs=[Attr.id(clipboard_shim_id)], []); +}; + +let copy = (str: string) => { + focus_clipboard_shim(); + Dom_html.document##execCommand( + Js.string("selectAll"), + Js.bool(false), + Js.Opt.empty, + ); + Dom_html.document##execCommand( + Js.string("insertText"), + Js.bool(false), + Js.Opt.option(Some(Js.string(str))), + ); + Dom_html.document##execCommand( + Js.string("selectAll"), + Js.bool(false), + Js.Opt.empty, + ); +}; + +let paste = evt => + Js.to_string(evt##.clipboardData##getData(Js.string("text"))) + |> Re.Str.global_replace(Re.Str.regexp("\n[ ]*"), "\n"); diff --git a/src/web/view/Page.re b/src/web/view/Page.re index 0839d151..0e1acdf0 100644 --- a/src/web/view/Page.re +++ b/src/web/view/Page.re @@ -194,6 +194,12 @@ let on_key = (~inject, ~model) => { ]; }; +let copy = (cur: Tylr_core.Zipper.Cursor.Base.t('tok)) => + switch (Tylr_core.Zipper.selection_str(cur)) { + | None => () + | Some(str) => Util.Dom.copy(str) + }; + let view = (~inject, model: Model.t) => { div( ~attrs= @@ -202,33 +208,31 @@ let view = (~inject, model: Model.t) => { // necessary to make cell focusable // tabindex(0), on_blur(_ => { - Util.Dom.get_elem_by_id("page")##focus; + //Util.Dom.get_elem_by_id("page")##focus; + Util.Dom.focus_clipboard_shim(); Effect.Prevent_default; }), + Attr.on_focus(_ => { + Util.Dom.focus_clipboard_shim(); + Effect.Ignore; + }), Attr.on_copy(_ => { - print_endline("TODO: copying"); - //JsUtil.copy(Printer.to_string_selection(editor)); + copy(model.zipper.cur); Effect.Ignore; }), Attr.on_cut(_ => { - print_endline("TODO: cutting"); - //JsUtil.copy(Printer.to_string_selection(editor)); - //inject(UpdateAction.PerformAction(Destruct(Left))); - Effect.Ignore; + copy(model.zipper.cur); + inject(Update.PerformAction(Delete(L))); }), Attr.on_paste(evt => { - print_endline("TODO: pasting"); - open Js_of_ocaml; - let pasted_text = - Js.to_string(evt##.clipboardData##getData(Js.string("text"))); - //|> Util.StringUtil.trim_leading; - Dom.preventDefault(evt); - inject(Update.PerformAction(Insert(pasted_text))); + Js_of_ocaml.Dom.preventDefault(evt); + inject(Update.PerformAction(Insert(Util.Dom.paste(evt)))); }), ...on_key(~inject, ~model), ], [ FontSpecimen.view("font-specimen"), + Util.Dom.clipboard_shim, // FontSpecimen.view("logo-font-specimen"), Dec.Filters.all, // top_bar_view(~inject, model),