aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 10:54:12 +0100
committereunhhu <[email protected]>2026-09-26 10:54:12 +0100
commitb230b70252f09cf3425a8b4b959b3dfd72075b31 (patch)
tree6ddae95ead87fa1905e133dbabf0f2d6a595efde
parentd58e1d29cc3cbd5f1c9b271c0b7e25b9962fad4f (diff)
downloadpure-b230b70252f09cf3425a8b4b959b3dfd72075b31.tar.gz
pure-b230b70252f09cf3425a8b4b959b3dfd72075b31.zip
Let a host point an index at an item
pure_app_point moves an index state to an item, as a user pointing at a row would: an event with no handler, saved like a key event, joining the burst when saving is deferred; past the last item it is not_found. A host learns which index to move from the view that shows it: pure_module_position gives the state behind a number view that is just `at.position`. An interface can so turn a click on a list row into its focus without any code in the app.
-rw-r--r--docs/design/008-module-format.md5
-rw-r--r--prototypes/connections/module.c20
-rw-r--r--prototypes/connections/module.h7
-rw-r--r--prototypes/connections/test-module.c32
4 files changed, 63 insertions, 1 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 2ee4bec..6d1b32e 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -151,6 +151,11 @@ text·리스트가 남을 때만 위치를 유지한다.
응답은 `pure_app_reply`로 순서대로 빌려준다. 페이로드와 문서의 익스텐트를 복사하지 않는다. 문서를 고친 이벤트의
문서 응답과 text 상태의 응답은 확정 전에 복사한다. text 보기는 `pure_app_view_text`로 빌려준다.
+호스트는 인덱스 상태를 한 항목으로 옮길 수 있다(`pure_app_point`, 사용자가 행을 가리키는 것). 핸들러 없는 이벤트이며
+키 이벤트처럼 저장되고, 마지막 항목을 넘으면 `not_found`다. 어느 인덱스를 옮길지는 보기가 알려 준다.
+`pure_module_position`은 수 보기가 인덱스 상태의 위치 그대로(`view tasks_focus = at.position`)이면 그 상태를 돌려준다.
+그래서 인터페이스(Vega)는 앱 코드 없이 목록의 클릭을 포커스로 옮긴다.
+
표 보기는 리스트 항목마다 행 하나를 가진다(`pure_app_rows`). 호스트는 보여 줄 칸만
`pure_app_cell`·`pure_app_cell_text`로 행 번호와 함께 계산한다. 화면 모델을 따로 만들거나 목록을 복사하지 않으므로,
10만 항목 목록에서 20줄을 그리는 표면은 20행만 계산한다(가상 스크롤이 런타임의 성질이 된다). 보기가 실패해도
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index ebb746f..5e2b98f 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -522,6 +522,13 @@ int pure_module_view(const PureModule *m, const char *name) {
return -1;
}
+int pure_module_position(const PureModule *m, uint32_t view) {
+ if (view >= m->views || m->view_type[view] != PURE_VIEW_NUM || m->view_table[view]) return -1;
+ const PureBlock *block = &m->view_code[view];
+ const PureOp *code = &m->code[block->start];
+ return block->length == 2 && code[0].op == PURE_OP_INDEX_LOAD && code[1].op == PURE_OP_INDEX_NUM ? (int)code[0].arg : -1;
+}
+
int pure_module_column(const PureModule *m, uint32_t table, const char *name) {
for (uint32_t i = 0; i < m->views; ++i)
if (m->view_table[i] == table + 1 && !strcmp(m->view_names[i], name)) return (int)i;
@@ -1121,6 +1128,19 @@ PureResult pure_app_input(PureApp *app, uint32_t input, PureEdge edge) {
return dispatch(app, 1 + 2 * input + (edge == PURE_EDGE_UP), &ev);
}
+PureResult pure_app_point(PureApp *app, uint32_t state, size_t position) {
+ const PureModule *m = app->module;
+ pure_app_finish(app);
+ if (state >= m->states || m->state_type[state] != PURE_STATE_INDEX) return PURE_INVALID;
+ if (position >= pure_list_count(app->lists[m->state_ref[state]])) return PURE_NOT_FOUND;
+ open_burst(app); /* what a failed save returns to */
+ app->fault = PURE_FAULT_NONE;
+ app->fault_pc = UINT32_MAX;
+ app->values[state] = (double)position;
+ ++app->events;
+ return app->deferred ? PURE_OK : pure_app_save(app);
+}
+
PureResult pure_app_text(PureApp *app, uint32_t input, PureSpan text) {
pure_app_finish(app);
if (input >= app->module->inputs || app->module->input[input].kind != PURE_KIND_TEXT ||
diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h
index c9fb606..16b755c 100644
--- a/prototypes/connections/module.h
+++ b/prototypes/connections/module.h
@@ -106,6 +106,9 @@ int pure_module_state(const PureModule *, const char *name);
int pure_module_input(const PureModule *, const char *name);
int pure_module_view(const PureModule *, const char *name);
int pure_module_column(const PureModule *, uint32_t table, const char *name);
+/* The index state a number view shows as its position (a view that is just
+ * `at.position`), or -1: what a host moves with pure_app_point. */
+int pure_module_position(const PureModule *, uint32_t view);
/* Keyed collections live in a data host (for example the tree store). One
* event opens at most one document, the message's key, at its current
@@ -189,6 +192,10 @@ bool pure_app_defer(PureApp *, bool deferred);
void pure_app_stop(PureApp *);
PureResult pure_app_frame(PureApp *);
PureResult pure_app_input(PureApp *, uint32_t input, PureEdge);
+/* Point an index state at the item at position (0 first), as a user pointing
+ * at a row: an event with no handler, saved like a key event. NOT_FOUND past
+ * the last item, INVALID for a state that is not an index. */
+PureResult pure_app_point(PureApp *, uint32_t state, size_t position);
/* Deliver committed text (for example from an input method). Not UTF-8: invalid. */
PureResult pure_app_text(PureApp *, uint32_t input, PureSpan text);
/* Deliver a message. base is the revision the sender edited, or -1 for none;
diff --git a/prototypes/connections/test-module.c b/prototypes/connections/test-module.c
index 6f48bc8..b0666fe 100644
--- a/prototypes/connections/test-module.c
+++ b/prototypes/connections/test-module.c
@@ -1714,6 +1714,35 @@ static void deferred(void) {
deferred_messages();
}
+/* A host points at an item: the index a view shows as its position moves
+ * there, as an event with no handler, saved like a key event; deferred, it
+ * joins the burst and a failed save undoes it with the rest. */
+static void pointing(void) {
+ CHECK(load(list_spec, &module_a) == PURE_OK);
+ CHECK(pure_module_position(&module_a, 1) == 2); /* position = at.position */
+ CHECK(pure_module_position(&module_a, 0) == -1 && pure_module_position(&module_a, 2) == -1 &&
+ pure_module_position(&module_a, 3) == -1 && pure_module_position(&module_a, 99) == -1);
+ char path[] = "/tmp/pure-point-XXXXXX";
+ CHECK(mkdtemp(path) && pure_store_init(path) == PURE_OK);
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
+ CHECK(pure_app_point(&app, 2, 0) == PURE_NOT_FOUND); /* nothing to point at */
+ for (int i = 0; i < 3; ++i) CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)"a", 1}) == PURE_OK);
+ uint64_t events = app.events;
+ CHECK(app.values[2] == 2 && pure_app_point(&app, 2, 0) == PURE_OK && app.values[2] == 0 && app.events == events + 1);
+ CHECK(pure_app_point(&app, 2, 3) == PURE_NOT_FOUND && app.values[2] == 0);
+ CHECK(pure_app_point(&app, 0, 1) == PURE_INVALID && pure_app_point(&app, 1, 1) == PURE_INVALID); /* a number, a list */
+ CHECK(pure_app_point(&app, 2, 1) == PURE_OK);
+ pure_app_stop(&app);
+ CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[2] == 1);
+ CHECK(pure_app_defer(&app, true) && pure_app_point(&app, 2, 2) == PURE_OK && app.open && app.values[2] == 2);
+ CHECK(pure_app_save(&app) == PURE_OK);
+ pure_app_stop(&app);
+ CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[2] == 2);
+ pure_app_stop(&app);
+ remove_store(path);
+}
+
int main(void) {
behaviour();
faults();
@@ -1724,12 +1753,13 @@ int main(void) {
texts();
text_mutations();
lists();
+ pointing();
list_mutations();
tables();
table_mutations();
stored_states();
deferred();
printf("{\"sizeof_module\":%zu,\"sizeof_app\":%zu}\n", sizeof(PureModule), sizeof(PureApp));
- puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts\"}");
+ puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing\"}");
return 0;
}