58 Commits

Author SHA1 Message Date
cqc
420e350544 examples.ml lol that was hard 2023-03-05 15:50:18 -06:00
cqc
ab91e5dee0 toplevel emits round trip pprint of ast to console. 2023-03-03 18:47:24 -06:00
cqc
272778ad7b well this is a toplevel, now to add a ppx ast printer? 2023-03-03 13:48:46 -06:00
cqc
8c16946650 time to pivot again 2023-02-23 23:43:57 -06:00
cqc
7a1e4ef2ba Dom.preventDefault hack keeps browser shortcuts from happening. 2023-02-17 15:25:04 -06:00
cqc
480e77bbb9 switched to using local test repo because there was no internet on Le Canadien, fixed tree_nav focus handle embedding, added Moar to may_handle 2023-02-16 13:35:34 -06:00
cqc
53982ab0c6 more debug 2023-02-13 09:29:47 -06:00
cqc
5c11183217 colorizing the focus status of each node reveals things are v broke 2023-02-08 15:40:39 -06:00
cqc
2ec6426fe5 key press display 2023-02-06 09:36:14 -06:00
cqc
0df5884a88 list-based key press handlers 2023-02-05 15:47:00 -06:00
cqc
6948a65a97 padding 2023-02-05 14:47:26 -06:00
cqc
bba26b9c0f cleanup 2023-02-03 12:10:27 -06:00
cqc
fcf528275b selectable node/contents, needs correcting focus control 2023-02-03 12:07:10 -06:00
cqc
d53f6687e5 broke focus, but rearranged for better save implementation 2023-01-23 13:04:04 -06:00
cqc
d46c1de49d still works 2023-01-23 11:54:46 -06:00
cqc
f0c5556450 tree_nav :3 2023-01-22 23:16:15 -06:00
cqc
97730899c6 tree_nav cursor movement 2023-01-13 09:30:36 -06:00
cqc
18daf83c1c loads and displays teh git tree 2023-01-13 05:39:51 -06:00
cqc
dfef26fcf5 text_area basic nav 2023-01-07 07:24:29 -06:00
cqc
048ea0eab4 text_area improvements 2022-12-18 11:14:37 -06:00
cqc
3509930195 added Focus.releases for line_table 2022-12-15 12:05:15 -06:00
cqc
b1ac36ce3e text editor issue is a problem with focus resolution
might require Focus.release, but may be incorrect use of Focus.request.
2022-12-14 19:39:28 -06:00
cqc
a12db025e0 Backspace but there's a subtle/unusual issue with enter/backspace and the text insertion occuring on the wrong line after unknown sequence. 2022-12-14 13:23:16 -06:00
cqc
5c10f3860a basic text field edition 2022-12-14 09:46:09 -06:00
cqc
a64fcbb010 refactored resizing and stuff 2022-12-11 18:25:57 -06:00
cqc
7baa6f3648 compute ui.w and ui.h during update 2022-12-10 14:27:22 -06:00
cqc
af92f03706 moar 2022-12-08 20:12:56 -06:00
cqc
cb263b5758 edit field editsdune build -w ./boot_js.bc.js 2022-12-08 12:27:36 -06:00
cqc
44879eb947 another barely working text rendering scheme 2022-12-07 12:47:56 -06:00
cqc
49bddb6365 urgh... stuck with how to implement cursor/focus etc. Decided to try to integrate Nottui and Nottui_widgets directly istead of reinventing another wheel. 2022-12-04 12:25:00 -06:00
cqc
b5d846b35d re-arranged 2022-11-22 23:38:53 -06:00
cqc
60c83c608a looks like shit 2022-11-22 13:19:51 -06:00
cqc
58ec73972b lwd-ifying it 2022-11-22 02:21:45 -06:00
cqc
b705c598ff use main branch 2022-11-17 20:25:20 -06:00
cqc
9d1ccb93b5 little re-arranging 2022-11-17 20:16:15 -06:00
cqc
3fc8125d42 lol browser requests github repo via cors proxy (via npm, run with ./cors_proxy.sh) but then stack overflows 2022-11-08 22:06:58 -06:00
cqc
3b09bb1c11 works 2022-10-06 14:29:57 -05:00
cqc
281351371d Irmin_git.KV (Irmin_git.Mem) (Git.Mem.Sync (Irmin_git.Mem)) results in a.caml_thread_initialize is not a function 2022-10-06 12:18:32 -05:00
cqc
fec4249d9f irmin 2022-10-04 23:36:25 -05:00
cqc
65aa7ff901 character insertion 2022-09-03 15:20:22 -05:00
cqc
39193ff253 cursor works 2022-09-03 11:24:34 -05:00
cqc
399280d9c4 correct? rendering 2022-09-02 21:34:47 -05:00
cqc
6a484c3a06 it renders text, but wrong 2022-09-02 19:22:06 -05:00
cqc
7460b8f793 halfway to graphv_webgl replacing wall 2022-08-31 12:32:44 -05:00
cqc
c40e725978 uhh 2022-08-06 12:03:33 -05:00
cqc
50831dc73d most cursor movement functionality works, but there are lots of weird quirks to iron out 2022-03-20 16:01:41 -05:00
cqc
98e78d81ec ok it works now 2022-03-20 13:06:06 -05:00
cqc
fd7db32917 what have i done 2022-03-20 11:57:25 -05:00
cqc
c81dce7148 cursor movement by char works across lines (i.e. subnested joins) 2022-03-19 16:05:11 -05:00
cqc
205f650eac Action.pp_t and cleanup 2022-03-19 15:14:23 -05:00
cqc
8067e29ea8 C-f and C-b 2022-03-19 12:10:23 -05:00
cqc
0d831aa9cf ui node tree pretty-printer 2022-03-13 15:55:10 -05:00
cqc
ecf9983728 working on cursor contro 2022-02-13 16:28:58 -06:00
cqc
a82c9464f4 here i am going to abandon using the linear "Regions" which are "Trope"s from grenier.trope which were adopted while star-struck from a weird ui library i can't remember the name of at the moment, because using regions with nodes is just pure fucking premature optimization and makes the backward references really hard and i don't want to deal with it anymore 2022-02-06 18:34:17 -06:00
cqc
481870e067 i left it like this for a long time whoops 2022-01-04 04:17:26 -06:00
cqc
8ee3789cb9 mr. derpsalot derps more; refactored the ui widget thing again... 2021-11-08 22:27:47 -06:00
cqc
983fc326d6 storeview 2021-10-29 12:27:58 -05:00
cqc
364e3e7165 getting super confused while trying to make generic tree structure operations that map to irmin 2021-10-25 11:08:01 -05:00
23 changed files with 6502 additions and 2304 deletions

1
.console Normal file
View File

@ -0,0 +1 @@
it consoles you

View File

@ -1 +0,0 @@
profile = compact

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

205
backend.ml Normal file
View File

@ -0,0 +1,205 @@
module F = Fmt
module Key = struct
type special =
[ `Enter
| `Escape
| `Tab
| `Arrow of [`Up | `Down | `Left | `Right]
| `Function of int
| `Page of [`Up | `Down]
| `Home
| `End
| `Insert
| `Delete
| `Backspace ]
(** Type of key code. *)
type code =
[`Uchar of Uchar.t (** A unicode character. *) | special]
type keystate =
{ctrl: bool; meta: bool; shift: bool; super: bool; code: code}
module KeyS = struct
type t = keystate
let compare = compare
end
module Bind = struct
(* parts stolen from lambda-term/src/lTerm_{edit,key}.ml{,i} *)
module S = Zed_input.Make (KeyS)
type 'a t = 'a list S.t
type 'a resolver = 'a list S.resolver
type 'a result = 'a list S.result
type 'a state =
{ mutable bindings: 'a t
; mutable state: 'a result
; mutable last_keyseq: keystate list
; mutable last_actions: 'a list }
type mods = Ctrl | Meta | Super | Shift
type key = C of char | U of code
let keystate_of_mods ks m =
List.fold_left
(fun ks m ->
match m with
| Meta -> {ks with meta= true}
| Ctrl -> {ks with ctrl= true}
| Super -> {ks with super= true}
| Shift -> {ks with shift= true} )
ks m
let add events action bindings =
let events =
List.map
(fun (m, k) ->
keystate_of_mods
{ meta= false
; ctrl= false
; super= false
; shift= false
; code=
( match k with
| C c -> `Uchar (Uchar.of_char c)
| U c -> c ) }
m )
events in
S.add events action bindings
let default_resolver b = S.resolver [S.pack (fun x -> x) b]
let get_resolver result default =
match result with S.Continue r -> r | _ -> default
let init bindings =
{bindings; state= S.Rejected; last_keyseq= []; last_actions= []}
let resolve = S.resolve
let empty = S.empty
type action =
| Custom of (unit -> unit)
| CustomLwt of (unit -> unit Lwt.t)
| Zed of Zed_edit.action
let resolve_events (state : 'a state) events =
List.flatten
(List.filter_map
(fun e ->
match e with
| `Key (`Press, (k : keystate)) -> (
( match state.state with
| Continue _ -> ()
| _ -> state.last_keyseq <- [] ) ;
state.state <-
resolve k
(get_resolver state.state
(default_resolver state.bindings) ) ;
state.last_keyseq <- k :: state.last_keyseq ;
match state.state with
| Accepted a ->
state.last_actions <- a ;
Some a
| Rejected ->
state.last_actions <- [] ;
None
| _ -> None )
| _ -> None )
events )
let actions_of_events (state : action state) events =
List.flatten
(List.filter_map
(fun e ->
match e with
| `Key (`Press, (k : keystate)) -> (
( match state.state with
| Continue _ -> ()
| _ -> state.last_keyseq <- [] ) ;
state.state <-
resolve k
(get_resolver state.state
(default_resolver state.bindings) ) ;
state.last_keyseq <- k :: state.last_keyseq ;
match state.state with
| Accepted a ->
state.last_actions <- a ;
Some a
| Rejected ->
state.last_actions <- [] ;
None
| _ -> None )
| _ -> None )
events )
let process bindstate events =
Lwt_list.iter_s
(function
| Custom f -> Lwt.return (f ())
| CustomLwt f -> f ()
| Zed _ -> Lwt.return_unit )
(actions_of_events bindstate events)
end
(* stolen from lambda-term/src/lTerm_{edit,key}.ml{,i} *)
let string_of_code = function
| `Uchar ch ->
if Uchar.is_char ch then F.str "Char '%c'" (Uchar.to_char ch)
else F.str "Char 0x%02x" (Uchar.to_int ch)
| `Enter -> "Enter"
| `Escape -> "Escape"
| `Tab -> "Tab"
| `Arrow `Up -> "Up"
| `Arrow `Down -> "Down"
| `Arrow `Left -> "Left"
| `Arrow `Right -> "Right"
| `Function i -> F.str "F%d" i
| `Page `Up -> "Page Up"
| `Page `Down -> "Page Down"
| `Home -> "Home"
| `End -> "End"
| `Insert -> "Insert"
| `Delete -> "Delete"
| `Backspace -> "Backspace"
let to_string key =
Printf.sprintf
"{ control = %B; meta = %B; shift = %B; super = %B; code = %s }"
key.ctrl key.meta key.shift key.super
(string_of_code key.code)
let to_string_compact key =
let buffer = Buffer.create 32 in
if key.ctrl then Buffer.add_string buffer "Ctrl-" ;
if key.meta then Buffer.add_string buffer "Meta-" ;
if key.shift then Buffer.add_string buffer "Shift-" ;
if key.super then Buffer.add_string buffer "Super-" ;
( match key.code with
| `Uchar ch ->
let code = Uchar.to_int ch in
if Uchar.is_char ch then
match Uchar.to_char ch with
| ( 'a' .. 'z'
| 'A' .. 'Z'
| '0' .. '9'
| '_' | '(' | ')' | '[' | ']' | '{' | '}' | '#' | '~'
| '&' | '$' | '*' | '%' | '!' | '?' | ',' | ';' | ':'
| '/' | '\\' | '.' | '@' | '=' | '+' | '-' ) as ch ->
Buffer.add_char buffer ch
| ' ' -> Buffer.add_string buffer "space"
| _ -> Printf.bprintf buffer "U+%02x" code
else if code <= 0xffff then
Printf.bprintf buffer "U+%04x" code
else Printf.bprintf buffer "U+%06x" code
| `Page `Down -> Buffer.add_string buffer "pgup"
| `Page `Up -> Buffer.add_string buffer "pgdn"
| code ->
Buffer.add_string buffer
(String.lowercase_ascii (string_of_code code)) ) ;
Buffer.contents buffer
end

7
backend_js.ml Normal file
View File

@ -0,0 +1,7 @@
include Backend
module Keycode = struct
open Js_of_ocaml
type t = Dom_html.Keyboard_code.t
end

438
backend_sdl.ml Normal file
View File

@ -0,0 +1,438 @@
module Key = struct
let sdlkey_map = Hashtbl.create 1024
let () =
let aa (x : int) (y : Key.code) = Hashtbl.add sdlkey_map x y in
aa return `Enter ;
aa escape `Escape ;
aa backspace `Backspace ;
aa tab `Tab ;
aa f1 (`Function 1) ;
aa f2 (`Function 2) ;
aa f3 (`Function 3) ;
aa f4 (`Function 4) ;
aa f5 (`Function 5) ;
aa f6 (`Function 6) ;
aa f7 (`Function 7) ;
aa f8 (`Function 8) ;
aa f9 (`Function 9) ;
aa f10 (`Function 10) ;
aa f11 (`Function 11) ;
aa f12 (`Function 12) ;
aa insert `Insert ;
aa delete `Delete ;
aa home `Home ;
aa kend `End ;
aa pageup (`Page `Up) ;
aa pagedown (`Page `Down) ;
aa right (`Arrow `Right) ;
aa left (`Arrow `Left) ;
aa down (`Arrow `Down) ;
aa up (`Arrow `Up)
let key_of_sdlkey ev =
let (kc : Sdl.keycode) =
Sdl.Event.get ev Sdl.Event.keyboard_keycode
land lnot Sdl.K.scancode_mask in
match (Hashtbl.find_opt sdlkey_map kc, Uchar.is_valid kc) with
| Some s, _ -> Some s
| None, true -> Some (`Uchar (Uchar.of_int kc))
| None, false -> None
let event_of_sdlevent ev : t option =
match Sdl.Event.enum (Sdl.Event.get ev Sdl.Event.typ) with
| (`Key_down | `Key_up) as d -> (
match key_of_sdlkey ev with
| None -> None
| Some code ->
let km = Sdl.Event.get ev Sdl.Event.keyboard_keymod in
Some
(`Key
( ( match d with
| _
when Sdl.Event.get ev Sdl.Event.keyboard_repeat > 1
->
`Repeat
| `Key_up -> `Release
| _ -> `Press )
, { code
; ctrl= km land Sdl.Kmod.ctrl > 0
; meta= km land Sdl.Kmod.alt > 0
; super= km land Sdl.Kmod.gui > 0
; shift= km land Sdl.Kmod.shift > 0 } ) ) )
| `Mouse_motion ->
let x, y = snd (Tsdl.Sdl.get_mouse_state ()) in
Some (`Mouse (V2.v (float x) (float y)))
| `Quit -> Some `Quit
(* Unhandled events *)
| `Text_editing -> Some (`Unknown "`Text_editing")
| `Text_input -> Some (`Unknown "`Text_input")
| `App_did_enter_background ->
Some (`Unknown "`App_did_enter_background")
| `App_did_enter_foreground ->
Some (`Unknown "`App_did_enter_foreground ")
| `App_low_memory -> Some (`Unknown "`App_low_memory ")
| `App_terminating -> Some (`Unknown "`App_terminating ")
| `App_will_enter_background ->
Some (`Unknown "`App_will_enter_background ")
| `App_will_enter_foreground ->
Some (`Unknown "`App_will_enter_foreground ")
| `Clipboard_update -> Some (`Unknown "`Clipboard_update ")
| `Controller_axis_motion ->
Some (`Unknown "`Controller_axis_motion ")
| `Controller_button_down ->
Some (`Unknown "`Controller_button_down ")
| `Controller_button_up -> Some (`Unknown "`Controller_button_up ")
| `Controller_device_added ->
Some (`Unknown "`Controller_device_added ")
| `Controller_device_remapped ->
Some (`Unknown "`Controller_device_remapped ")
| `Controller_device_removed ->
Some (`Unknown "`Controller_device_removed ")
| `Dollar_gesture -> Some (`Unknown "`Dollar_gesture ")
| `Dollar_record -> Some (`Unknown "`Dollar_record ")
| `Drop_file -> Some (`Unknown "`Drop_file ")
| `Finger_down -> Some (`Unknown "`Finger_down")
| `Finger_motion -> Some (`Unknown "`Finger_motion ")
| `Finger_up -> Some (`Unknown "`Finger_up ")
| `Joy_axis_motion -> Some (`Unknown "`Joy_axis_motion ")
| `Joy_ball_motion -> Some (`Unknown "`Joy_ball_motion ")
| `Joy_button_down -> Some (`Unknown "`Joy_button_down ")
| `Joy_button_up -> Some (`Unknown "`Joy_button_up ")
| `Joy_device_added -> Some (`Unknown "`Joy_device_added ")
| `Joy_device_removed -> Some (`Unknown "`Joy_device_removed ")
| `Joy_hat_motion -> Some (`Unknown "`Joy_hat_motion ")
| `Mouse_button_down -> Some (`Unknown "`Mouse_button_down ")
| `Mouse_button_up -> Some (`Unknown "`Mouse_button_up")
| `Mouse_wheel -> Some (`Unknown "`Mouse_wheel ")
| `Multi_gesture -> Some (`Unknown "`Multi_gesture")
| `Sys_wm_event -> Some (`Unknown "`Sys_wm_event ")
| `Unknown e -> Some (`Unknown (Format.sprintf "`Unknown %d" e))
| `User_event -> Some (`Unknown "`User_event ")
| `Display_event -> Some (`Unknown "`Display_event ")
| `Sensor_update -> Some (`Unknown "`Sensor_update ")
| `Window_event -> Some (`Unknown "`Window_event ")
let key_up : Sdl.keycode = 0x40000052
let key_down : Sdl.keycode = 0x40000051
let key_left : Sdl.keycode = 0x40000050
let key_right : Sdl.keycode = 0x4000004f
end
module Display = struct
open Tgles2
open Tsdl
open Gg
open Wall
module I = Image
module P = Path
let ( >>>= ) x f =
match x with Ok a -> f a | Error _ as result -> result
let get_result = function
| Ok x -> x
| Error (`Msg msg) -> failwith msg
(* current window state to be passed to window renderer *)
type state =
{ box: box2
(* This is cannonically box within which the next element should draw *)
; time: float
; wall: Wall.renderer }
(* the box2 here is cannonically the place the returner drew
(the Wall.image extents) *)
type image = box2 * Wall.image
type pane = state -> state * image
type actor = (Event.events -> pane Lwt.t) ref
let pane_empty s =
(s, (Box2.of_pts (Box2.o s.box) (Box2.o s.box), Image.empty))
type frame =
{ sdl_win: Sdl.window
; gl: Sdl.gl_context
; wall: Wall.renderer
; mutable last_pane: pane
; mutable quit: bool
; mutable fullscreen: bool }
let ticks () = Int32.to_float (Sdl.get_ticks ()) /. 1000.
let on_failure ~cleanup result =
(match result with Ok _ -> () | Error _ -> cleanup ()) ;
result
let video_initialized = lazy (Sdl.init Sdl.Init.video)
let make_frame ?(title = "komm") ~w ~h () =
Lazy.force video_initialized
>>>= fun () ->
Sdl.create_window ~w ~h title
Sdl.Window.(
opengl + allow_highdpi + resizable (*+ input_grabbed*))
>>>= fun sdl_win ->
Sdl.set_window_title sdl_win title ;
ignore (Sdl.gl_set_swap_interval (-1)) ;
ignore (Sdl.gl_set_attribute Sdl.Gl.stencil_size 1) ;
on_failure
( Sdl.gl_create_context sdl_win
>>>= fun gl ->
let wall =
Wall.Renderer.create ~antialias:true ~stencil_strokes:true ()
in
Ok
{ sdl_win
; gl
; wall
; quit= false
; fullscreen= false
; last_pane= pane_empty } )
~cleanup:(fun () -> Sdl.destroy_window sdl_win)
let handle_frame_events frame events =
List.iter
(fun (e : Event.t) ->
match e with
| `Quit -> frame.quit <- true
| `Fullscreen a ->
frame.fullscreen <- a ;
ignore (Sdl.show_cursor (not frame.fullscreen) : _ result) ;
ignore
( Sdl.set_window_fullscreen frame.sdl_win
( if frame.fullscreen then
Sdl.Window.fullscreen_desktop
else Sdl.Window.windowed )
: _ result )
| _ -> () )
events
let draw_pane frame pane =
let width, height = Sdl.gl_get_drawable_size frame.sdl_win in
let _, (_, image) =
pane
{ box= Box2.v (P2.v 0. 0.) (P2.v (float width) (float height))
; time= ticks ()
; wall= frame.wall } in
Sdl.gl_make_current frame.sdl_win frame.gl
>>>= fun () ->
Gl.viewport 0 0 width height ;
Gl.clear_color 0.0 0.0 0.0 1.0 ;
Gl.(
clear
(color_buffer_bit lor depth_buffer_bit lor stencil_buffer_bit)) ;
Gl.enable Gl.blend ;
Gl.blend_func_separate Gl.one Gl.src_alpha Gl.one
Gl.one_minus_src_alpha ;
Gl.enable Gl.cull_face_enum ;
Gl.disable Gl.depth_test ;
let width = float width and height = float height in
Wall.Renderer.render frame.wall ~width ~height image ;
Sdl.gl_swap_window frame.sdl_win ;
Ok ()
let rec get_events () : Event.t list =
(* create and fill event list *)
let ev = Sdl.Event.create () in
if Sdl.poll_event (Some ev) then
match Event.event_of_sdlevent ev with
| Some e -> get_events () @ [e]
| None -> get_events ()
else []
let successful_actor = ref (fun _ -> Lwt.return pane_empty)
let display_frame frame (actor : actor) =
let events = get_events () in
handle_frame_events frame events ;
if List.length events > 0 then (
(* recompute the actor definition with the new events to return a new pane *)
( try
!actor events
>|= fun p ->
successful_actor := !actor ;
p
with e ->
F.epr
"Display.display_frame (!actor events) failed with:@. %s \
@."
(Printexc.to_string e) ;
actor := !successful_actor ;
!actor events )
>>= fun p ->
frame.last_pane <- p ;
(* call draw_pane because we should redraw now that we have updated *)
ignore (draw_pane frame frame.last_pane) ;
Lwt.return_unit )
else Lwt.return_unit
let run frame actor () =
let frame = get_result frame in
Sdl.show_window frame.sdl_win ;
let rec loop () =
Lwt.pause () (* seems required for the irc connection to work *)
>>= fun () ->
Lwt_unix.sleep 0.030
>>= fun () ->
display_frame frame actor
>>= fun () ->
if not frame.quit then loop () else Lwt.return_unit in
Lwt_main.run (loop ()) ;
print_endline "quit" ;
Sdl.hide_window frame.sdl_win ;
Sdl.gl_delete_context frame.gl ;
Sdl.destroy_window frame.sdl_win ;
Sdl.quit () ;
()
let gray ?(a = 1.0) v = Color.v v v v a
module FontCache = Map.Make (String)
let font_cache = ref FontCache.empty
let load_font name =
match FontCache.find_opt name !font_cache with
| Some font -> font
| None -> (
let ic = open_in_bin name in
let dim = in_channel_length ic in
let fd = Unix.descr_of_in_channel ic in
let buffer =
Unix.map_file fd Bigarray.int8_unsigned Bigarray.c_layout
false [|dim|]
|> Bigarray.array1_of_genarray in
let offset = List.hd (Stb_truetype.enum buffer) in
match Stb_truetype.init buffer offset with
| None -> assert false
| Some font ->
font_cache := FontCache.add name font !font_cache ;
font )
let font_icons = lazy (load_font "fonts/entypo.ttf")
let font_sans = lazy (load_font "fonts/Roboto-Regular.ttf")
let font_sans_bold = lazy (load_font "fonts/Roboto-Bold.ttf")
let font_sans_light = lazy (load_font "fonts/Roboto-Light.ttf")
let font_sans_italic = lazy (load_font "fonts/Roboto-Italic.ttf")
let font_sans_bold_italic =
lazy (load_font "fonts/Roboto-BoldItalic.ttf")
let font_serif =
lazy (load_font "fonts/ScheherazadeNew-Regular.ttf")
let font_serif_bold =
lazy (load_font "fonts/ScheherazadeNew-Bold.ttf")
let font_mono = lazy (load_font "fonts/static/RobotoMono-Regular")
let font_mono_bold =
lazy (load_font "fonts/static/RobotoMono-Regular")
let font_mono_light =
lazy (load_font "fonts/static/RobotoMono-Regular")
let font_emoji = lazy (load_font "fonts/NotoEmoji-Regular.ttf")
let str_of_box b =
Printf.sprintf "(ox:%0.1f oy:%0.1f ex%0.1f ey%0.1f)" (Box2.ox b)
(Box2.oy b) (Box2.maxx b) (Box2.maxy b)
let draw_label text b =
let f =
Wall_text.Font.make ~size:(Box2.h b) (Lazy.force font_sans)
in
( Box2.v (Box2.o b)
(P2.v (Wall_text.Font.text_width f text) (Box2.h b))
, I.paint
(Paint.color (gray ~a:0.5 1.0))
Wall_text.(
simple_text f ~valign:`BASELINE ~halign:`LEFT ~x:(Box2.ox b)
~y:(Box2.oy b +. (Box2.h b *. 0.75))
text) )
let fill_box c b =
( b
, I.paint (Paint.color c)
( I.fill_path
@@ fun t ->
P.rect t ~x:(Box2.ox b) ~y:(Box2.oy b) ~w:(Box2.w b)
~h:(Box2.h b) ) )
let draw_filled_box c (s : state) = (s, fill_box c s.box)
let path_box c b (s : state) =
( s
, ( b
, I.paint (Paint.color c)
( I.stroke_path (Outline.make ())
@@ fun t ->
P.rect t ~x:(Box2.ox b) ~y:(Box2.oy b) ~w:(Box2.w b)
~h:(Box2.h b) ) ) )
let path_circle c b (s : state) =
( s
, ( b
, I.paint (Paint.color c)
( I.stroke_path (Outline.make ())
@@ fun t ->
P.circle t ~cx:(Box2.midx b) ~cy:(Box2.midy b)
~r:(Box2.w b /. 2.) ) ) )
(** 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
*)
let simple_text f text (s : state) =
let fm = Wall_text.Font.font_metrics f in
let font_height = fm.ascent -. fm.descent +. fm.line_gap in
let tm = Wall_text.Font.text_measure f text in
let br_pt =
P2.v (Box2.ox s.box +. tm.width) (Box2.oy s.box +. font_height)
in
let bextent = Box2.of_pts (Box2.o s.box) br_pt in
(* let _, (_, redbox) = path_box Color.red bextent s in*)
( {s with box= Box2.of_pts (Box2.br_pt bextent) (Box2.max s.box)}
, ( bextent
, (* I.stack redbox *)
I.paint
(Paint.color (gray ~a:0.5 1.0))
Wall_text.(
simple_text f ~valign:`BASELINE ~halign:`LEFT
~x:(Box2.ox s.box)
~y:(Box2.oy s.box +. fm.ascent)
text) ) )
let pane_box next_point_func (subpanes : pane list) (so : state) =
let sr, (br, ir) =
List.fold_left
(fun (sp, (bp, ip)) (pane : pane) ->
(* uses br to hold max extent of boxes *)
let sr, (br, ir) = pane sp in
(* draw the pane *)
let _, (_, irb) = path_box Color.blue br sr in
(* draw the box around the pane *)
( { sr with
box= Box2.of_pts (next_point_func br) (Box2.max sp.box)
}
, ( Box2.of_pts (Box2.o bp)
(P2.v
(max (Box2.maxx br) (Box2.maxx bp))
(max (Box2.maxy br) (Box2.maxy bp)) )
, Image.seq [ip; irb; ir] ) ) )
( so
, (Box2.of_pts (Box2.o so.box) (Box2.o so.box), Image.empty)
)
subpanes in
let _, (_, redbox) = path_box Color.red br sr in
(sr, (br, Image.stack redbox ir))
end

123
boot_js.ml Normal file
View File

@ -0,0 +1,123 @@
open Js_of_ocaml
open Lwt.Infix
module NVG = Graphv_webgl
let _ =
Logs.set_reporter (Human.Logs_reporter.console_reporter ());
Logs.set_level (Some Debug)
module Log = (val Logs.src_log Logs.default : Logs.LOG)
(* This scales the canvas to match the DPI of the window,
it prevents blurriness when rendering to the canvas *)
let scale_canvas (canvas : Dom_html.canvasElement Js.t) =
let dpr = Dom_html.window##.devicePixelRatio in
let rect = canvas##getBoundingClientRect in
let width = rect##.right -. rect##.left in
let height = rect##.bottom -. rect##.top in
canvas##.width := width *. dpr |> int_of_float;
canvas##.height := height *. dpr |> int_of_float;
let width =
Printf.sprintf "%dpx" (int_of_float width) |> Js.string
in
let height =
Printf.sprintf "%dpx" (int_of_float height) |> Js.string
in
canvas##.style##.width := width;
canvas##.style##.height := height
let webgl_initialize canvas =
scale_canvas canvas;
(* Graphv requires a stencil buffer to work properly *)
let attrs = WebGL.defaultContextAttributes in
attrs##.stencil := Js._true;
match
WebGL.getContextWithAttributes canvas attrs |> Js.Opt.to_option
with
| None ->
print_endline "Sorry your browser does not support WebGL";
raise Exit
| Some ctx -> ctx
let graphv_initialize webgl_ctx =
let open NVG in
let vg =
create
~flags:CreateFlags.(antialias lor stencil_strokes)
webgl_ctx
in
(* File in this case is actually the CSS font name *)
Text.create vg ~name:"sans" ~file:"sans" |> ignore;
webgl_ctx##clearColor 0.3 0.3 0.32 1.;
vg
let request_animation_frame () =
let t, s = Lwt.wait () in
let (_ : Dom_html.animation_frame_request_id) =
Dom_html.window##requestAnimationFrame
(Js.wrap_callback (fun (time : float) -> Lwt.wakeup s time))
in
t
let render_stream canvas webgl_ctx vg
(render : NVG.t -> ?time:float -> Gg.p2 -> Human.I.t -> unit) :
Human.I.t Lwt_stream.t -> unit Lwt.t =
Lwt_stream.iter_n (fun i ->
request_animation_frame () >>= fun time ->
webgl_ctx##clear
(webgl_ctx##._COLOR_BUFFER_BIT_
lor webgl_ctx##._DEPTH_BUFFER_BIT_
lor webgl_ctx##._STENCIL_BUFFER_BIT_);
let device_ratio = Dom_html.window##.devicePixelRatio in
NVG.begin_frame vg ~width:canvas##.width ~height:canvas##.height
~device_ratio;
NVG.Transform.scale vg ~x:device_ratio ~y:device_ratio;
render vg ~time Gg.P2.o i;
NVG.end_frame vg;
Lwt.return_unit)
open Human
let _ =
let canvas =
Js.Unsafe.coerce (Dom_html.getElementById_exn "canvas")
in
let webgl_ctx = webgl_initialize canvas in
let vg = graphv_initialize webgl_ctx in
let open Js_of_ocaml_lwt.Lwt_js_events in
let open Nottui in
let gravity_pad = Gravity.make ~h:`Negative ~v:`Negative in
let gravity_crop = Gravity.make ~h:`Positive ~v:`Negative in
let body = Lwd.var (Lwd.pure Ui.empty) in
let wm = Widgets.window_manager (Lwd.join (Lwd.get body)) in
Nav.test_populate () >>= fun test_store ->
let ui = Widgets.(h_node_area (test_store, [ [] ])) in
let root =
Lwd.set body
(Lwd.map ~f:(Ui.resize ~pad:gravity_pad ~crop:gravity_crop) ui);
Widgets.window_manager_view wm
in
let events, push_event = Lwt_stream.create () in
let images =
Human.Nottui_lwt.render vg
~size:(Gg.P2.v canvas##.width canvas##.height)
events root
in
async (fun () ->
render_stream canvas webgl_ctx vg
(fun vg ?(time = 0.) p i ->
let _ = time in
Log.debug (fun m ->
m "Drawing image: p=%a n=%a" Gg.V2.pp p
(I.Draw.pp ~attr:A.dark)
i);
let p' = I.Draw.node vg A.dark p i in
Logs.debug (fun m ->
m "Drawing finished: p'=%a" Gg.V2.pp p'))
images);
buffered_loop (make_event Dom_html.Event.keydown) Dom_html.document
(fun ev _ ->
Dom.preventDefault ev;
Lwt.return
@@ push_event (Some (`Keys [ Event_js.evt_of_jskey ev ])))

7
cors_proxy.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
if [ ! -f /tmp/key.pem ]; then
echo Creating key
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout /tmp/key.pem -out /tmp/cert.pem -batch
fi
npx http-server --cors -S -P https://gitea.departmentofinter.net --log-ip -c-1 -C /tmp/cert.pem -K /tmp/key.pem

210
dune
View File

@ -1,74 +1,160 @@
(env
(dev
(flags (:standard -warn-error -A))))
(dev (flags (:standard -warn-error -A))
(js_of_ocaml (flags :standard)
(build_runtime_flags :standard --no-inline --debug-info)
(compilation_mode whole_program)
(link_flags :standard))))
(library
(name human)
(name log_js)
(modes byte)
(modules human)
(preprocess (pps js_of_ocaml-ppx))
(flags (:standard -rectypes -linkall))
(modules log_js)
(libraries
topinf
lwt_ppx
tsdl
tgls.tgles2
wall
zed
lambda-term
irmin-unix
nottui-pretty
ocaml-compiler-libs.common
ocaml-compiler-libs.bytecomp
ocaml-compiler-libs.toplevel))
logs))
(executable
(name irc)
(modes byte)
(modules irc)
(name boot_js)
(modes byte js)
(preprocess (pps js_of_ocaml-ppx))
(flags (:standard -rectypes -linkall))
(modules boot_js human)
(libraries
human
fmt
graphv_webgl
js_of_ocaml-lwt
js_of_ocaml-compiler
js_of_ocaml-toplevel
digestif.ocaml
checkseum.ocaml
irmin.mem
git
irmin-git
cohttp-lwt-jsoo
mimic
uri
gg
lwd
log_js))
(rule
(targets test_dynlink.cmo test_dynlink.cmi)
(action
(run ocamlc -c %{dep:test_dynlink.ml})))
(rule
(targets test_dynlink.js)
(action
(run %{bin:js_of_ocaml} --pretty --toplevel %{dep:test_dynlink.cmo})))
(rule
(targets embedded_fs.js)
(action
(run %{bin:jsoo_fs} -I . -o %{targets} %{dep:examples.ml} %{dep:test_dynlink.js})))
(rule
(targets export.txt)
(deps
(package js_of_ocaml-ppx)
(package js_of_ocaml)
(package js_of_ocaml-compiler)
(package js_of_ocaml-lwt)
(package js_of_ocaml-tyxml)
(package js_of_ocaml-toplevel))
(action
(run
jsoo_listunits
-o
%{targets}
stdlib
graphics
str
dynlink
js_of_ocaml-compiler.runtime
js_of_ocaml-lwt.graphics
js_of_ocaml-ppx.as-lib
js_of_ocaml.deriving
lwt
fmt
topinf
lwt_ppx
irc-client
irc-client-lwt
irc-client-unix
irc-client-tls
nottui-lwt
nottui-pretty
))
tyxml.functor
tyxml.functor:html_types.cmi
react
reactiveData
js_of_ocaml
js_of_ocaml-lwt
js_of_ocaml-tyxml
js_of_ocaml-toplevel
dynlink)))
(executable
(name boot)
(modes byte)
(modules boot)
(link_flags (-linkall))
(executables
(names toplevel)
(modules toplevel ppx_graph)
(flags
(:standard -rectypes -linkall))
(modes js)
(js_of_ocaml
(flags
compile
--pretty
--Werror
--target-env
browser
--export
%{dep:export.txt}
--toplevel
--disable
shortvar
+toplevel.js
+dynlink.js
%{dep:embedded_fs.js}))
(preprocess
(pps js_of_ocaml-ppx))
(libraries
lwt_ppx
lambda-term
topinf))
js_of_ocaml-compiler
js_of_ocaml-tyxml
js_of_ocaml-toplevel
lwt
js_of_ocaml-lwt
;; not used directly
graphics
js_of_ocaml.deriving
js_of_ocaml-lwt.graphics
js_of_ocaml-ppx.as-lib
compiler-libs
compiler-libs.common
compiler-libs.bytecomp
js_of_ocaml-compiler.runtime
ocp-indent.lib
react
reactiveData
str
log_js)
)
(library
(name topinf)
(modes byte)
(modules topinf)
(libraries
fmt
tsdl
tgls.tgles2
wall
zed
lambda-term
irmin-unix
nottui
nottui-pretty
nottui-lwt
uuseg
irc-client
irc-client-lwt
irc-client-unix
irc-client-tls
ocaml-compiler-libs.common
ocaml-compiler-libs.bytecomp
ocaml-compiler-libs.toplevel))
; (rule
; (targets toplevel.js)
; (deps examples.ml)
; (action
; (run
; %{bin:js_of_ocaml}
; compile
; --pretty
; --Werror
; --target-env
; browser
; --extern-fs
; "--file=%{dep:examples.ml}"
; --export
; %{dep:export.txt}
; --toplevel
; --disable
; shortvar
; +toplevel.js
; +dynlink.js
; %{dep:toplevel.bc}
; -o
; %{targets})))
(alias
(name default)
(deps toplevel.bc.js index.html toplevel.html))

View File

@ -1,3 +1,2 @@
(lang dune 2.8)
(name komm)
(wrapped_executables false)
(lang dune 3.4)
(name boot)

5607
human.ml

File diff suppressed because it is too large Load Diff

35
index.html Normal file
View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<style>
html, body {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
}
div {
display: flex;
align-items: center;
justify-content: center;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div>
<canvas id='canvas'></canvas>
</div>
</body>
<script
type='text/javascript'
defer
src='_build/default/boot_js.bc.js'>
</script>
</html>

604
irc.ml
View File

@ -14,249 +14,481 @@ open Lwt_react
module F = Fmt
module Communicator = struct
module Message = struct
type t = {content: string; time: string; mutable seen: bool}
let base_path = "communicator"
let topch = "top"
let make ?(time = "<ts>") content = {content; time; seen= false}
module Istore = struct
include Human.Store
let from_storeview (sv : storeview) = sv.store
include Human.Store.Istore
end
module Message = struct
type t = {time: string list; content: string}
let make ?(time = Unix.gettimeofday ()) content =
let tm = Unix.localtime time in
{ time=
List.map string_of_int
[tm.tm_year + 1900; tm.tm_mon + 1; tm.tm_mday; tm.tm_hour]
@ [ string_of_float
( float_of_int (tm.tm_min * tm.tm_sec)
+. fst (modf time) ) ]
; content }
end
module Channel = struct
type t = {name: string; content: Message.t list Lwd.var}
(* a channels step key may not be blank (i.e. "") *)
type t = {store: Istore.t; path: Istore.key}
let add_msg (c : t) (msg : Message.t) =
F.epr "Channel.add_msg msg.content=\"%s\"@." msg.content ;
let cn = Lwd.peek c.content in
Lwd.set c.content (msg :: cn)
let make (store : Istore.t) ~path ~(name : string) =
Lwt.return {store; path= path @ ["#" ^ name]}
let make name = {name; content= Lwd.var []}
let add_msg {store; path} (msg : Message.t) : unit Lwt.t =
F.epr "add_msg path=[" ;
F.list ~sep:F.semi F.string F.stderr (path @ msg.time) ;
F.epr "] content=%s @." msg.content ;
Istore.set_exn store ~info:Irmin.Info.none (path @ msg.time)
msg.content
end
module Tree = struct
open Channel
open Message
type t =
{ channel: Channel.t
; subs: t Lwd_table.t
; focus: Nottui.Focus.handle }
type selection = Istore.Key.t
type t = {store: Istore.t; view: Istore.key}
type protocol = Irc | Email | Rss | Mublog | ActivityPub
let contents {store; view} (s : selection) :
Istore.Contents.t option Lwt.t =
Istore.find store (view @ s)
let add (comm : t) ch : unit =
let c' =
{ channel= ch
; subs= Lwd_table.make ()
; focus= Nottui.Focus.make () } in
Lwd_table.append' comm.subs c'
let make_top ?(view = [base_path]) gitpath branchname : t Lwt.t =
Istore.Repo.v (Irmin_git.config gitpath)
>>= fun repo ->
Istore.of_branch repo branchname
>>= fun store ->
let t = {store; view} in
Channel.make store ~path:view ~name:topch
>>= fun ch_top ->
Channel.add_msg ch_top
(Message.make "Communicator restarting...")
>>= fun () ->
Channel.add_msg ch_top
(Message.make "Currently only IRC is implemented")
>>= fun () -> Lwt.return t
let make_top () =
let channel = Channel.make "communicator-top" in
add_msg channel (Message.make "Welcome to the Communicator") ;
add_msg channel
(Message.make "Currently only IRC is implemented") ;
{channel; subs= Lwd_table.make (); focus= Nottui.Focus.make ()}
let add {store; view} ~(name : string list) ~(config : Istore.tree)
: t Lwt.t =
Istore.get_tree store name
>>= fun tree ->
Istore.Tree.remove tree ["_config"]
>>= fun tree ->
Istore.Tree.add_tree tree [] config
>>= fun tree ->
Istore.set_tree_exn ~info:Irmin.Info.none store name tree
>>= fun () -> Lwt.return {store; view}
end
module Protocol = struct
type t = Irc | Email | Rss | Mublog | ActivityPub
let to_string = function
| Irc -> ("IRC", "Internet Relay Chat")
| Email -> ("E-mail", "Electronic Mail")
| Rss -> ("RSS", "Really Simple Subscriptions???")
| Mublog -> ("uBlog", "Microblogging (Twitter)")
| ActivityPub -> ("ActivityPub", "Mastodon, etc.")
let id t = fst (to_string t)
let desc t = snd (to_string t)
end
module Irc = struct
module C = Irc_client_tls
module M = Irc_message
let connection (c : Tree.t) server port nick
(channels : string list) : Channel.t =
let channel =
Channel.make ("IRC: " ^ server ^ ":" ^ string_of_int port)
in
let _c' = Tree.add c channel in
let add_msg str = Channel.add_msg channel (Message.make str) in
let channel_assoc = ref [] in
let make_ch name =
let ch = Channel.make name in
Tree.add c ch ;
channel_assoc := (name, ch) :: !channel_assoc ;
ch in
Lwt.async
(C.reconnect_loop ~after:30
~connect:(fun () ->
add_msg "Connecting..." ;
C.connect_by_name ~server ~port ~nick ()
module Config = struct
type t = Istore.tree
open Lwt.Infix
let path = "_config"
let make_connection Tree.{store; view} server port nick =
let name = F.str "%s@%s:%d" nick server port in
Istore.Tree.add Istore.Tree.empty ["server"] server
>>= fun t' ->
Istore.Tree.add t' ["port"] (string_of_int port)
>>= fun t' ->
Istore.Tree.add t' ["nick"] nick
>>= fun t' ->
Istore.Tree.add t' ["protocol"] (Protocol.id Irc)
>>= fun t' ->
F.epr "Creating connection config /%s/%s/@." name path ;
Istore.set_tree_exn ~info:Irmin.Info.none store
(view @ [name; path])
t'
>>= fun _ -> Lwt.return_unit
let server t : string Lwt.t = Istore.Tree.get t [path; "server"]
let port t : int Lwt.t =
Istore.Tree.get t [path; "port"] >|= fun p -> int_of_string p
let nick t : string Lwt.t = Istore.Tree.get t [path; "nick"]
let protocol t : string option Lwt.t =
Istore.Tree.find t [path; "protocol"]
end
let get_channels ~store ~path =
Istore.list store path
>>= fun c ->
Lwt_io.printl "connect_by_name returned"
>>= fun () -> Lwt.return c )
let rec iter l =
Lwt_list.filter_map_p
(fun (s, _) ->
if String.length s > 1 && String.get s 0 = '#' then
Lwt.return (Some s)
else Lwt.return None )
l in
iter c
let connect ?(path = [base_path]) ({store; _} : Tree.t) :
unit Lwt.t =
(* search for all connections and start them *)
(* also need ot figure out how to preserve custom ordering of items like servers and channels
maybe just a _order file that has the ordering of files listed and hten gets updated etc. *)
Channel.make store ~path ~name:topch
>>= fun top_channel ->
let _top_msg str =
Channel.add_msg top_channel (Message.make str) in
let channel_assoc = ref [] in
let make_channel store path (name : string) =
Channel.make store ~path ~name
>>= fun ch ->
channel_assoc := (name, ch) :: !channel_assoc ;
Channel.add_msg ch
(Message.make (F.str "channel %s created" name))
>>= fun () -> Lwt.return ch in
Istore.list store path
>>= fun servers ->
Lwt_list.filter_p
(fun (_, tree) ->
Config.protocol tree
>|= function Some p -> p = Protocol.id Irc | None -> false
)
servers
(* filter out non-irc protocols, TODO currently relying on this to filter out non-server folders too *)
>>= fun servers ->
F.epr "protocols filtered for irc@." ;
Lwt_list.iter_p
(fun (name, tree) ->
F.epr "Irc.connect server=%s @." name ;
Config.nick tree
>>= fun nick ->
Config.server tree
>>= fun server ->
Config.port tree
>>= fun port ->
Channel.make store ~path:(path @ [name]) ~name:topch
>>= fun server_channel ->
let add_msg s =
Channel.add_msg server_channel (Message.make s) in
C.reconnect_loop ~after:30
~connect:(fun () ->
add_msg "Connecting..."
>>= fun () ->
C.connect_by_name ~server ~port ~nick ()
>>= fun c -> Lwt.return c )
~f:(fun connection ->
add_msg "Connected" ;
F.epr "Irc.connect C.reconnect_loop ~f:(Connected...)@." ;
add_msg "Connected"
>>= fun () ->
get_channels ~store ~path:[name]
>>= fun chs ->
Lwt_list.iter_p
(fun chname ->
C.send_join ~connection ~channel:chname
>>= fun () ->
ignore (make_ch chname) ;
ignore (make_channel store [name] chname) ;
Lwt.return_unit )
channels )
chs )
~callback:(fun _connection result ->
match result with
| Result.Ok ({M.command= M.Other _; _} as msg) ->
add_msg (M.to_string msg) ;
Lwt.return_unit
add_msg (M.to_string msg)
| Result.Ok
{M.command= M.PRIVMSG (target, data); prefix= user}
->
-> (
let user =
match user with
| Some u -> List.hd (String.split_on_char '!' u)
| None -> "unknown" in
( match List.assoc_opt target !channel_assoc with
| Some ch -> Channel.add_msg ch
| None -> Channel.add_msg (make_ch target) )
(Message.make (F.str "<%s> %s" user data)) ;
Lwt.return_unit
match List.assoc_opt target !channel_assoc with
| Some ch ->
Channel.add_msg ch
(Message.make (F.str "<%s> %s" user data))
| None ->
make_channel store [server] target
>>= fun ch ->
Channel.add_msg ch
(Message.make (F.str "<%s> %s" user data)) )
| Result.Ok msg ->
add_msg (M.to_string msg) ;
Lwt.return_unit
| Result.Error e -> Lwt_io.printl e ) ) ;
channel
add_msg (M.to_string msg)
>>= fun () -> Lwt.return_unit
| Result.Error e -> Lwt_io.printl e )
() )
servers
end
module Panel = struct
open Nottui
module P = Nottui_pretty
open Panel
open Panel.Ui
let string ?attr text = P.ui (Nottui_widgets.string ?attr text)
let ( ^^ ) = P.( ^^ )
let ( ^/^ ) a b = P.(a ^^ break 1 ^^ b)
type viewer =
{ step: string
; var: view Lwd.var
; mutable parent: view
; mutable node: viewer list }
let messagelist (ch : Channel.t) : P.t Lwd.t =
Lwd.map (Lwd.get ch.content) ~f:(fun (msgs : Message.t list) ->
List.fold_left
(fun doc (msg : Message.t) ->
F.epr "Communicator.Panel.messagelist ch.content=%s@."
msg.content ;
doc
^^ P.group
( string msg.time ^/^ string " | "
^/^ string msg.content )
^^ P.hardline )
P.empty msgs )
and view = [`Empty | `View of viewer]
open Nottui_widgets
let add v node =
( match v with
| `View v ->
v.node <- node :: v.node ;
Lwd.set v.var (`View v)
| `Empty -> () ) ;
node.parent <- v ;
Lwd.set node.var (`View node) ;
`View node
(*type focustree =
{channel: Channel.t; subs: focustree list; focus: Focus.handle}
let make step parent node =
let v = {step; var= Lwd.var `Empty; parent; node} in
( match parent with
| `View parent ->
parent.node <- v :: parent.node ;
Lwd.set parent.var (`View parent)
| `Empty -> () ) ;
let rec iter = function
| [] -> ()
| x :: xs ->
x.parent <- `View v ;
Lwd.set x.var (`View x) ;
iter xs in
iter node ;
Lwd.set v.var (`View v) ;
`View v
let channeltree (tree : Tree.t) : focustree Lwd.t =
let rec fold (tree : Tree.t) : focustree list Lwd.t =
Lwd_table.map_reduce
(fun _row (tree : Tree.t) ->
Lwd.map (fold tree) ~f:(fun (subs : focustree list) ->
{ channel= tree.channel
; subs
; focus= Focus.make () } ))
([], fun a b -> List.append a b)
tree.subs in
let {channel= tree.channel; subs= fold tree; focus= Focus.make ()} *)
let rec last = function
| [] -> None
| [x] -> Some x
| _ :: xs -> last xs
let channelview (tree : Tree.t) : 'a Lwd.t * Channel.t Lwd.var =
let channel = Lwd.var tree.channel in
let rec fold ?(indent = 0) ?superfocus (tree : Tree.t) :
'a Lwd.t =
let subfocus = Focus.make () in
let rec last_def = function
| [] -> "[]"
| [x] -> x
| _ :: xs -> last_def xs
let find_node ~step ~view =
match view with
| `Empty -> None
| `View v -> List.find_opt (fun v' -> v'.step = step) v.node
let string_of_path path =
"[" ^ F.str "%a" (F.list ~sep:F.semi F.string) path ^ "]"
let remove (v : viewer) =
Lwd.set v.var `Empty ;
`Empty
let storeview store path =
Istore.get_tree store path
>>= fun tree ->
let update d key (view : view) : view option Lwt.t =
F.epr "fold ~pre:update key=%s @." (string_of_path key) ;
Lwt.return
( match
( List.rev key
, find_node
~step:(Option.value (last key) ~default:"[]")
~view
, d )
with
| [], None, `Added | [], None, `Updated ->
Some (make "[]" view [])
| [], Some v, _ -> Some (`View v)
| [], None, `Removed -> None
| _ :: k :: _, _, _ when k.[0] == '#' -> None
| k :: _, None, `Added | k :: _, None, `Updated ->
Some (make k view [])
| _ :: _, None, `Removed -> None
| _ :: _, Some v, _ -> Some (`View v) ) in
(* if pre returns None, the children of that node are skipped. *)
let rec map ?(key = []) ~node tree (acc : view) : view Lwt.t =
let acc =
match acc with
| `Empty -> make (last_def key) acc []
| v -> v in
Istore.Tree.list tree []
>>= fun tree ->
Lwt_list.iter_s
(fun (s, t) ->
let k = key @ [s] in
node k acc
>|= function
| Some a ->
F.epr "storeview Fold step=%s @." s ;
ignore (map ~key:k ~node t a)
| None -> F.epr "storeview None step=%s @." s )
tree
>|= fun () -> acc in
map ~node:(update `Added) tree `Empty
>>= fun t ->
let root = Lwd.var t in
Istore.watch_key store path (fun diff ->
let d, tree =
match diff with
| `Added (_, tree) -> (`Added, tree)
| `Removed (_, tree) -> (`Removed, tree)
| `Updated (_, (_, tree)) -> (`Updated, tree) in
map ~node:(update d) tree t
>>= fun t' -> Lwd.set root t' ; Lwt.return_unit )
>>= fun watch -> Lwt.return (watch, root)
let channelview (store, path) =
storeview store path
>>= fun (_watch, root) ->
let ui =
Lwd.join
(Lwd_table.map_reduce
(fun row (tree : Tree.t) ->
let focus =
match superfocus with
| Some sf ->
Lwd.map2 (Focus.status sf)
(Focus.status tree.focus)
~f:(fun superfocus' focus' ->
if Focus.has_focus superfocus' then
F.epr
"Focus.has_focus superfocus' = true@." ;
Focus.release sf ;
Focus.request tree.focus ;
focus' )
| None -> Focus.status tree.focus in
Lwd.map2
(Lwd.map focus ~f:(fun focus ->
if Focus.has_focus focus then
Lwd.set channel tree.channel ;
Ui.keyboard_area ~focus
(fun key ->
(Lwd.map (Lwd.get root) ~f:(function
| `Empty ->
failwith "channelview says root Lwd.var is `Empty"
| `View v ->
let rec iter ?(indent = 0) (v : viewer) =
Lwd.bind (Lwd.get v.var) ~f:(function
| `Empty -> Lwd.return Ui.empty
| `View v' ->
let sub =
Lwd_utils.pack Ui.pack_y
(List.map
(iter ~indent:(indent + 1))
v'.node ) in
Lwd.map sub ~f:(fun sub ->
Ui.join_y
(Ui.string
( String.make indent '>' ^ " "
^ v'.step ) )
sub ) ) in
iter v ) ) in
let chs, chs_push = Lwt_stream.create () in
Channel.make store ~path:[base_path] ~name:topch
>>= fun ch ->
chs_push (Some ch) ;
Lwt.return (chs, ui)
let messagelist ({store; path} : Channel.t) mlist :
Istore.watch Lwt.t =
let mlist' () =
Istore.get_tree store path
>>= fun tree ->
Istore.Tree.fold ~depth:(`Eq 5)
~contents:(fun key contents view ->
match key with
| `ASCII 'w', [] -> (
match Lwd_table.prev row with
| Some r -> (
match Lwd_table.get r with
| Some r ->
Focus.release tree.focus ;
Focus.request r.focus ;
`Handled
| None -> `Unhandled )
| None -> `Unhandled )
| `ASCII 'a', [] -> (
match superfocus with
| Some f ->
Focus.release tree.focus ;
Focus.request f ;
`Handled
| None -> `Unhandled )
| `ASCII 's', [] -> (
match Lwd_table.next row with
| Some r -> (
match Lwd_table.get r with
| Some r ->
Focus.release tree.focus ;
Focus.request r.focus ;
`Handled
| None -> `Unhandled )
| None -> `Unhandled )
| `ASCII 'd', [] ->
Focus.release tree.focus ;
Focus.request subfocus ;
`Handled
| _ -> `Unhandled )
(Ui.join_x
(Ui.join_x
( if Focus.has_focus focus then
string "+"
else string "" )
(string (String.make indent '-')) )
(string Tree.(tree.channel.name)) ) ) )
(fold ~indent:(indent + 1) ~superfocus:subfocus tree)
~f:(fun parent subs -> Ui.join_y parent subs) )
(Lwd_utils.lift_monoid Ui.pack_y)
tree.subs ) in
(fold tree, channel)
| [y; m; d; h; s] ->
Lwt.return (((y, m, d, h, s), contents) :: view)
| _ ->
F.epr
"ERROR: messagelist (fold ~depth:(`Eq 5)) got \
wrong number of steps@." ;
Lwt.return view )
~node:(fun _key _node view ->
F.epr
"ERROR: messagelist (fold ~depth:(`Eq 5)) found a \
node@." ;
Lwt.return view )
tree [] in
mlist' ()
>>= fun ml ->
Lwd.set mlist ml ;
Istore.watch_key store path (fun _ ->
mlist' ()
>>= fun mlist' -> Lwt.return (Lwd.set mlist mlist') )
let messageview (ch : Channel.t Lwd.var) =
Panel.Nottui.scroll_area
(Lwd.map
(Lwd.bind (Lwd.get ch) ~f:messagelist)
~f:(P.pretty 200) )
let messageview ch =
let mlist = Lwd.var [(("", "", "", "", ""), "")] in
let rec update_messagelist watch () =
Lwt_stream.last_new ch
>>= fun ch ->
( match watch with
| None -> Lwt.return_unit
| Some w -> Istore.unwatch w )
>>= fun () ->
messagelist ch mlist
>>= fun watch -> update_messagelist (Some watch) () in
Lwt.async (update_messagelist None) ;
Lwt.return
(Lwd.map (Lwd.get mlist) ~f:(fun mlist ->
scroll
(List.fold_left
(fun doc ((year, month, day, hour, sec), content) ->
F.epr
"Communicator.Panel.messagelist ch.content=%s@."
content ;
doc
^/^ Ui.string
(F.str "%s.%s.%s.%s.%s" year month day hour
sec )
^^ Ui.string " | " ^^ string content )
Ui.empty mlist ) ) )
let commview c =
let cv, ch = channelview c in
Nottui_widgets.h_pane
(Panel.Nottui.scroll_area cv)
(messageview ch)
let commview (store, path) =
channelview (store, List.rev (List.tl (List.rev path)))
>>= fun (ch, cv) ->
messageview ch
>>= fun mv ->
Lwt.return (Lwd.map2 cv mv ~f:(fun c m -> join_x c m))
type view = Channel of (Channel.t * view list) | Cursor of view
let panel (comm : Tree.t) =
let base = Lwd.var Nottui_widgets.empty_lwd in
Lwd.set base (commview comm) ;
Panel.Nottui.panel (Lwd.join (Lwd.get base)) ()
let panel ({store; view} : Tree.t) : (Event.t -> atom Lwt.t) Lwt.t
=
commview (store, view) >>= fun cv -> Panel.Ui.panel cv
end
end
let _ =
let comm = Communicator.Tree.make_top () in
let _irc =
Communicator.Irc.connection comm "irc.hackint.org" 6697 "cqcaml"
["#CQC"] in
root_actor := std_actor (Communicator.Panel.panel comm)
(**
program starts...
- spawn connections to servers
- these connections will populate the Channel.t in a Channel.tree
**)
let _ =
Lwt.async (fun () ->
Communicator.Tree.make_top "commstore" "current"
>>= fun comm ->
Communicator.Irc.Config.make_connection comm "irc.hackint.org"
6697 "cqcaml"
>>= fun () ->
Lwt.async (fun () -> Communicator.Irc.connect comm) ;
F.epr
"root_actor := std_actor (Communicator.Panel.panel comm)@." ;
Communicator.Panel.panel comm
>|= fun f ->
root_actor :=
std_actor
(Lwt.return
Panel.
{ act=
(fun _ events ->
Lwt_list.fold_left_s
(fun _ ev ->
f ev
>>= fun i ->
Lwt.return (fun s ->
( s
, ( Gg.Box2.of_pts Gg.V2.zero (snd i)
, fst i ) ) ) )
Display.pane_empty events )
; subpanels= []
; tag= "irc" } ) )

56
log_js.ml Normal file
View File

@ -0,0 +1,56 @@
module Logs_reporter = struct
(* Console reporter *)
open Jsoo_runtime
let console : Logs.level -> string -> unit =
fun level s ->
let meth =
match level with
| Logs.Error -> "error"
| Logs.Warning -> "warn"
| Logs.Info -> "info"
| Logs.Debug -> "debug"
| Logs.App -> "log"
in
ignore
(Js.meth_call
(Js.pure_js_expr "console")
meth
[| Js.string s |])
let ppf, flush =
let b = Buffer.create 255 in
let flush () =
let s = Buffer.contents b in
Buffer.clear b;
s
in
(Format.formatter_of_buffer b, flush)
let hook =
ref (fun level s ->
ignore (Logs.level_to_string (Some level) ^ ": " ^ s))
let console_report _src level ~over k msgf =
let k _ =
let s = flush () in
console level s;
!hook level s;
over ();
k ()
in
msgf @@ fun ?header ?tags fmt ->
let _tags = tags in
match header with
| None -> Format.kfprintf k ppf ("@[" ^^ fmt ^^ "@]@.")
| Some h -> Format.kfprintf k ppf ("[%s] @[" ^^ fmt ^^ "@]@.") h
let console_reporter () = { Logs.report = console_report }
end
let _ =
Logs.set_reporter (Logs_reporter.console_reporter ());
Logs.set_level (Some Debug)
module Log = Logs

44
notes.org Normal file
View File

@ -0,0 +1,44 @@
* mvp todo
** toplevel repl in js_of_ocaml
** git pull from gitea.departmentofinter.net/console/boot
** git push to gitea.departmentofinter.net/console/boot
** execute a git file execution in top level
** display arbitrary git file from pulled repo
** edit arbitrary file with common emacs bindings
*** move left and right by character
*** move up and down by line
* other todo
*** yank (to clipboard) next char
*** move left and right by word and sentance
*** region select
* principles?
an "anywhere" programming environment
* 221211
ok you got the scroll box mostly working so next:
** fix the scroll jump bugs
** setup better keybindings
** fix cursor and active focus indicators
* 221210 -
** need to resolve the issue with the ui.t Resize type.
this is an issue with the direction of the determination of the .height and .width fields of Ui.t
currently you were planning to combine update_sensors and update_size
in the original nottui.ml library, are Ui.t.w and Ui.t.h determined from the top down orbottom up?
the bottom up, becahse they are chars

361
opam-switch Normal file
View File

@ -0,0 +1,361 @@
opam-version: "2.0"
compiler: ["ocaml-system.4.13.1"]
roots: [
"bogue.20210917"
"findlib_top.v0.11.0"
"glfw-ocaml.3.3.1-1"
"huffman.0.1.2"
"inuit.0.4.1"
"irc-client.0.7.0"
"irc-client-lwt.0.7.0"
"irc-client-tls.0.7.0"
"irc-client-unix.0.7.0"
"irmin.2.9.0"
"irmin-unix.2.9.0"
"lambda-term.3.1.0"
"lwd.0.1"
"lwt_ppx.2.0.3"
"merlin.4.4-413"
"note.0.0.1"
"nottui.0.1"
"nottui-lwt.0.1"
"nottui-pretty.0.1"
"ocaml-manual.4.13.0"
"ocaml-system.4.13.1"
"ocamlformat.0.20.1"
"odig.0.0.7"
"odoc.2.0.2"
"pp.1.1.2"
"pprint.20211129"
"tgls.0.8.5"
"tsdl.0.9.8"
"user-setup.0.7"
"wall.0.4.1"
"zed.3.1.0"
]
installed: [
"angstrom.0.15.0"
"arp.3.0.0"
"asn1-combinators.0.2.6"
"astring.0.8.5"
"awa.0.0.4"
"awa-mirage.0.0.4"
"b0.0.0.3"
"base.v0.14.2"
"base-bigarray.base"
"base-bytes.base"
"base-threads.base"
"base-unix.base"
"base64.3.5.0"
"bheap.2.0.0"
"bigarray-compat.1.0.0"
"bigstringaf.0.8.0"
"biniou.1.2.1"
"bogue.20210917"
"bos.0.2.0"
"ca-certs.0.2.2"
"ca-certs-nss.3.71"
"camomile.1.0.2"
"carton.0.4.3"
"carton-git.0.4.3"
"carton-lwt.0.4.3"
"cf.0.4"
"cf-lwt.0.4"
"charInfo_width.1.1.0"
"checkseum.0.3.2"
"cmdliner.1.0.4"
"cohttp.4.0.0"
"cohttp-lwt.4.0.0"
"cohttp-lwt-unix.4.0.0"
"conduit.4.0.2"
"conduit-lwt.4.0.2"
"conduit-lwt-unix.4.0.2"
"conf-cairo.1"
"conf-gles2.1"
"conf-glfw3.2"
"conf-gmp.3"
"conf-gmp-powm-sec.3"
"conf-libffi.2.0.0"
"conf-libX11.1"
"conf-m4.1"
"conf-pkg-config.2"
"conf-sdl2.1"
"conf-sdl2-image.1"
"conf-sdl2-ttf.1"
"cppo.1.6.8"
"crunch.3.2.0"
"csexp.1.5.1"
"cstruct.6.0.1"
"cstruct-lwt.6.0.1"
"cstruct-sexp.6.0.1"
"cstruct-unix.6.0.1"
"ctypes.0.20.0"
"ctypes-foreign.0.18.0"
"decompress.1.4.2"
"digestif.1.1.0"
"dispatch.0.5.0"
"domain-name.0.3.1"
"dot-merlin-reader.4.1"
"duff.0.4"
"dune.2.9.1"
"dune-build-info.2.9.1"
"dune-configurator.2.9.1"
"duration.0.2.0"
"easy-format.1.3.2"
"either.1.0.0"
"emile.1.1"
"encore.0.8"
"eqaf.0.8"
"ethernet.3.0.0"
"findlib_top.v0.11.0"
"fix.20211125"
"fmt.0.9.0"
"fpath.0.7.3"
"fsevents.0.3.0"
"fsevents-lwt.0.3.0"
"gg.0.9.3"
"git.3.6.0"
"git-cohttp.3.6.0"
"git-cohttp-unix.3.6.0"
"git-unix.3.6.0"
"glfw-ocaml.3.3.1-1"
"gmap.0.3.0"
"graphql.0.13.0"
"graphql-cohttp.0.13.0"
"graphql-lwt.0.13.0"
"graphql_parser.0.13.0"
"graphv_core.0.1.1"
"graphv_core_lib.0.1.1"
"graphv_font.0.1.1"
"graphv_font_js.0.1.1"
"graphv_gles2.0.1.1"
"graphv_gles2_native_impl.0.1.1"
"graphv_webgl.0.1.1"
"graphv_webgl_impl.0.1.1"
"grenier.0.13"
"hex.1.4.0"
"hkdf.1.0.4"
"huffman.0.1.2"
"hxd.0.3.1"
"index.1.5.0"
"inotify.2.3"
"integers.0.5.1"
"inuit.0.4.1"
"ipaddr.5.2.0"
"ipaddr-sexp.5.2.0"
"irc-client.0.7.0"
"irc-client-lwt.0.7.0"
"irc-client-tls.0.7.0"
"irc-client-unix.0.7.0"
"irmin.2.9.0"
"irmin-fs.2.9.0"
"irmin-git.2.9.0"
"irmin-graphql.2.9.0"
"irmin-http.2.9.0"
"irmin-layers.2.9.0"
"irmin-pack.2.9.0"
"irmin-unix.2.9.0"
"irmin-watcher.0.5.0"
"jbuilder.1.0+beta20.2"
"js_of_ocaml.3.11.0"
"js_of_ocaml-compiler.3.11.0"
"js_of_ocaml-ppx.3.11.0"
"jsonm.1.0.1"
"ke.0.4"
"lambda-term.3.1.0"
"logs.0.7.0"
"lru.0.3.0"
"lwd.0.1"
"lwt.5.5.0"
"lwt-dllist.1.0.1"
"lwt_log.1.1.1"
"lwt_ppx.2.0.3"
"lwt_react.1.1.5"
"macaddr.5.2.0"
"macaddr-cstruct.5.2.0"
"magic-mime.1.2.0"
"menhir.20211128"
"menhirLib.20211128"
"menhirSdk.20211128"
"merlin.4.4-413"
"metrics.0.3.0"
"mew.0.1.0"
"mew_vi.0.5.0"
"mimic.0.0.4"
"mirage-clock.4.0.0"
"mirage-clock-unix.4.0.0"
"mirage-crypto.0.10.5"
"mirage-crypto-ec.0.10.5"
"mirage-crypto-pk.0.10.5"
"mirage-crypto-rng.0.10.5"
"mirage-device.2.0.0"
"mirage-flow.3.0.0"
"mirage-kv.4.0.0"
"mirage-net.4.0.0"
"mirage-no-solo5.1"
"mirage-no-xen.1"
"mirage-profile.0.9.1"
"mirage-protocols.8.0.0"
"mirage-random.3.0.0"
"mirage-stack.4.0.0"
"mirage-time.3.0.0"
"mmap.1.1.0"
"mtime.1.3.0"
"note.0.0.1"
"nottui.0.1"
"nottui-lwt.0.1"
"nottui-pretty.0.1"
"notty.0.2.2"
"num.1.4"
"oasis.0.4.11"
"ocaml.4.13.1"
"ocaml-compiler-libs.v0.12.4"
"ocaml-config.2"
"ocaml-manual.4.13.0"
"ocaml-migrate-parsetree.2.3.0"
"ocaml-options-vanilla.1"
"ocaml-syntax-shims.1.0.0"
"ocaml-system.4.13.1"
"ocaml-version.3.4.0"
"ocamlbuild.0.14.0"
"ocamlfind.1.9.1"
"ocamlformat.0.20.1"
"ocamlgraph.2.0.0"
"ocamlify.0.0.1"
"ocamlmod.0.0.9"
"ocb-stubblr.0.1.1-1"
"ocp-indent.1.8.1"
"ocplib-endian.1.2"
"odig.0.0.7"
"odoc.2.0.2"
"odoc-parser.1.0.0"
"optint.0.1.0"
"parsexp.v0.14.1"
"pbkdf.1.2.0"
"pecu.0.6"
"pp.1.1.2"
"pprint.20211129"
"ppx_cstruct.6.0.1"
"ppx_derivers.1.2.1"
"ppx_deriving.5.2.1"
"ppx_irmin.2.9.0"
"ppx_repr.0.5.0"
"ppx_sexp_conv.v0.14.3"
"ppxlib.0.24.0"
"progress.0.2.1"
"psq.0.2.0"
"ptime.0.8.6"
"randomconv.0.1.3"
"re.1.10.3"
"react.1.2.1"
"repr.0.5.0"
"result.1.5"
"rresult.0.6.0"
"semaphore-compat.1.0.1"
"seq.base"
"sexplib.v0.14.0"
"sexplib0.v0.14.0"
"stb_image.0.5"
"stb_truetype.0.6"
"stdio.v0.14.0"
"stdlib-shims.0.3.0"
"stringext.1.6.0"
"tcpip.7.0.0"
"terminal.0.2.1"
"terminal_size.0.1.4"
"tgls.0.8.5"
"tls.0.14.1"
"tls-mirage.0.14.1"
"topkg.1.0.4"
"trie.1.0.0"
"tsdl.0.9.8"
"tsdl-image.0.3.2"
"tsdl-ttf.0.3.2"
"tyxml.4.5.0"
"uchar.0.0.2"
"uri.4.2.0"
"uri-sexp.4.2.0"
"user-setup.0.7"
"uucp.14.0.0"
"uuseg.14.0.0"
"uutf.1.0.2"
"vector.1.0.0"
"wall.0.4.1"
"webmachine.0.7.0"
"x509.0.14.1"
"yaml.3.0.0"
"yojson.1.7.0"
"zarith.1.12"
"zed.3.1.0"
]
pinned: ["lwd.0.1" "nottui.0.1"]
package "lwd" {
opam-version: "2.0"
version: "0.1"
synopsis: "Lightweight reactive documents"
maintainer: "fred@tarides.com"
authors: "Frédéric Bour"
license: "MIT"
homepage: "https://github.com/let-def/lwd"
doc: "https://let-def.github.io/lwd/doc"
bug-reports: "https://github.com/let-def/lwd/issues"
depends: [
"dune" {>= "2.0"}
"seq"
"ocaml" {>= "4.03"}
"qtest" {with-test}
"qcheck" {with-test}
]
build: [
["dune" "subst"] {pinned}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/let-def/lwd.git"
url {
src: "git+file:///home/cqc/p/console/ref/lwd#master"
}
}
package "nottui" {
opam-version: "2.0"
version: "0.1"
synopsis: "UI toolkit for the terminal built on top of Notty and Lwd"
maintainer: "fred@tarides.com"
authors: "Frédéric Bour"
license: "MIT"
homepage: "https://github.com/let-def/lwd"
doc: "https://let-def.github.io/lwd/doc"
bug-reports: "https://github.com/let-def/lwd/issues"
depends: [
"dune" {>= "2.0"}
"lwd"
"notty"
]
build: [
["dune" "subst"] {pinned}
[
"dune"
"build"
"-p"
name
"-j"
jobs
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
]
dev-repo: "git+https://github.com/let-def/lwd.git"
url {
src: "git+file:///home/cqc/p/console/ref/lwd#master"
}
}

11
ppx_graph.ml Normal file
View File

@ -0,0 +1,11 @@
open Ppxlib
open Log_js
let log_info pp exp =
Log.info (fun m -> m "ppx_graph: %a" pp exp);
exp
let init () =
Driver.register_transformation
~impl:(log_info Ocaml_common.Pprintast.structure)
"ppx_graph"

230
toplevel.html Normal file
View File

@ -0,0 +1,230 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>OCaml toplevel</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<style>
code, kbd, pre, samp {
font-family: Menlo,Monaco,Consolas,monospace;
}
body,html {
height: 100%;
background-color:#eee;
}
#toplevel-container {
width: 50%;
background-color: black;
color: #ccc;
overflow: auto;
overflow-x: hidden;
height: 100%;
float:left;
padding:10px;
padding-top: 20px;
}
#toplevel-container pre#output {
padding: 0px;
}
#toplevel-container #output {
background-color:transparent;
color: #ccc;
border: none;
line-height:18px;
font-size: 12px;
margin-bottom: 0px;
}
#toplevel-container textarea {
width:90%;
line-height:18px;
font-size: 12px;
background-color: transparent;
color: #fff;
border: 0;
resize: none;
outline: none;
font-family: Menlo,Monaco,Consolas,monospace;
font-weight: bold;
float:left;
margin: 0px;
padding:0px;
}
#toplevel-container #sharp {
float: left;
line-height:18px;
font-size: 12px;
font-family: Menlo,Monaco,Consolas,monospace;
white-space: pre;
}
.sharp:before{
content:"# ";
line-height:18px;
font-size: 12px;
font-family: Menlo,Monaco,Consolas,monospace;
}
.caml{
color:rgb(110, 110, 201);
}
#toplevel-side{
position:relative;
width:45%;
height: 100%;
overflow: auto;
text-align:justify;
float:left;
margin-left:30px;
}
#toplevel-side ul{
padding: 0px;
list-style-type: none;
}
.stderr {
color: #d9534f;
}
.stdout {
}
.errorloc{
border-bottom-width: 3px;
border-bottom-style: solid;
border-bottom-color: red;
}
canvas {
border: 1px dashed black;
float: left;
margin: 7px;
}
#output canvas {
background-color: #464646;
float: none;
display: block;
border: 1px dashed while;
margin: 7px;
}
#output img {
display:block;
}
#toplevel-examples {
width: 270px;
float: left;
}
#toplevel-examples .list-group-item{
padding: 5px 15px;
}
#btn-share {
float:right;
margin-top:-20px;
background-color:rgb(92, 129, 184);
border-color: rgb(70, 75, 128);
padding: 1px 5px;
display:none;
}
.clear { clear:both; }
.sharp .id { color: #59B65C ; font-style: italic }
.sharp .kw0 { color: rgb(64, 75, 190); font-weight: bold ;}
.sharp .kw1 { color: rgb(150, 0, 108); font-weight: bold ;}
.sharp .kw2 { color: rgb(23, 100, 42); font-weight: bold ;}
.sharp .kw3 { color: #59B65C; font-weight: bold ;}
.sharp .kw4 { color: #59B65C; font-weight: bold ;}
.sharp .comment { color: green ; font-style: italic ; }
.sharp .string { color: #6B6B6B; font-weight: bold ; }
.sharp .text { }
.sharp .numeric { color: #729AAF; }
.sharp .directive { font-style: italic ; color : #EB00FF; } ;
.sharp .escape { color: #409290 ; }
.sharp .symbol0 { color: orange ; font-weight: bold ; }
.sharp .symbol1 { color: #993300 ; font-weight: bold ; }
.sharp .constant { color: rgb(0, 152, 255); }
</style>
<script type="text/javascript">
window.onhashchange = function() { window.location.reload() }
var hash = window.location.hash.replace(/^#/,"");
var fields = hash.split(/&/);
var prefix = "";
var version = "";
for(var f in fields){
var data = fields[f].split(/=/);
if(data[0] == "version"){
version = data[1].replace(/%20|%2B/g,"+");
break;
}
}
function load_script(url){
var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", prefix+(version==""?"":(version+"/"))+url);
document.getElementsByTagName("head")[0].appendChild(fileref);
}
load_script("_build/default/exported-unit.cmis.js");
load_script("_build/default/toplevel.bc.js");
</script>
</head>
<body>
<div id="toplevel-container">
<pre id="output"></pre>
<div>
<div id="sharp" class="sharp"></div>
<textarea id="userinput">Loading ...</textarea>
<button type="button" class="btn btn-default"
id="btn-share">Share</button>
</div>
</div>
<div id="toplevel-side">
<h3>Js_of_ocaml</h3>
<h4>A compiler from OCaml bytecode to Javascript.</h4>
<p>It makes OCaml programs that run on Web browsers. It is
easy to install as it works with an existing installation of OCaml,
with no need to recompile any library. It comes with bindings for a
large part of the browser APIs.</p>
<p>This web-based OCaml toplevel is compiled using Js_of_ocaml.</p>
<h4>Command</h4>
<table class="table table-striped table-condensed">
<tbody class>
<tr>
<td>Enter/Return</td>
<td>Submit code</td>
</tr>
<tr>
<td>Ctrl + Enter</td>
<td>Newline</td>
</tr>
<tr>
<td>Up / Down</td>
<td>Browse history</td>
</tr>
<tr>
<td>Ctrl + l</td>
<td>Clear display</td>
</tr>
<tr>
<td>Ctrl + k</td>
<td>Reset toplevel</td>
</tr>
<tr>
<td>Tab</td>
<td>Indent code</td>
</tr>
</tbody>
</table>
<h4>Try to execute samples</h4>
<div id="toplevel-examples" class="list-group"></div>
<canvas width=200 height=200 id="test-canvas"></canvas>
<h4 class="clear">See the generated javascript code</h4>
<pre id="last-js">
</pre>
</div>
</body>
</html>

644
toplevel.ml Normal file
View File

@ -0,0 +1,644 @@
(* Js_of_ocaml toplevel
* http://www.ocsigen.org/js_of_ocaml/
* Copyright (C) 2011 Jérôme Vouillon
* Laboratoire PPS - CNRS Université Paris Diderot
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)
open Js_of_ocaml
open Js_of_ocaml_lwt
open Js_of_ocaml_tyxml
open Js_of_ocaml_toplevel
open Lwt
module Graphics_support = struct
let init elt = Graphics_js.open_canvas elt
end
open Log_js
module Ppx_support = struct
let init () =
Ppx_graph.init ();
Ast_mapper.register "js_of_ocaml" (fun _ -> Ppx_js.mapper);
Ast_mapper.register "ppxlib" (fun _ ->
Log.info (fun m -> m "Ppxlib.mapper");
{
Ast_mapper.default_mapper with
structure =
(fun _ ->
Log.info (fun m -> m "Ppxlib.Driver.map_structure");
Ppxlib.Driver.map_structure);
signature =
(fun _ ->
Log.info (fun m -> m "Ppxlib.Driver.map_signature");
Ppxlib.Driver.map_signature);
})
end
module Colorize = struct
open Js_of_ocaml
open Js_of_ocaml_tyxml
let text ~a_class:cl s =
Tyxml_js.Html.(span ~a:[ a_class [ cl ] ] [ txt s ])
let ocaml = text
let highlight from_ to_ e =
match Js.Opt.to_option e##.textContent with
| None -> assert false
| Some x ->
let x = Js.to_string x in
let (`Pos from_) = from_ in
let to_ =
match to_ with `Pos n -> n | `Last -> String.length x - 1
in
e##.innerHTML := Js.string "";
let span kind s =
if s <> "" then
let span =
Tyxml_js.Html.(span ~a:[ a_class [ kind ] ] [ txt s ])
in
Dom.appendChild e (Tyxml_js.To_dom.of_element span)
in
span "normal" (String.sub x 0 from_);
span "errorloc" (String.sub x from_ (to_ - from_));
span "normal" (String.sub x to_ (String.length x - to_))
end
module Indent : sig
val textarea : Dom_html.textAreaElement Js.t -> unit
end = struct
let _ = Approx_lexer.enable_extension "lwt"
let indent s in_lines =
let output =
{
IndentPrinter.debug = false;
config = IndentConfig.default;
in_lines;
indent_empty = true;
adaptive = true;
kind = IndentPrinter.Print (fun s acc -> acc ^ s);
}
in
let stream = Nstream.of_string s in
IndentPrinter.proceed output stream IndentBlock.empty ""
let textarea (textbox : Dom_html.textAreaElement Js.t) : unit =
let rec loop s acc (i, pos') =
try
let pos = String.index_from s pos' '\n' in
loop s ((i, (pos', pos)) :: acc) (succ i, succ pos)
with _ -> List.rev ((i, (pos', String.length s)) :: acc)
in
let rec find (l : (int * (int * int)) list) c =
match l with
| [] -> assert false
| (i, (lo, up)) :: _ when up >= c -> (c, i, lo, up)
| (_, (_lo, _up)) :: rem -> find rem c
in
let v = textbox##.value in
let pos =
let c1 = textbox##.selectionStart
and c2 = textbox##.selectionEnd in
if
Js.Opt.test (Js.Opt.return c1)
&& Js.Opt.test (Js.Opt.return c2)
then
let l = loop (Js.to_string v) [] (0, 0) in
Some (find l c1, find l c2)
else None
in
let f =
match pos with
| None -> fun _ -> true
| Some ((_c1, line1, _lo1, _up1), (_c2, line2, _lo2, _up2)) ->
fun l -> l >= line1 + 1 && l <= line2 + 1
in
let v = indent (Js.to_string v) f in
textbox##.value := Js.string v;
match pos with
| Some ((c1, line1, _lo1, up1), (c2, line2, _lo2, up2)) ->
let l = loop v [] (0, 0) in
let lo1'', up1'' = List.assoc line1 l in
let lo2'', up2'' = List.assoc line2 l in
let n1 = max (c1 + up1'' - up1) lo1'' in
let n2 = max (c2 + up2'' - up2) lo2'' in
let () = (Obj.magic textbox)##setSelectionRange n1 n2 in
textbox##focus;
()
| None -> ()
end
let compiler_name = "OCaml"
let by_id s = Dom_html.getElementById s
let by_id_coerce s f =
Js.Opt.get
(f (Dom_html.getElementById s))
(fun () -> raise Not_found)
let do_by_id s f =
try f (Dom_html.getElementById s) with Not_found -> ()
(* load file using a synchronous XMLHttpRequest *)
let load_resource_aux filename url =
Js_of_ocaml_lwt.XmlHttpRequest.perform_raw
~response_type:XmlHttpRequest.ArrayBuffer url
>|= fun frame ->
if frame.Js_of_ocaml_lwt.XmlHttpRequest.code = 200 then
Js.Opt.case frame.Js_of_ocaml_lwt.XmlHttpRequest.content
(fun () -> Printf.eprintf "Could not load %s\n" filename)
(fun b ->
Sys_js.update_file ~name:filename
~content:(Typed_array.String.of_arrayBuffer b))
else ()
let load_resource scheme ~prefix ~path:suffix =
let url = scheme ^ suffix in
let filename = Filename.concat prefix suffix in
Lwt.async (fun () -> load_resource_aux filename url);
Some ""
let setup_pseudo_fs () =
Sys_js.mount ~path:"/dev/" (fun ~prefix:_ ~path:_ -> None);
Sys_js.mount ~path:"/http/" (load_resource "http://");
Sys_js.mount ~path:"/https/" (load_resource "https://");
Sys_js.mount ~path:"/ftp/" (load_resource "ftp://");
Sys_js.mount ~path:"/home/" (load_resource "filesys/")
let exec' s =
let res : bool = JsooTop.use Format.std_formatter s in
if not res then Format.eprintf "error while evaluating %s@." s
module Version = struct
type t = int list
let split_char ~sep p =
let len = String.length p in
let rec split beg cur =
if cur >= len then
if cur - beg > 0 then [ String.sub p beg (cur - beg) ] else []
else if sep p.[cur] then
String.sub p beg (cur - beg) :: split (cur + 1) (cur + 1)
else split beg (cur + 1)
in
split 0 0
let split v =
match
split_char
~sep:(function '+' | '-' | '~' -> true | _ -> false)
v
with
| [] -> assert false
| x :: _ ->
List.map int_of_string
(split_char ~sep:(function '.' -> true | _ -> false) x)
let current : t = split Sys.ocaml_version
let compint (a : int) b = compare a b
let rec compare v v' =
match (v, v') with
| [ x ], [ y ] -> compint x y
| [], [] -> 0
| [], y :: _ -> compint 0 y
| x :: _, [] -> compint x 0
| x :: xs, y :: ys -> (
match compint x y with 0 -> compare xs ys | n -> n)
end
let setup_toplevel () =
JsooTop.initialize ();
Sys.interactive := false;
if Version.compare Version.current [ 4; 07 ] >= 0 then
exec' "open Stdlib";
exec'
"module Lwt_main = struct\n\
\ let run t = match Lwt.state t with\n\
\ | Lwt.Return x -> x\n\
\ | Lwt.Fail e -> raise e\n\
\ | Lwt.Sleep -> failwith \"Lwt_main.run: thread \
didn't return\"\n\
\ end";
let header1 =
Printf.sprintf " %s version %%s" compiler_name
in
let header2 =
Printf.sprintf " Compiled with Js_of_ocaml version %s"
Sys_js.js_of_ocaml_version
in
exec'
(Printf.sprintf "Format.printf \"%s@.\" Sys.ocaml_version;;"
header1);
exec' (Printf.sprintf "Format.printf \"%s@.\";;" header2);
exec' "#enable \"pretty\";;";
exec' "#disable \"shortvar\";;";
Ppx_support.init ();
let[@alert "-deprecated"] new_directive n k =
Hashtbl.add Toploop.directive_table n k
in
new_directive "load_js"
(Toploop.Directive_string
(fun name -> Js.Unsafe.global##load_script_ name));
Sys.interactive := true;
()
let resize ~container ~textbox () =
Lwt.pause () >>= fun () ->
textbox##.style##.height := Js.string "auto";
textbox##.style##.height
:= Js.string (Printf.sprintf "%dpx" (max 18 textbox##.scrollHeight));
container##.scrollTop := container##.scrollHeight;
Lwt.return ()
let setup_printers () =
exec'
"let _print_error fmt e = Format.pp_print_string fmt \
(Js_of_ocaml.Js_error.to_string e)";
Topdirs.dir_install_printer Format.std_formatter
Longident.(Lident "_print_error");
exec'
"let _print_unit fmt (_ : 'a) : 'a = Format.pp_print_string fmt \
\"()\"";
Topdirs.dir_install_printer Format.std_formatter
Longident.(Lident "_print_unit")
let setup_examples ~container ~textbox =
let r = Regexp.regexp "^\\(\\*+(.*)\\*+\\)$" in
let all = ref [] in
(try
let ic = open_in "/static/examples.ml" in
while true do
let line = input_line ic in
match Regexp.string_match r line 0 with
| Some res ->
let name =
match Regexp.matched_group res 1 with
| Some s -> s
| None -> assert false
in
all := `Title name :: !all
| None -> all := `Content line :: !all
done;
assert false
with _ -> ());
let example_container = by_id "toplevel-examples" in
let _ =
List.fold_left
(fun acc tok ->
match tok with
| `Content line -> line ^ "\n" ^ acc
| `Title name ->
let a =
Tyxml_js.Html.(
a
~a:
[
a_class [ "list-group-item" ];
a_onclick (fun _ ->
textbox##.value := (Js.string acc)##trim;
Lwt.async (fun () ->
resize ~container ~textbox ()
>>= fun () ->
textbox##focus;
Lwt.return_unit);
true);
]
[ txt name ])
in
Dom.appendChild example_container (Tyxml_js.To_dom.of_a a);
"")
"" !all
in
()
(* we need to compute the hash form href to avoid different encoding behavior
across browser. see Url.get_fragment *)
let parse_hash () =
let frag = Url.Current.get_fragment () in
Url.decode_arguments frag
let rec iter_on_sharp ~f x =
Js.Opt.iter (Dom_html.CoerceTo.element x) (fun e ->
if Js.to_bool (e##.classList##contains (Js.string "sharp")) then
f e);
match Js.Opt.to_option x##.nextSibling with
| None -> ()
| Some n -> iter_on_sharp ~f n
let setup_share_button ~output =
do_by_id "btn-share" (fun e ->
e##.style##.display := Js.string "block";
e##.onclick :=
Dom_html.handler (fun _ ->
(* get all ocaml code *)
let code = ref [] in
Js.Opt.iter output##.firstChild
(iter_on_sharp ~f:(fun e ->
code :=
Js.Opt.case e##.textContent
(fun () -> "")
Js.to_string
:: !code));
let code_encoded = String.concat "" (List.rev !code) in
let url, is_file =
match Url.Current.get () with
| Some (Url.Http url) ->
(Url.Http { url with Url.hu_fragment = "" }, false)
| Some (Url.Https url) ->
(Url.Https { url with Url.hu_fragment = "" }, false)
| Some (Url.File url) ->
(Url.File { url with Url.fu_fragment = "" }, true)
| _ -> assert false
in
let frag =
let frags = parse_hash () in
let frags =
List.remove_assoc "code" frags
@ [ ("code", code_encoded) ]
in
Url.encode_arguments frags
in
let uri = Url.string_of_url url ^ "#" ^ frag in
let append_url str =
let dom =
Tyxml_js.Html.(
p
[
txt "Share this url : ";
a ~a:[ a_href str ] [ txt str ];
])
in
Dom.appendChild output (Tyxml_js.To_dom.of_element dom)
in
Lwt.async (fun () ->
Lwt.catch
(fun () ->
if is_file then
failwith "Cannot shorten url with file scheme"
else
let uri =
Printf.sprintf
"http://is.gd/create.php?format=json&url=%s"
(Url.urlencode uri)
in
Lwt.bind (Js_of_ocaml_lwt.Jsonp.call uri)
(fun o ->
let str = Js.to_string o##.shorturl in
append_url str;
Lwt.return_unit))
(fun exn ->
Format.eprintf
"Could not generate short url. reason: %s@."
(Printexc.to_string exn);
append_url uri;
Lwt.return_unit));
Js._false))
let setup_js_preview () =
let ph = by_id "last-js" in
let runcode : string -> 'a = Js.Unsafe.global##.toplevelEval in
Js.Unsafe.global##.toplevelEval := fun bc ->
ph##.innerHTML := Js.string bc;
runcode bc
let current_position = ref 0
let highlight_location loc =
let x = ref 0 in
let output = by_id "output" in
let first =
Js.Opt.get
(output##.childNodes##item !current_position)
(fun _ -> assert false)
in
iter_on_sharp first ~f:(fun e ->
incr x;
let _file1, line1, col1 =
Location.get_pos_info loc.Location.loc_start
in
let _file2, line2, col2 =
Location.get_pos_info loc.Location.loc_end
in
if !x >= line1 && !x <= line2 then
let from_ = if !x = line1 then `Pos col1 else `Pos 0 in
let to_ = if !x = line2 then `Pos col2 else `Last in
Colorize.highlight from_ to_ e)
let append colorize output cl s =
Dom.appendChild output
(Tyxml_js.To_dom.of_element (colorize ~a_class:cl s))
module History = struct
let data = ref [| "" |]
let idx = ref 0
let get_storage () =
match Js.Optdef.to_option Dom_html.window##.localStorage with
| exception _ -> raise Not_found
| None -> raise Not_found
| Some t -> t
let setup () =
try
let s = get_storage () in
match Js.Opt.to_option (s##getItem (Js.string "history")) with
| None -> raise Not_found
| Some s ->
let a = Json.unsafe_input s in
data := a;
idx := Array.length a - 1
with _ -> ()
let push text =
let l = Array.length !data in
let n = Array.make (l + 1) "" in
!data.(l - 1) <- text;
Array.blit !data 0 n 0 l;
data := n;
idx := l;
try
let s = get_storage () in
let str = Json.output !data in
s##setItem (Js.string "history") str
with Not_found -> ()
let current text = !data.(!idx) <- text
let previous textbox =
if !idx > 0 then (
decr idx;
textbox##.value := Js.string !data.(!idx))
let next textbox =
if !idx < Array.length !data - 1 then (
incr idx;
textbox##.value := Js.string !data.(!idx))
end
let run _ =
let container = by_id "toplevel-container" in
let output = by_id "output" in
let textbox : 'a Js.t =
by_id_coerce "userinput" Dom_html.CoerceTo.textarea
in
let sharp_chan = open_out "/dev/null0" in
let sharp_ppf = Format.formatter_of_out_channel sharp_chan in
let caml_chan = open_out "/dev/null1" in
let caml_ppf = Format.formatter_of_out_channel caml_chan in
let execute () =
let content = Js.to_string textbox##.value##trim in
let content' =
let len = String.length content in
if
try
content <> ""
&& content.[len - 1] <> ';'
&& content.[len - 2] <> ';'
with _ -> true
then content ^ ";;"
else content
in
current_position := output##.childNodes##.length;
textbox##.value := Js.string "";
History.push content;
JsooTop.execute true ~pp_code:sharp_ppf ~highlight_location
caml_ppf content';
resize ~container ~textbox () >>= fun () ->
container##.scrollTop := container##.scrollHeight;
textbox##focus;
Lwt.return_unit
in
let history_down _e =
let txt = Js.to_string textbox##.value in
let pos = textbox##.selectionStart in
try
if String.length txt = pos then raise Not_found;
let _ = String.index_from txt pos '\n' in
Js._true
with Not_found ->
History.current txt;
History.next textbox;
Js._false
in
let history_up _e =
let txt = Js.to_string textbox##.value in
let pos = textbox##.selectionStart - 1 in
try
if pos < 0 then raise Not_found;
let _ = String.rindex_from txt pos '\n' in
Js._true
with Not_found ->
History.current txt;
History.previous textbox;
Js._false
in
let meta e =
let b = Js.to_bool in
b e##.ctrlKey || b e##.altKey || b e##.metaKey
in
let shift e = Js.to_bool e##.shiftKey in
(* setup handlers *)
textbox##.onkeyup :=
Dom_html.handler (fun _ ->
Lwt.async (resize ~container ~textbox);
Js._true);
textbox##.onchange :=
Dom_html.handler (fun _ ->
Lwt.async (resize ~container ~textbox);
Js._true);
textbox##.onkeydown :=
Dom_html.handler (fun e ->
match e##.keyCode with
| 13 when not (meta e || shift e) ->
Lwt.async execute;
Js._false
| 13 ->
Lwt.async (resize ~container ~textbox);
Js._true
| 09 ->
Indent.textarea textbox;
Js._false
| 76 when meta e ->
output##.innerHTML := Js.string "";
Js._true
| 75 when meta e ->
setup_toplevel ();
Js._false
| 38 -> history_up e
| 40 -> history_down e
| _ -> Js._true);
(Lwt.async_exception_hook :=
fun exc ->
Format.eprintf "exc during Lwt.async: %s@."
(Printexc.to_string exc);
match exc with
| Js_error.Exn e ->
let e = Js_error.to_error e in
Firebug.console##log e##.stack
| _ -> ());
Lwt.async (fun () ->
resize ~container ~textbox () >>= fun () ->
textbox##focus;
Lwt.return_unit);
Graphics_support.init
(by_id_coerce "test-canvas" Dom_html.CoerceTo.canvas);
Sys_js.set_channel_flusher caml_chan
(append Colorize.ocaml output "caml");
Sys_js.set_channel_flusher sharp_chan
(append Colorize.ocaml output "sharp");
Sys_js.set_channel_flusher stdout
(append Colorize.text output "stdout");
Sys_js.set_channel_flusher stderr
(append Colorize.text output "stderr");
let readline () =
Js.Opt.case
(Dom_html.window##prompt
(Js.string "The toplevel expects inputs:")
(Js.string ""))
(fun () -> "")
(fun s -> Js.to_string s ^ "\n")
in
Sys_js.set_channel_filler stdin readline;
setup_share_button ~output;
setup_examples ~container ~textbox;
setup_pseudo_fs ();
setup_toplevel ();
setup_js_preview ();
setup_printers ();
History.setup ();
textbox##.value := Js.string "";
(* Run initial code if any *)
try
let code = List.assoc "code" (parse_hash ()) in
textbox##.value := Js.string code;
Lwt.async execute
with
| Not_found -> ()
| exc ->
Firebug.console##log_3 (Js.string "exception")
(Js.string (Printexc.to_string exc))
exc
let _ =
Dom_html.window##.onload
:= Dom_html.handler (fun _ ->
run ();
Js._false)

37
unicom.org Normal file
View File

@ -0,0 +1,37 @@
UNICOM
unifying tools for thought and commmunication
At the top level is a kind of "trace" that connects all the tools together
and records the exact use of all the tools in a session. Traces are the only
kind of document in the system, and are basically written by a user when
they interact with the system. Users can store, retrieve, view, and modify
traces as a way of controlling system state, retrieving history, and sharing
information?
tools for thought:
- todo lists
- personal journal
- calendar
- calculator/spreadsheet
- health, fitness, finance data tracking, analysis
- Integrated Development Environment [for development/configuration of all these tools]
tools for communication:
- collaborative documents
video / .txt file
- content discovery,display,labeling?
twitter / netflix / books
- direct messages
sms / email
- group messages
private (chat) / private (irc,twitter)
- audio/music production
sound driver / DAW
- cad
KiCad / OpenSCAD