diff --git a/dune b/dune index 5124748..fda4166 100644 --- a/dune +++ b/dune @@ -13,8 +13,8 @@ graphv_webgl js_of_ocaml lwt -; irmin-git -; irmin-indexeddb + irmin + irmin-git zed gg diff --git a/human.ml b/human.ml index 8cc2724..a1d6e93 100644 --- a/human.ml +++ b/human.ml @@ -1,5 +1,14 @@ (* +describe exactly every case you can think of that you want this drawing and layout system to handle: + +* draw text on variously coloured backgrounds that can be defined locally or globally +* TODO + +*) + +(* + ALWAYS BREAK UP THE PROBLEM INTO SMALLER CHUNKS BITCH!! Times I would have solved it faster if i broke it up instead of trying to understand it all at once: 2 @@ -20,18 +29,26 @@ some options: *) -open Lwt.Infix module F = Fmt module NVG = Graphv_webgl -(* module Istore = Irmin_unix.Git.FS.KV (Irmin.Contents.String)*) -(*module Istore = - Irmin_git.Generic - (Irmin_indexeddb.Content_store) - (Irmin_indexeddb.Branch_store) - (Irmin.Contents.String) - (Irmin.Path.String_list) - (Irmin.Branch.String)*) +module Nav = struct + open Lwt.Infix + + module S = Irmin_mem.KV.Make(Irmin.Contents.String) + + type t = S.tree + + let init () : t Lwt.t = + S.Repo.v (Irmin_mem.config ()) >>= S.main >>= S.tree + + let test_populate () : t Lwt.t = + let add p s t = S.Tree.add t p s in + add ["hello"] "world" (S.Tree.empty ()) >>= + add ["hello";"daddy"] "ily" >>= + add ["beep";"beep"] "motherfucker" + +end module Key = struct type special = @@ -325,11 +342,9 @@ module Event_js = struct | None -> `Unknown "keypress .key is None?" end -module Display = struct +module Panel = struct open Gg - module I = NVG.Image - module P = NVG.Path - module Color = NVG.Color + open NVG (* current window state to be passed to window renderer *) type state = @@ -380,45 +395,6 @@ module Display = struct NVG.stroke vg ; Box2.max b - (* Display.state.box as supplied to a widget defines the allowed drawing area for the widget. - This way basic widgets will just expand to the full area of a box, while other widgets can have - the express purpose of limiting the size of an object in a larger system of limitations. - - Panes return a tuple: (state, (box, image)) - state is the updated state, where state.box is always - - the top left corner of the box the pane drew in, and - - the bottom right corner of the state.box that was passed in - box is the area the widget actually drew in (or wants to sort of "use") - image is the Wall.image to compose with other panes and draw to the display - *) -end - -module Panel = struct - open Gg - open NVG - - type t = - { mutable act: t -> Event.events -> Display.pane Lwt.t - ; mutable subpanels: t Lwt.t list - ; mutable tag: string } - - type panel = t - - let blank = - { act= (fun _panel _events -> Lwt.return Display.pane_empty) - ; subpanels= [] - ; tag= "blank pane" } - - let draw (pane : Display.pane) = - Lwt.return - { act= (fun _panel _events -> Lwt.return pane) - ; subpanels= [] - ; tag= "draw-pane" } - - let actor (panel : t) : Event.events -> Display.pane Lwt.t = - fun events -> - panel.act panel events >>= fun pane -> Lwt.return pane - module Style = struct module Font = struct type t = @@ -531,6 +507,8 @@ module Panel = struct ; b= Gg.Size1.zero ; l= Gg.Size1.zero ; r= Gg.Size1.zero } + + let all v = {t= v; b= v; l= v; r= v} end module Ui = struct @@ -546,7 +524,8 @@ module Panel = struct | `Attr of attr * node | `Join of dir * node * node ] - and node = {mutable parent: node option; mutable t: t; n: int} + and node = {mutable parent: parent; mutable t: t; n: int} + and parent = [`Left of node | `Right of node | `None] and cursor = {root: node; mutable sel: node} and atom = @@ -556,12 +535,19 @@ module Panel = struct | `Hint of [`Line | `Other] | `Empty ] - and attr = [`Style of style | `Pad of Pad.t | `Handler of handler] + and attr = + [ `Style of style + | `Pad of Pad.t + | `Handler of handler + | `Draw of draw ] + and dir = [`X | `Y | `Z] and image = NVG.Image.image and boundary = [`Char | `Word | `Phrase | `Line | `Page | `Text] and style = Style.t and handler = node -> Event.t -> Event.t option + and draw_context = {vg: NVG.t; style: Style.t} + and draw = draw_context -> P2.t -> P2.t let node_count = ref 0 @@ -572,10 +558,10 @@ module Panel = struct let set_parent_on_children n : node = ( match n.t with | `Atom _ -> () - | `Attr (_, a) -> a.parent <- Some n + | `Attr (_, a) -> a.parent <- `Left n | `Join (_, a, b) -> - a.parent <- Some n ; - b.parent <- Some n ) ; + a.parent <- `Left n ; + b.parent <- `Right n ) ; n let sub (n : node) : node = @@ -585,23 +571,28 @@ module Panel = struct | `Join (_, a, _) -> a let super (n : node) : node = - match n.parent with Some n' -> n' | None -> n + match n.parent with `Left n' | `Right n' -> n' | `None -> n - let set_children_on_parent ~oldc ~newc = - match newc.parent with - | Some ({t= `Attr (a, _); _} as s) -> - s.t <- `Attr (a, newc) ; - newc - | Some ({t= `Join (d, a, b); _} as s) when oldc == a -> - s.t <- `Join (d, newc, b) ; - newc - | Some ({t= `Join (d, a, b); _} as s) when oldc == b -> - s.t <- `Join (d, a, newc) ; - newc - | _ -> newc + let set_children_on_parent n = + match n.parent with + | `Left ({t= `Attr (a, _); _} as s) + |`Right ({t= `Attr (a, _); _} as s) -> + s.t <- `Attr (a, n) ; + n + | `Left ({t= `Join (d, _, b); _} as s) -> + s.t <- `Join (d, n, b) ; + n + | `Right ({t= `Join (d, a, _); _} as s) -> + s.t <- `Join (d, a, n) ; + n + | _ -> n + + let option_of_parent = function + | `None -> None + | `Left a | `Right a -> Some a let node (t : t) = - set_parent_on_children {parent= None; t; n= node_n ()} + set_parent_on_children {parent= `None; t; n= node_n ()} let atom (a : atom) = node (`Atom a) let attr (a : attr) (child : node) = node (`Attr (a, child)) @@ -609,6 +600,159 @@ module Panel = struct let empty_image = V2.zero let empty_node () = node (`Atom `Empty) let style (s : Style.t) (n : node) = node (`Attr (`Style s, n)) + let pad v n = attr (`Pad (Pad.all v)) n + + let rec node_up_ (d : [`Left | `Right]) n' = + match (d, n'.parent) with + | _, `None -> None + | ( _ + , ( `Left ({t= `Attr _; _} as p) + | `Right ({t= `Attr _; _} as p) ) ) -> + node_up_ d p + | `Right, `Right ({t= `Join _; _} as p) + |`Left, `Left ({t= `Join _; _} as p) -> + node_up_ d p + | `Left, `Right {t= `Join (_, l, _); _} -> Some l + | `Right, `Left {t= `Join (_, _, r); _} -> Some r + | _, (`Left {t= `Atom _; _} | `Right {t= `Atom _; _}) -> + assert false + + let node_next_ (d : [`Left | `Right]) (n : node) = + match (d, n.t) with + | _, `Atom _ -> node_up_ d n + | _, `Attr (_, n') -> Some n' + | `Right, `Join (_, _, r) -> Some r + | `Left, `Join (_, l, _) -> Some l + + let rec search_preorder (f : node -> 'a option) (n : node) : + 'a option = + match f n with + | None -> ( + match node_next_ `Left n with + | Some n -> search_preorder f n + | None -> None ) + | x -> x + + let rec search_reverse_preorder (f : node -> 'a option) (n : node) + : 'a option = + match f n with + | None -> ( + match node_next_ `Right n with + | Some n -> search_reverse_preorder f n + | None -> None ) + | x -> x + + let replace_parents_child parent n : node = + match parent with + | `Left ({t= `Attr (a, _); _} as p) + |`Right ({t= `Attr (a, _); _} as p) -> + p.t <- `Attr (a, n) ; + n + | `Left ({t= `Join (d, _, r); _} as p) -> + p.t <- `Join (d, n, r) ; + n + | `Right ({t= `Join (d, l, _); _} as p) -> + p.t <- `Join (d, l, n) ; + n + | _ -> n + + let rec tree_iter f n i = + if i <> 0 then tree_iter f (f n) (i - 1) else f n + + let search_forward f (n : node) = search_preorder f n + let search_backward f (n : node) = search_reverse_preorder f n + + let is_atom_uchar = function + | {t= `Atom (`Uchar _); _} as n -> Some n + | _ -> None + + let tree_uchar_fwd n = + Option.value (search_forward is_atom_uchar n) ~default:n + + let tree_uchar_back n = + Option.value (search_backward is_atom_uchar n) ~default:n + + let is_boundary b n = + match (b, n.t) with + | `Char, `Atom (`Uchar _) + |`Word, `Atom (`Boundary `Word) + |`Phrase, `Atom (`Boundary `Phrase) + |`Line, `Atom (`Boundary `Line) + |`Page, `Atom (`Boundary `Page) -> + Some n + | _ -> None + + let search_back_opt (f : node -> node option) (n : node option) = + Option.bind n (search_backward f) + + let search_back_uchar_opt = search_back_opt is_atom_uchar + + let rec traverse_nodes ~(f : node -> node option) (n : node) : + unit = + match f n with + | Some {t= `Atom _; _} -> () + | Some {t= `Attr (_, n'); _} -> traverse_nodes ~f n' + | Some {t= `Join (_, a, b); _} -> + traverse_nodes ~f a ; traverse_nodes ~f b + | None -> () + + let insert_join_l (d : dir) (n : node) (n' : node) : node = + let p = n.parent in + let n'' = join d n' n in + n''.parent <- p ; + set_children_on_parent n'' + + let remove_join_l (n : node) : node = + match n.parent with + | `Left ({t= `Attr (_, n'); _} as s) + |`Right ({t= `Attr (_, n'); _} as s) + |`Left ({t= `Join (_, _, n'); _} as s) -> + s.t <- n'.t ; + n' + | _ -> n + + let kill_backward_char (n : node) : node option = + search_forward is_atom_uchar + (replace_parents_child (super (tree_uchar_back n)).parent n) + + let insert_attr (a : attr) (n : node) : node = + let p = n.parent in + let n' = node (`Attr (a, n)) in + n'.parent <- p ; + set_children_on_parent n' + + let remove_attr (n : node) : node = + match n.t with + | `Attr (_, n') -> + ( match n.parent with + | `Left ({t= `Join (d, _, b); _} as p) -> + p.t <- `Join (d, n', b) ; + ignore (set_parent_on_children p) + | `Right ({t= `Join (d, a, _); _} as p) -> + p.t <- `Join (d, a, n') ; + ignore (set_parent_on_children p) + | `Left ({t= `Attr (a, _); _} as p) + |`Right ({t= `Attr (a, _); _} as p) -> + p.t <- `Attr (a, n') ; + ignore (set_parent_on_children p) + | _ -> () ) ; + n' + | _ -> assert false + + let join_x = join `X + let join_y = join `Y + let join_z = join `Z + let ( ^^ ) = join_x + let ( ^/^ ) = join_y + let ( ^*^ ) = join_z + + let append_ d (l : node -> node) (a : node) : node -> node = + fun n -> l (join d a n) + + let empty_append = Fun.id + let append_x = append_ `X + let append_y = append_ `Y + let append_z = append_ `Z module Pp = struct let pp_uchar ppf v = @@ -650,7 +794,8 @@ module Panel = struct | `Pad _ -> "`Pad ..." | `Shift _ -> "`Shift ..." | `Cursor -> "`Cursor" - | `Handler _ -> "`Handler ..." ) ) + | `Handler _ -> "`Handler ..." + | `Draw _ -> "`Draw ..." ) ) ppf () let pp_dir ppf v = @@ -674,6 +819,13 @@ module Panel = struct ++ const child b ) ) () + and _pp_parent ppf v = + let open Fmt in + match v with + | `None -> pf ppf "`None" + | `Left n -> pf ppf "`Left %a" pp_node_n n + | `Right n -> pf ppf "`Right %a" pp_node_n n + and _pp_node child ppf v = let open Fmt in pf ppf "@[%a@]" @@ -681,9 +833,7 @@ module Panel = struct (record [ field "n" (fun v -> v.n) int ; field "t" (fun v -> v.t) (_pp_t child) - ; field "parent" - (fun v -> v.parent) - (option pp_node_n) ] ) ) + ; field "parent" (fun v -> v.parent) _pp_parent ] ) ) v and pp_node_n_record = @@ -716,58 +866,6 @@ module Panel = struct open Pp - let rec traverse_nodes ~(f : node -> node option) (n : node) : - unit = - match f n with - | Some {t= `Atom _; _} -> () - | Some {t= `Attr (_, n'); _} -> traverse_nodes ~f n' - | Some {t= `Join (_, a, b); _} -> - traverse_nodes ~f a ; traverse_nodes ~f b - | None -> () - - let insert_join_r (d : dir) (n : node) (n' : node) : node = - let p = n.parent in - let n'' = join d n' n in - n''.parent <- p ; - set_children_on_parent ~oldc:n ~newc:n'' - - let insert_attr (a : attr) (n : node) : node = - let p = n.parent in - let n' = node (`Attr (a, n)) in - n'.parent <- p ; - set_children_on_parent ~oldc:n ~newc:n' - - let remove_attr (n : node) : node = - match n.t with - | `Attr (_, n') -> - ( match n.parent with - | Some p -> - p.t <- - ( match p.t with - | `Attr (a, _) -> `Attr (a, n') - | `Join (d, a, b) when n == a -> `Join (d, n', b) - | `Join (d, a, b) when n == b -> `Join (d, a, n') - | _ -> assert false ) ; - ignore (set_parent_on_children p) - | None -> () ) ; - n' - | _ -> assert false - - let join_x = join `X - let join_y = join `Y - let join_z = join `Z - let ( ^^ ) = join_x - let ( ^/^ ) = join_y - let ( ^*^ ) = join_z - - let append_ d (l : node -> node) (a : node) : node -> node = - fun n -> l (join d a n) - - let empty_append = Fun.id - let append_x = append_ `X - let append_y = append_ `Y - let append_z = append_ `Z - module Text = struct let rec decode dec (l : 'a) : 'a * [< `Await | `End | `Uchar of Uchar.t] = @@ -821,7 +919,7 @@ module Panel = struct open NVG type d = [`X | `Y | `Z] - type t = {vg: NVG.t; style: Style.t} + type t = draw_context let vcat d a b = match d with @@ -855,7 +953,7 @@ module Panel = struct ( P2.y t +. metrics.ascender +. metrics.descender +. metrics.line_height ) - let rec atom vg b a : P2.t = + let rec atom vg b (a : atom) : P2.t = let vg = vg.vg in match a with | `Image image -> @@ -877,15 +975,16 @@ module Panel = struct and attr t b ((a : attr), n) : P2.t = match a with | `Style s -> - Display.fill_box t.vg s.bg + path_box t.vg s.bg (Box2.of_pts b (node {t with style= Style.merge t.style s} b n) ) | `Pad p -> pad t b p n - | _ -> node t b n + | `Draw d -> d t b + | `Handler _ -> node t b n and pad vg t (p : Pad.t) n = let nv = node vg P2.(v (p.l +. x t) (p.t +. y t)) n in - P2.(v (x nv +. p.r) (y t +. p.b)) + P2.(v (x nv +. p.r) (y nv +. p.b)) and join vg t (d, a, b) : P2.t = let av = node vg t a in @@ -910,10 +1009,10 @@ module Panel = struct | `Atom a -> atom t b a | `Attr a -> attr t b a | `Join a -> join t b a in - (* ignore + (*ignore (Display.path_box t.vg - (Color.rgbaf ~r:1. ~g:0. ~b:0. ~a:0.2) - (Box2.of_pts b b') ) ;*) + (Color.rgbaf ~r:1. ~g:0. ~b:0. ~a:0.2) + (Box2.of_pts b b') ) ; *) b' end @@ -985,80 +1084,7 @@ module Panel = struct ppf () end - let tree_next (n : node) = - let rec next_right n' = - match n'.parent with - | None -> None - | Some ({t= `Attr _; _} as p) -> next_right p - | Some {t= `Join (_, a, b); _} when n' == a -> Some b - | Some ({t= `Join (_, _, b); _} as p) when n' == b -> - next_right p - | Some {t= `Join _; _} -> assert false - | Some {t= `Atom _; _} -> assert false in - match n.t with - | `Atom _ -> next_right n - | `Attr (_, n') -> Some n' - | `Join (_, a, _) -> Some a - - let tree_prev (n : node) = - let rec prev_right n' = - match n'.t with - | `Attr (_, nn) -> prev_right nn - | `Join (_, _, b) -> prev_right b - | `Atom _ -> Some n' in - match n.parent with - | None -> None - | Some {t= `Atom _; _} -> - assert false - (* shouldn't happen TODO is there no way to type constrain these? *) - | Some {t= `Attr _; _} -> n.parent - | Some {t= `Join (_, a, b); _} when b == n -> prev_right a - | Some {t= `Join (_, a, _); _} when a == n -> n.parent - | Some {t= `Join _; _} -> assert false - (* shouldn't happen *) - - let rec tree_iter f n i = - if i <> 0 then tree_iter f (f n) (i - 1) else f n - - let rec search_ next f n = - (* F.epr "search_ " ; *) - match next n with - | Some n' -> ( - (* F.epr "%a@." pp_n n' ; *) - match f n' with - | Some a -> (n', Some a) - | None -> search_ next f n' ) - | None -> (*F.epr "None@." ; *) (n, None) - - let search_forward f (n : node) = snd (search_ tree_next f n) - let search_backward f (n : node) = snd (search_ tree_prev f n) - - let is_atom_uchar = function - | {t= `Atom (`Uchar _); _} as n -> Some n - | _ -> None - - let tree_uchar_fwd n = - match is_atom_uchar n with - | Some a -> a - | None -> - Option.value (search_forward is_atom_uchar n) ~default:n - - let tree_uchar_back n = - match is_atom_uchar n with - | Some a -> a - | None -> - Option.value (search_backward is_atom_uchar n) ~default:n - let perform_action (a : Action.t) (c : cursor) : node option = - let mb ?(f = fun a -> a) b n = - match (b, n.t) with - | `Char, `Atom (`Uchar _) - |`Word, `Atom (`Boundary `Word) - |`Phrase, `Atom (`Boundary `Phrase) - |`Line, `Atom (`Boundary `Line) - |`Page, `Atom (`Boundary `Page) -> - Some (f n) - | _ -> None in match a with | `Move (`Forward `Line) -> ( let i = ref 0 in @@ -1069,13 +1095,13 @@ module Panel = struct | {t= `Atom (`Uchar _); _} -> incr i ; None | _ -> None ) c.sel ) ; - match search_forward (mb `Line) c.sel with + match search_forward (is_boundary `Line) c.sel with | Some n' -> Some (tree_iter (fun nn -> Option.value - (search_forward (mb `Char) nn) + (search_forward (is_boundary `Char) nn) ~default:nn ) n' !i ) | None -> None ) @@ -1090,34 +1116,32 @@ module Panel = struct c.sel with | Some n' -> - Some - (tree_iter - (fun nn -> - Option.value - (search_forward (mb `Char) nn) - ~default:nn ) - (fst (search_ tree_prev (mb `Line) n')) - !i ) + Option.map + (fun n -> tree_iter tree_uchar_back n !i) + (search_backward (is_boundary `Line) n') | None -> None ) | `Move (`Forward b) -> - search_forward (mb ~f:tree_uchar_back b) c.sel - | `Move (`Backward b) -> - search_backward (mb ~f:tree_uchar_fwd b) c.sel - | `Move (`Beginning b) -> - (* uses last searched node regardless of match *) - Some (tree_uchar_fwd (fst (search_ tree_prev (mb b) c.sel))) + Option.map tree_uchar_fwd + (search_forward (is_boundary b) c.sel) | `Move (`End b) -> - (* uses last searched node regardless of match *) - Some - (tree_uchar_back (fst (search_ tree_next (mb b) c.sel))) + Option.map tree_uchar_back + (search_forward (is_boundary b) c.sel) + | `Move (`Backward b) -> + Option.map tree_uchar_back + (search_backward (is_boundary b) c.sel) + | `Move (`Beginning b) -> + Option.map tree_uchar_fwd + (search_backward (is_boundary b) c.sel) | `Insert n -> - ignore (insert_join_r `X (super c.sel) n) ; + ignore (insert_join_l `X (super c.sel) n) ; Some c.sel | `Overwrite _s -> None | `Yank _s -> None + | `Kill (`Forward `Char) -> None (*kill_forward_char c.sel *) + | `Kill (`Backward `Char) -> kill_backward_char c.sel | `Kill _s -> None | `Descend -> Some (sub c.sel) - | `Ascend -> c.sel.parent + | `Ascend -> option_of_parent c.sel.parent | `Custom _s -> None type event_status = [`Handled | `Event of Event.t] @@ -1146,6 +1170,8 @@ module Panel = struct |> add [([Ctrl], C 'a')] [`Move (`Beginning `Line)] |> add [([Ctrl], C 'e')] [`Move (`End `Line)] |> add [([Ctrl], C 'k')] [`Kill (`End `Line)] + |> add [([], U `Backspace)] [`Kill (`Backward `Char)] + |> add [([], U `Delete)] [`Kill (`Forward `Char)] |> add [([Ctrl], U `Backspace)] [`Kill (`Backward `Word)] |> add [([Meta], U `Backspace)] [`Kill (`Backward `Word)] |> add @@ -1157,6 +1183,25 @@ module Panel = struct let cursor_attr = `Style Style.(bg NVG.Color.(rgbaf ~r:1. ~g:1. ~b:0. ~a:1.)) + let draw_cursor_root (c : cursor) : node = + let open Gg in + attr + (`Draw + (fun (t : draw_context) (b : P2.t) -> + Draw.node t b + (Text.lines (Fmt.to_to_string pp_node_structure c.root)) + ) ) + (atom `Empty) + + let draw_cursor_sel (c : cursor) : node = + let open Gg in + attr + (`Draw + (fun (t : draw_context) (b : P2.t) -> + Draw.node t b + (Text.lines (Fmt.to_to_string pp_node (sub c.sel))) ) ) + (atom `Empty) + let textedit ?(bindings = textedit_bindings) (n : node) = Format.pp_set_max_boxes F.stderr 64 ; (*full screen fynn *) @@ -1178,23 +1223,27 @@ module Panel = struct | `Uchar c -> Some (`Insert (atom (`Uchar c))) | _ -> None ) | _ -> None ) in - match a with - | Some x -> - c.sel <- remove_attr c.sel ; - ( match perform_action x c with - | Some n' -> - F.epr "textedit action @[%a@] Success@." - Action.pp_t x ; - c.sel <- n' - | None -> - F.epr "textedit action @[%a@] Failure@." - Action.pp_t x ) ; - c.sel <- insert_attr cursor_attr c.sel ; - F.epr "tree: @[%a@]@." Pp.pp_node_structure c.root ; - None - | None -> None ) + let r = + match a with + | Some x -> + c.sel <- remove_attr c.sel ; + ( match perform_action x c with + | Some n' -> + F.epr "textedit action @[%a@] Success@." + Action.pp_t x ; + c.sel <- n' + | None -> + F.epr "textedit action @[%a@] Failure@." + Action.pp_t x ) ; + c.sel <- insert_attr cursor_attr c.sel ; + None + | None -> None in + r ) , n ) ; - set_parent_on_children c.root + join_y (pad 5. c.root) + (join_y + (pad 5. (draw_cursor_sel c)) + (pad 5. (draw_cursor_root c)) ) let handler_of_node (n : node) : handler option = let f n = @@ -1217,14 +1266,15 @@ module Panel = struct Draw.node {vg; style= Style.dark} p t let test = - textedit - (style Style.dark - Text.( - (* text "--- welcome to my land of idiocy ---" - ^/^ *) - text "hello bitch" - (*^^ text "! sup daddy" ^^ nl) - ^/^ lines "123")*)) ) + style Style.dark + (pad 20. + (textedit + Text.( + (* text "--- welcome to my land of idiocy ---" + ^/^ *) + text "hello bitch" + (*^^ text "! sup daddy" ^^ nl) + ^/^ lines "123")*)) ) ) end end @@ -1256,3 +1306,5 @@ end If the Irmin Tree is better implemented than the garbage i am trying to make ad hoc, (i.e. we can implement all our cursor movement and editing mechanisms with the Irmin.Tree interface easily, then yea lol) *) + +(* would be nice to be able to switch arbitrary nodes between their drawn representation and the sort of node structure representation. This might be a more general philsophy to apply to the entire system, where you want to be able to switch between representations (i.e. "view-source" but with further higher level analysis views built on top as well *)