diff --git a/.agents/skills/journal-build/scripts/journal_build.py b/.agents/skills/journal-build/scripts/journal_build.py index bc478bd..1e5e26e 100644 --- a/.agents/skills/journal-build/scripts/journal_build.py +++ b/.agents/skills/journal-build/scripts/journal_build.py @@ -1239,6 +1239,9 @@ def interrupted(signum, frame): { (void)n; (void)token; (void)outcome; return 1; } int32_t lui_ocaml_visible_range(int64_t n, int64_t first, int64_t last) { (void)n; (void)first; (void)last; return 1; } +int32_t lui_ocaml_virtual_window(int64_t n, int64_t generation, int64_t first, + int64_t last, const int64_t *pins, int32_t count) + { (void)n; (void)generation; (void)first; (void)last; (void)pins; (void)count; return 1; } int32_t lui_ocaml_picked(int64_t n, const char *payload) { (void)n; (void)payload; return 1; } int32_t journal_ocaml_extension_event(int64_t n, const char *name, const char *p) { (void)n; (void)name; (void)p; return 1; } diff --git a/app/application.ml b/app/application.ml index 775aa62..633e6d3 100644 --- a/app/application.ml +++ b/app/application.ml @@ -7032,6 +7032,7 @@ let start in let app = Lui_app.create_with_extensions + ~virtual_rows:(Lui_native_bridge.virtual_rows_supported host_code) backend Journal_lui_native.registry initial_state diff --git a/app/journal_bridge.ml b/app/journal_bridge.ml index 4c3b0db..ddfa48c 100644 --- a/app/journal_bridge.ml +++ b/app/journal_bridge.ml @@ -72,6 +72,10 @@ let visible_range node first last = dispatch_lui (Lui_protocol.VisibleRange (node, first, last)) ;; +let virtual_window node generation first last pins = + dispatch_lui (Lui_protocol.VirtualWindow (node, generation, first, last, pins)) +;; + let picked node payload = dispatch_lui (Lui_protocol.Picked (node, payload)) let extension_event node name payload = (hooks ()).extension_event node name payload let pump () = (hooks ()).pump () @@ -110,6 +114,7 @@ let register hooks = Callback.register "lui_ocaml_slider_changed" slider_changed; Callback.register "lui_ocaml_scroll_completed" scroll_completed; Callback.register "lui_ocaml_visible_range" visible_range; + Callback.register "lui_ocaml_virtual_window" virtual_window; Callback.register "lui_ocaml_picked" picked; Callback.register "lui_ocaml_dispose" dispose; Callback.register "lui_ocaml_root_node" root_node; diff --git a/app/journal_lui_bridge.c b/app/journal_lui_bridge.c index f100d57..06417cb 100644 --- a/app/journal_lui_bridge.c +++ b/app/journal_lui_bridge.c @@ -324,6 +324,52 @@ LUI_EXPORT int32_t lui_ocaml_picked(int64_t node, const char *payload) { return dispatch_string("lui_ocaml_picked", node, payload); } +LUI_EXPORT int32_t lui_ocaml_virtual_rows_version(void) { + return 1; +} + +LUI_EXPORT int32_t lui_ocaml_virtual_window(int64_t node, int64_t generation, + int64_t first, int64_t last, + const int64_t *pins, int32_t count) { + const char *name = "lui_ocaml_virtual_window"; + if (!runtime_started || node <= 0 || node > Max_long || generation <= 0 || + generation > Max_long || first < 0 || last < first || last > Max_long || + count < 0 || (count > 0 && pins == NULL)) { + completed(operation_for(name),4); + return 0; + } + for (int32_t index = 0; index < count; index++) { + if (pins[index] <= 0 || pins[index] > Max_long) { + completed(operation_for(name),4); + return 0; + } + } + patch_response response = pending_response(name,0); + caml_leave_blocking_section(); + const value *dispatch = caml_named_value(name); + if (dispatch != NULL) { + CAMLparam0(); + CAMLlocal3(list, cell, result); + CAMLlocalN(arguments, 5); + list = Val_emptylist; + for (int32_t index = count; index > 0; index--) { + cell = caml_alloc_small(2,0); + Field(cell,0) = Val_long(pins[index - 1]); + Field(cell,1) = list; + list = cell; + } + arguments[0] = Val_long(node); + arguments[1] = Val_long(generation); + arguments[2] = Val_long(first); + arguments[3] = Val_long(last); + arguments[4] = list; + result = caml_callbackN_exn(*dispatch,5,arguments); + response = copy_patch(name,result); + CAMLdrop; + } + return release_and_deliver(response); +} + LUI_EXPORT int32_t lui_ocaml_stop(void) { patch_response response = pending_response("lui_ocaml_dispose",0); caml_leave_blocking_section(); diff --git a/apple-tests/native-events/JournalLUIEventsTests.swift b/apple-tests/native-events/JournalLUIEventsTests.swift index 86116f0..6bdc0db 100644 --- a/apple-tests/native-events/JournalLUIEventsTests.swift +++ b/apple-tests/native-events/JournalLUIEventsTests.swift @@ -11,6 +11,13 @@ private func start( private func picked(_ node: Int64, _ payload: UnsafePointer?) -> Int32 @_silgen_name("lui_ocaml_root_node") private func rootNode() -> Int64 +@_silgen_name("lui_ocaml_virtual_rows_version") +private func virtualRowsVersion() -> Int32 +@_silgen_name("lui_ocaml_virtual_window") +private func virtualWindow( + _ node: Int64, _ generation: Int64, _ first: Int64, _ last: Int64, + _ pins: UnsafePointer?, _ count: Int32 +) -> Int32 @_silgen_name("journal_ocaml_pump") private func pump() -> Int32 @_silgen_name("lui_ocaml_stop") @@ -64,6 +71,7 @@ private let receivePatch: PatchCallback = { source in } static func main() throws { + try check(virtualRowsVersion() == 1, "bounded-row capability unavailable") try check("{}".withCString { start(receivePatch, 2, 2, $0, 2) } == 1, "runtime failed to start") try check(patches.count == 1 && callbackFailure == nil, "startup callback failed") let node = (1 << 40) + 7 @@ -100,6 +108,13 @@ private let receivePatch: PatchCallback = { source in ), (.visibleRange(node: node, first: 0, last: 14), "visibleRange:0:14"), (.visibleRange(node: node, first: -1, last: -1), "visibleRange:-1:-1"), + (.virtualWindow(node: node, generation: node + 1, first: 10, last: 10, pins: []), + "virtualWindow:\(node + 1):10:10:"), + (.virtualWindow(node: node, generation: node + 1, first: 0, last: 14, pins: []), + "virtualWindow:\(node + 1):0:14:"), + (.virtualWindow(node: node, generation: node + 1, first: node + 2, last: node + 3, + pins: [node + 4, node + 5]), + "virtualWindow:\(node + 1):\(node + 2):\(node + 3):\(node + 4),\(node + 5)"), (.picked(node: node, payload: payload), "picked:" + payload), ( .extension( @@ -119,6 +134,20 @@ private let receivePatch: PatchCallback = { source in try check(callbackFailure == nil, "callback reentry failed: \(event)") } patches.removeAll() + for (id, generation, first, last, count) in [ + (Int64(0), Int64(1), Int64(0), Int64(1), Int32(0)), + (Int64.max, 1, 0, 1, 0), (Int64(node), 0, 0, 1, 0), + (Int64(node), 1, -1, 1, 0), (Int64(node), 1, 2, 1, 0), + (Int64(node), 1, 0, 1, -1), (Int64(node), 1, 0, 1, 1), + ] { + try check(virtualWindow(id, generation, first, last, nil, count) == 0 && patches.isEmpty, + "invalid virtual window reached the runtime") + } + for invalidPin in [Int64(0), -1, Int64.max] { + var pin = invalidPin + try check(virtualWindow(Int64(node), 1, 0, 1, &pin, 1) == 0 && patches.isEmpty, + "invalid virtual pin reached the runtime") + } try check(picked(Int64(node), nil) == 0 && patches.isEmpty, "null payload produced an event") try check(extensionEvent(Int64(node), nil, nil) == 0, "null extension accepted") try check( diff --git a/apple-tests/native-events/bridge_fixture.ml b/apple-tests/native-events/bridge_fixture.ml index b5592a5..6d23ebf 100644 --- a/apple-tests/native-events/bridge_fixture.ml +++ b/apple-tests/native-events/bridge_fixture.ml @@ -78,6 +78,9 @@ let dispatch = function patch node (Printf.sprintf "scrollCompleted:%d:%s" token outcome) | VisibleRange (node, first, last) -> patch node (Printf.sprintf "visibleRange:%d:%d" first last) + | VirtualWindow (node, generation, first, last, pins) -> + patch node (Printf.sprintf "virtualWindow:%d:%d:%d:%s" generation first last + (String.concat "," (List.map string_of_int pins))) | Picked (node, payload) -> patch node ("picked:" ^ payload) | PressModifiers (node, modifiers) -> patch node ("pressModifiers:" ^ string_of_int modifiers) | PressDetail (node, detail) -> pointer_patch "pressDetail" node detail @@ -87,6 +90,9 @@ let dispatch = function | PointerLeave node -> patch node "pointerLeave" | ContextMenuPress (node, detail) -> pointer_patch "contextMenuPress" node detail | Load node -> patch node "load" + | NavigatePrevious _ | NavigateNext _ | NavigationKey _ | ScrollEdges _ + | PointerMove _ | PointerCancel _ | ToggleChangedDetail _ -> + failwith "event is not emitted by the Apple host" | ExtensionEvent _ -> failwith "extension bypassed its host hook" ;; diff --git a/logseq_journal.opam b/logseq_journal.opam index 079c707..467eb0d 100644 --- a/logseq_journal.opam +++ b/logseq_journal.opam @@ -49,7 +49,7 @@ pin-depends: [ ["mldoc.dev" "git+https://github.com/logseq/mldoc.git#b3e06c030b7396f7992ebe1c3a4f35f5e6983200"] ["xmlm.dev" "git+https://github.com/logseq/xmlm.git#eb469d536e98c98f2754c0ff8813c92b3a17fe9f"] ["rrbvec.dev" "git+https://github.com/logseq/rrbvec.git#main"] - ["lui.0.1.0" "git+https://github.com/logseq/lui.git#4fdb9136026af7863a8d025133871e34864eace1"] + ["lui.0.1.0" "git+https://github.com/logseq/lui.git#93b209713acd852094af085737762e59161e2265"] ["ocaml-signal.0.1.0" "git+https://github.com/logseq/ocaml-signal.git#02e18cdc5e0ad672aa223f638eaa3ba52fc9421e"] ["datascript_ocaml.dev" "git+https://github.com/logseq/datascript-ocaml.git#0561660e4894faee250d551ab2a32a6b5c25a5fb"] ["datascript-ocaml-native.dev" "git+https://github.com/logseq/datascript-ocaml.git#0561660e4894faee250d551ab2a32a6b5c25a5fb"] diff --git a/logseq_journal.opam.locked b/logseq_journal.opam.locked index 4c34eee..966c897 100644 --- a/logseq_journal.opam.locked +++ b/logseq_journal.opam.locked @@ -25,7 +25,7 @@ pin-depends: [ ] [ "lui.0.1.0" - "git+https://github.com/logseq/lui.git#4fdb9136026af7863a8d025133871e34864eace1" + "git+https://github.com/logseq/lui.git#93b209713acd852094af085737762e59161e2265" ] [ "melange-edn-core.0.5.0" @@ -70,17 +70,24 @@ depends: [ "bigstringaf" {= "0.10.0"} "bos" {= "0.3.0"} "ca-certs-nss" {= "3.131.1"} + "checkseum" {= "0.5.4"} "cmdliner" {= "2.1.1"} "compiler-cloning" {= "disabled"} "conf-gmp" {= "5"} "conf-gmp-powm-sec" {= "4"} + "conf-libffi" {= "2.0.0"} "conf-pkg-config" {= "5"} + "conf-sdl2" {= "1"} + "conf-sdl2-ttf" {= "1"} "conf-sqlite3" {= "1"} "cppo" {= "1.8.0"} "csexp" {= "1.5.2"} "cstruct" {= "6.2.0"} + "ctypes" {= "0.24.0"} + "ctypes-foreign" {= "0.24.0"} "datascript-ocaml-native" {= "dev"} "datascript_ocaml" {= "dev"} + "decompress" {= "1.6.1"} "digestif" {= "1.3.1"} "domain-local-await" {= "1.0.1"} "domain-name" {= "0.5.0"} @@ -108,6 +115,8 @@ depends: [ "httpun-eio" {= "0.2.0"} "httpun-types" {= "0.2.0"} "httpun-ws" {= "0.2.0"} + "imagelib" {= "20221222"} + "integers" {= "0.8.0"} "iomux" {= "0.4"} "ipaddr" {= "5.6.2"} "js_of_ocaml" {= "6.4.1"} @@ -174,13 +183,17 @@ depends: [ "sqlite3" {= "5.4.0"} "stdlib-shims" {= "0.3.0"} "stringext" {= "1.6.0"} + "tgls" {= "0.9.1"} "thread-table" {= "1.0.0"} "tls" {= "2.1.2"} "tls-eio" {= "2.1.2"} "topkg" {= "1.1.1"} + "tsdl" {= "1.3.0"} + "tsdl-ttf" {= "0.7"} "uri" {= "4.4.0"} "uucp" {= "17.0.0"} "uunf" {= "17.0.0"} + "uuseg" {= "17.0.0"} "uutf" {= "1.0.4"} "x509" {= "1.1.1"} "yojson" {= "3.0.0"} diff --git a/swift/JournalLUIEvents.swift b/swift/JournalLUIEvents.swift index 256700d..9bbaa00 100644 --- a/swift/JournalLUIEvents.swift +++ b/swift/JournalLUIEvents.swift @@ -54,6 +54,11 @@ private func luiOCamlScrollCompleted( ) -> Int32 @_silgen_name("lui_ocaml_visible_range") private func luiOCamlVisibleRange(_ node: Int64, _ first: Int64, _ last: Int64) -> Int32 +@_silgen_name("lui_ocaml_virtual_window") +private func luiOCamlVirtualWindow( + _ node: Int64, _ generation: Int64, _ first: Int64, _ last: Int64, + _ pins: UnsafePointer?, _ count: Int32 +) -> Int32 @_silgen_name("lui_ocaml_picked") private func luiOCamlPicked(_ node: Int64, _ payload: UnsafePointer?) -> Int32 @_silgen_name("journal_ocaml_extension_event") @@ -99,6 +104,12 @@ private func journalOCamlExtensionEvent( return outcome.withCString { luiOCamlScrollCompleted(Int64(node), Int64(token), $0) } case .visibleRange(let node, let first, let last): return luiOCamlVisibleRange(Int64(node), Int64(first), Int64(last)) + case .virtualWindow(let node, let generation, let first, let last, let pins): + guard pins.count <= Int32.max else { return 0 } + return pins.map(Int64.init).withUnsafeBufferPointer { + luiOCamlVirtualWindow(Int64(node), Int64(generation), Int64(first), Int64(last), + $0.baseAddress, Int32($0.count)) + } case .picked(let node, let payload): return payload.withCString { luiOCamlPicked(Int64(node), $0) } case .extension(let node, _, let name, let values): diff --git a/test/source_boundary_test.ml b/test/source_boundary_test.ml index 9fac1f1..a756bae 100644 --- a/test/source_boundary_test.ml +++ b/test/source_boundary_test.ml @@ -1142,7 +1142,7 @@ let () = (* Journal uses the upstream native host ABI decoder; keep the OCaml installation and Apple package on the same validated revision. *) let compatible_lui_revision = - "git+https://github.com/logseq/lui.git#4fdb9136026af7863a8d025133871e34864eace1" + "git+https://github.com/logseq/lui.git#93b209713acd852094af085737762e59161e2265" in List.iter (fun relative -> require_occurrences root relative compatible_lui_revision 1)