diff --git a/app/cli.c b/app/cli.c index bcde4876..dcb45ff1 100644 --- a/app/cli.c +++ b/app/cli.c @@ -28,7 +28,7 @@ #include "sheet/handlers_internal.h" #include "sheet/transformation.h" #endif -#include "sheet/procedure.h" +#include "../include/zsv/ext/procedure.h" #include "sheet/key-bindings.h" #include "sql_internal.h" @@ -432,6 +432,9 @@ static struct zsv_ext_callbacks *zsv_ext_callbacks_init(struct zsv_ext_callbacks e->ext_sheet_transformation_parser = zsvsheet_transformation_parser; e->ext_sheet_transformation_filename = zsvsheet_transformation_filename; e->ext_sheet_transformation_user_context = zsvsheet_transformation_user_context; + e->ext_sheet_open_context_menu = zsvsheet_ext_open_context_menu; + e->ext_sheet_context_menu_init = zsvsheet_ext_context_menu_init; + e->ext_sheet_context_menu_new_entry_func = zsvsheet_ext_context_menu_new_entry_func; #endif } return e; diff --git a/app/ext_example/Makefile b/app/ext_example/Makefile index ec7eb8fe..2b89648f 100644 --- a/app/ext_example/Makefile +++ b/app/ext_example/Makefile @@ -112,7 +112,7 @@ ${BUILD_DIR}/bin/cli: ${BUILD_DIR}/objs/utils/%.o: (cd .. && ${MAKE} CONFIGFILE=${CONFIGFILEPATH} CC=${CC} DEBUG=${DEBUG} $@ ) -TESTS=test-1 test-2 test-3 test-4 test-5 test-thirdparty +TESTS=test-1 test-2 test-3 test-4 test-5 test-thirdparty test-sheet-extension-context-menu test-sheet-extension-drill-down ifeq ($(ZSVSHEET_BUILD),1) TESTS+=test-sheet-extension-1 test-sheet-extension-2 endif @@ -198,6 +198,28 @@ test-thirdparty: test-%: ${CLI} ${TARGET} @${RUN_CLI} thirdparty >> /tmp/zsvext-$@.out @cmp /tmp/zsvext-$@.out test/expected/zsvext-$@.out && ${TEST_PASS} || ${TEST_FAIL} +test-sheet-extension-context-menu: ${CLI} ${TARGET_SHEET} ../test/worldcitiespop_mil.csv + ${TEST_INIT} + @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf + @${RUN_CLI} unregister mysheet 1>/dev/null || true + @${RUN_CLI} register mysheet 2>/dev/null + @tmux new-session -x 80 -y 25 -d -s "$@" "${RUN_CLI} sheet ../test/worldcitiespop_mil.csv" + @${RUN_CLI} unregister mysheet 1>/dev/null || true # unregister regardless of whether the test succeeds or not + @tmux send-keys -t $@ "v" "country" "Enter" + @tmux send-keys -t $@ "jjjj" "Enter" # go down a few rows and open menu + @${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL} + +test-sheet-extension-drill-down: ${CLI} ${TARGET_SHEET} ../test/worldcitiespop_mil.csv + ${TEST_INIT} + @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf + @${RUN_CLI} unregister mysheet 1>/dev/null || true + @${RUN_CLI} register mysheet 2>/dev/null + @tmux new-session -x 80 -y 25 -d -s "$@" "${RUN_CLI} sheet ../test/worldcitiespop_mil.csv" + @${RUN_CLI} unregister mysheet 1>/dev/null || true # unregister regardless of whether the test succeeds or not + @tmux send-keys -t $@ "v" "country" "Enter" + @tmux send-keys -t $@ "jjjj" "Enter" "Enter" # go down a few rows and open menu and drill down + @${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL} + clean: @rm -f ${TARGET} ${TARGET_SHEET} /tmp/zsvext-test*.out diff --git a/app/ext_example/mysheet_extension.c b/app/ext_example/mysheet_extension.c index 2111572c..255e460f 100644 --- a/app/ext_example/mysheet_extension.c +++ b/app/ext_example/mysheet_extension.c @@ -12,6 +12,8 @@ #include "../external/sqlite3/sqlite3.h" #include #include +#include +#include #include #include #include @@ -207,13 +209,13 @@ static zsvsheet_status zsv_sqlite3_to_csv(zsvsheet_proc_context_t pctx, struct z return zst; } -// -zsvsheet_status pivot_drill_down(zsvsheet_proc_context_t ctx) { +zsvsheet_status context_menu_drill_down(zsvsheet_proc_context_t ctx) { enum zsvsheet_status zst = zsvsheet_status_ok; char result_buffer[256] = {0}; zsvsheet_buffer_t buff = zsv_cb.ext_sheet_buffer_current(ctx); struct pivot_data *pd; struct zsvsheet_rowcol rc; + if (zsv_cb.ext_sheet_buffer_get_ctx(buff, (void **)&pd) != zsv_ext_status_ok || zsv_cb.ext_sheet_buffer_get_selected_cell(buff, &rc) != zsvsheet_status_ok) { return zsvsheet_status_error; @@ -255,6 +257,18 @@ zsvsheet_status pivot_drill_down(zsvsheet_proc_context_t ctx) { return zst; } +zsvsheet_status pivot_open_menu(zsvsheet_proc_context_t ctx) { + char entry_name[64]; + struct context_menu menu; + if (zsv_cb.ext_sheet_context_menu_init(&menu)) + return zsvsheet_status_error; + snprintf(entry_name, sizeof(entry_name), "NIGGER"); + if (zsv_cb.ext_sheet_context_menu_new_entry_func(&menu, "Drill-down", context_menu_drill_down)) + return zsvsheet_status_error; + zsv_cb.ext_sheet_open_context_menu(ctx->subcommand_context, &menu); + return zsvsheet_status_ok; +} + /** * Here we define a custom command for the zsv `sheet` feature */ @@ -294,7 +308,7 @@ zsvsheet_status my_pivot_table_command_handler(zsvsheet_proc_context_t ctx) { zsvsheet_buffer_t buff = zsv_cb.ext_sheet_buffer_current(ctx); zsv_cb.ext_sheet_buffer_set_ctx(buff, pd, pivot_data_delete); zsv_cb.ext_sheet_buffer_set_cell_attrs(buff, get_cell_attrs); - zsv_cb.ext_sheet_buffer_on_newline(buff, pivot_drill_down); + zsv_cb.ext_sheet_buffer_on_newline(buff, pivot_open_menu); pd = NULL; // so that it isn't cleaned up below } } @@ -319,6 +333,7 @@ zsvsheet_status my_pivot_table_command_handler(zsvsheet_proc_context_t ctx) { enum zsv_ext_status zsv_ext_init(struct zsv_ext_callbacks *cb, zsv_execution_context ctx) { zsv_cb = *cb; + zsv_cb.ext_set_help(ctx, "Sample zsv sheet extension"); zsv_cb.ext_set_license(ctx, "Unlicense. See https://github.com/spdx/license-list-data/blob/master/text/Unlicense.txt"); diff --git a/app/ext_example/test/expected/test-sheet-extension-context-menu.out b/app/ext_example/test/expected/test-sheet-extension-context-menu.out new file mode 100644 index 00000000..13526957 --- /dev/null +++ b/app/ext_example/test/expected/test-sheet-extension-context-menu.out @@ -0,0 +1,25 @@ +Row # value Count +1 ad 27 +2 ae 147 +3 af 27822 +4 ag 55 +5 ai 14 +* Drill-down al 4730 +7 am 896 +8 an 75 +9 ao 6170 +10 ar 2817 +11 at 4740 +12 au 3403 +13 aw 37 +14 az 3522 +15 ba 5076 +16 bb 165 +17 bd 8280 +18 be 5056 +19 bf 3248 +20 bg 6258 +21 bh 102 +22 bi 646 +23 bj 1331 +(building index) 5 diff --git a/app/ext_example/test/expected/test-sheet-extension-drill-down.out b/app/ext_example/test/expected/test-sheet-extension-drill-down.out new file mode 100644 index 00000000..05071db2 --- /dev/null +++ b/app/ext_example/test/expected/test-sheet-extension-drill-down.out @@ -0,0 +1,25 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +89472 ai betty hil Betty Hil 00 18.216666 -63.0 +89474 ai cannifist Cannifist 00 18.233333 -63.01666 +89480 ai east end East End 00 18.233333 -63.0 +89485 ai little di Little Di 00 18.233333 -63.03333 +89488 ai long grou Long Grou 00 18.2 -63.05 +89490 ai lower sou Lower Sou 00 where cou 18.183333 -63.1 +89492 ai mount for Mount For 00 18.233333 -62.98333 +89494 ai north sid North Sid 00 18.216666 -63.05 +89496 ai saint mar Saint Mar 00 18.216666 -63.06666 +89502 ai the copse The Copse 00 18.216666 -62.98333 +89504 ai the fores The Fores 00 18.2 -63.03333 +89506 ai the valle The Valle 00 1379 18.216666 -63.05 +89509 ai welches h Welches H 00 18.233333 -63.01666 +89512 ai white hil White Hil 00 18.25 -63.0 + + + + + + + + + +(building index) 89472 diff --git a/app/sheet.c b/app/sheet.c index a34ed39d..da68dc60 100644 --- a/app/sheet.c +++ b/app/sheet.c @@ -30,6 +30,7 @@ #include "sheet/screen_buffer.c" #include "sheet/lexer.c" #include "sheet/procedure.c" +#include "sheet/context-menu.c" /* TODO: move this somewhere else like common or utils */ #define UNUSED(X) ((void)X) @@ -74,8 +75,22 @@ struct zsvsheet_sheet_context { struct zsvsheet_display_info display_info; char *find; struct zsv_prop_handler *custom_prop_handler; + + struct { + bool active; + int row; + int col; + struct context_menu menu; + } context_menu; }; +void zsvsheet_display_context_menu(struct zsvsheet_sheet_context *state) { + if (state->context_menu.active) { + context_menu_display(&state->context_menu.menu, state->context_menu.row, state->context_menu.col, + state->display_info.dimensions); + } +} + static void get_subcommand(const char *prompt, char *buff, size_t buffsize, int footer_row) { *buff = '\0'; // this is a hack to blank-out the currently-selected cell value @@ -414,7 +429,6 @@ static zsvsheet_status zsvsheet_filter_handler(struct zsvsheet_proc_context *ctx struct zsvsheet_ui_buffer *current_ui_buffer = *state->display_info.ui_buffers.current; int prompt_footer_row = (int)(di->dimensions->rows - di->dimensions->footer_span); struct zsvsheet_buffer_info_internal binfo = zsvsheet_buffer_info_internal(current_ui_buffer); - int err; const char *filter; if (binfo.write_in_progress && !binfo.write_done) @@ -439,6 +453,28 @@ static zsvsheet_status zsvsheet_filter_handler(struct zsvsheet_proc_context *ctx return zsvsheet_status_ok; } +void zsvsheet_ext_open_context_menu(void *_state, const struct context_menu *menu) { + struct zsvsheet_sheet_context *state = (struct zsvsheet_sheet_context *)_state; + struct zsvsheet_ui_buffer *current_ui_buffer = *(state->display_info.ui_buffers.current); + struct zsvsheet_display_dimensions *ddims = state->display_info.dimensions; + size_t cell_display_width = zsvsheet_cell_display_width(current_ui_buffer, ddims); + + state->context_menu.row = current_ui_buffer->cursor_row; + state->context_menu.col = current_ui_buffer->cursor_col * cell_display_width; + state->context_menu.menu = *menu; + state->context_menu.active = true; + + zsvsheet_display_context_menu(state); +} + +int zsvsheet_ext_context_menu_init(struct context_menu *menu) { + return context_menu_init(menu); +} + +int zsvsheet_ext_context_menu_new_entry_func(struct context_menu *menu, const char *name, zsvsheet_proc_fn handler) { + return context_menu_new_entry_func(menu, name, handler); +} + static zsvsheet_status zsvsheet_subcommand_handler(struct zsvsheet_proc_context *ctx) { char prompt_buffer[256] = {0}; struct zsvsheet_sheet_context *state = (struct zsvsheet_sheet_context *)ctx->subcommand_context; @@ -547,6 +583,31 @@ zsvsheet_status zsvsheet_builtin_proc_handler(struct zsvsheet_proc_context *ctx) struct zsvsheet_sheet_context *state = (struct zsvsheet_sheet_context *)ctx->subcommand_context; struct zsvsheet_ui_buffer *current_ui_buffer = *(state->display_info.ui_buffers.current); + if (state->context_menu.active) { + zsvsheet_status context_menu_status = zsvsheet_status_ok; + switch (ctx->proc_id) { + case zsvsheet_builtin_proc_move_up: + context_menu_move_up(&state->context_menu.menu); + break; + case zsvsheet_builtin_proc_move_down: + context_menu_move_down(&state->context_menu.menu); + break; + case zsvsheet_builtin_proc_escape: + state->context_menu.active = false; + break; + case zsvsheet_builtin_proc_confirm: + context_menu_status = context_menu_confirm(&state->context_menu.menu, state); + state->context_menu.active = false; + break; + default: + state->context_menu.active = false; + goto non_context_menu_action; + } + zsvsheet_display_context_menu(state); + return context_menu_status; + } +non_context_menu_action: + switch (ctx->proc_id) { case zsvsheet_builtin_proc_quit: // while(*state->display_info.ui_buffers.current) @@ -588,6 +649,8 @@ zsvsheet_status zsvsheet_builtin_proc_handler(struct zsvsheet_proc_context *ctx) } } break; + case zsvsheet_builtin_proc_confirm: + return zsvsheet_newline_handler(ctx); case zsvsheet_builtin_proc_find: return zsvsheet_find(state, false); @@ -624,7 +687,7 @@ struct builtin_proc_desc { { zsvsheet_builtin_proc_filter, "filter", "Hide rows that do not contain the specified text", zsvsheet_filter_handler }, { zsvsheet_builtin_proc_subcommand, "subcommand", "Editor subcommand", zsvsheet_subcommand_handler }, { zsvsheet_builtin_proc_help, "help", "Display a list of actions and key-bindings", zsvsheet_help_handler }, - { zsvsheet_builtin_proc_newline, "","Follow hyperlink (if any)", zsvsheet_newline_handler }, + { zsvsheet_builtin_proc_confirm, "confirm", "Confirm", zsvsheet_builtin_proc_handler }, { -1, NULL, NULL, NULL } }; /* clang-format on */ @@ -726,6 +789,7 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op .find = NULL, .custom_prop_handler = custom_prop_handler, }; + memset(&handler_state.context_menu, 0, sizeof(handler_state.context_menu)); zsvsheet_status status; @@ -761,6 +825,7 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op } display_buffer_subtable(ub, header_span, &display_dims); + zsvsheet_display_context_menu(&handler_state); } endwin(); diff --git a/app/sheet/context-menu.c b/app/sheet/context-menu.c new file mode 100644 index 00000000..07b9ce41 --- /dev/null +++ b/app/sheet/context-menu.c @@ -0,0 +1,147 @@ +#include "../include/zsv/ext/context-menu.h" +#include "../include/zsv/ext/procedure.h" + +#ifndef MIN +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#endif +#ifndef MAX +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#endif + +struct context_menu_entry { + struct context_menu_entry *prev; + struct context_menu_entry *next; + char *name; + zsvsheet_proc_id_t proc_id; + /* Entries can be created on stack and linked to the menu but when using + * using context_menu_add_entry* the entries are allocated. + * This bit indicates whether or not this entry should be freed on cleanup. + */ + uint8_t allocated : 1; + uint8_t proc_ephemeral : 1; +}; + +int context_menu_init(struct context_menu *menu) { + memset(menu, 0, sizeof(*menu)); + return 0; +} + +void context_menu_add_entry(struct context_menu *menu, struct context_menu_entry *entry) { + if (menu->last_entry) { + menu->last_entry->next = entry; + entry->prev = menu->last_entry; + menu->last_entry = entry; + menu->last_entry->next = NULL; + } else { + assert(menu->entries == NULL); + menu->entries = menu->last_entry = entry; + menu->last_entry->next = menu->last_entry->prev = NULL; + menu->selected_entry = entry; + } +} + +int _context_menu_alloc_entry(struct context_menu *menu, const char *name, zsvsheet_proc_id_t proc_id, + bool proc_ephemeral) { + struct context_menu_entry *entry = malloc(sizeof(struct context_menu_entry)); + if (!entry) + return -ENOMEM; + memset(entry, 0, sizeof(*entry)); + entry->name = strdup(name); + entry->proc_id = proc_id; + entry->allocated = true; + entry->proc_ephemeral = proc_ephemeral; + context_menu_add_entry(menu, entry); + return 0; +} + +/* New entry calling existing procedure */ +int context_menu_new_entry(struct context_menu *menu, const char *name, zsvsheet_proc_id_t proc_id) { + return _context_menu_alloc_entry(menu, name, proc_id, false); +} + +/* New entry calling existing a newly created procedure */ +int context_menu_new_entry_func(struct context_menu *menu, const char *name, zsvsheet_proc_fn handler) { + zsvsheet_proc_id_t proc_id = zsvsheet_register_proc(NULL, NULL, handler); + if (!is_valid_proc_id(proc_id)) + return -EINVAL; + return _context_menu_alloc_entry(menu, name, proc_id, true); +} + +int context_menu_display(struct context_menu *menu, int row, int col, + struct zsvsheet_display_dimensions *display_dims) { + int padding = 2; + int menu_width = 0, menu_height = 0; + + for (struct context_menu_entry *entry = menu->entries; entry; entry = entry->next) { + int len = strlen(entry->name); + menu_width = MAX(menu_width, len); + menu_height += 1; + } + /* add padding */ + menu_width += padding * 2; + + /* Make sure we don't overrun the screen horizontally */ + col -= MAX(0, col + menu_width - (int)display_dims->columns); + + if (row + menu_height > (int)(display_dims->rows - display_dims->footer_span - display_dims->header_span)) + row -= menu_height; + else + row += 1; /* Display the menu under the selected cell by default */ + + attron(A_REVERSE); + int i = 0; + for (struct context_menu_entry *entry = menu->entries; entry; entry = entry->next) { + mvprintw(row + i, col, "%-*s", menu_width, ""); + if (entry == menu->selected_entry) + attron(A_UNDERLINE); + mvprintw(row + i, col + padding, "%s", entry->name); + if (entry == menu->selected_entry) { + attroff(A_UNDERLINE); + if (padding) /* add a lil star if we have space */ + mvprintw(row + i, col, "*"); + } + i += 1; + } + attroff(A_REVERSE); + return 0; +} + +zsvsheet_status context_menu_confirm(struct context_menu *menu, void *subcommand_context) { + if (!menu->selected_entry) + return zsvsheet_status_error; + + zsvsheet_proc_id_t proc_id = menu->selected_entry->proc_id; + struct zsvsheet_proc_context context = { + .proc_id = proc_id, + .invocation.type = zsvsheet_proc_invocation_type_context_menu, + .invocation.interactive = true, + .invocation.u.context_menu.entry = menu->selected_entry, + .subcommand_context = subcommand_context, + }; + return zsvsheet_proc_invoke(proc_id, &context); +} + +void context_menu_move_up(struct context_menu *menu) { + if (menu->selected_entry && menu->selected_entry->prev) + menu->selected_entry = menu->selected_entry->prev; +} + +void context_menu_move_down(struct context_menu *menu) { + if (menu->selected_entry && menu->selected_entry->next) + menu->selected_entry = menu->selected_entry->next; +} + +void context_menu_cleanup(struct context_menu *menu) { + struct context_menu_entry *entry = menu->entries, *next; + while (entry) { + next = entry->next; + entry->next = NULL; + free(entry->name); + if (entry->proc_ephemeral) + zsvsheet_unregister_proc(entry->proc_id); + if (entry->allocated) + free(entry); + entry = next; + } + memset(menu, 0, sizeof(*menu)); +} diff --git a/app/sheet/handlers.c b/app/sheet/handlers.c index 4b6306c8..241dbc14 100644 --- a/app/sheet/handlers.c +++ b/app/sheet/handlers.c @@ -1,5 +1,5 @@ #include "file.h" -#include "procedure.h" +#include "../include/zsv/ext/procedure.h" static void zsvsheet_key_handlers_delete(struct zsvsheet_key_data **root, struct zsvsheet_key_data ***nextp) { for (struct zsvsheet_key_data *next, *e = *root; e; e = next) { diff --git a/app/sheet/handlers_internal.h b/app/sheet/handlers_internal.h index b639fe45..81b841f1 100644 --- a/app/sheet/handlers_internal.h +++ b/app/sheet/handlers_internal.h @@ -120,9 +120,18 @@ zsvsheet_status zsvsheet_register_command(int ch, const char *long_name, */ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, struct zsvsheet_buffer_transformation_opts opts); -#endif struct zsvsheet_buffer_data zsvsheet_buffer_info(zsvsheet_buffer_t buff); /** cell formatting **/ zsvsheet_cell_attr_t zsvsheet_cell_profile_attrs(enum zsvsheet_cell_profile_t); + +struct context_menu; + +int zsvsheet_ext_context_menu_init(struct context_menu *menu); + +int zsvsheet_ext_context_menu_new_entry_func(struct context_menu *menu, const char *name, zsvsheet_proc_fn handler); + +void zsvsheet_ext_open_context_menu(void *zsvsheet_sheet_context, const struct context_menu *menu); + +#endif \ No newline at end of file diff --git a/app/sheet/key-bindings.c b/app/sheet/key-bindings.c index c1b5bdc7..d98dff40 100644 --- a/app/sheet/key-bindings.c +++ b/app/sheet/key-bindings.c @@ -1,4 +1,4 @@ -#include "procedure.h" +#include "../include/zsv/ext/procedure.h" #include "key-bindings.h" #include @@ -170,10 +170,12 @@ struct zsvsheet_key_binding zsvsheet_vim_key_bindings[] = { /* Open is a subcommand only in vim. Keeping the binding for now */ { .ch = 'e', .proc_id = zsvsheet_builtin_proc_open_file, }, { .ch = 'f', .proc_id = zsvsheet_builtin_proc_filter, }, + { .ch = 'c', .proc_id = zsvsheet_builtin_proc_open_cell_context_menu, }, { .ch = ':', .proc_id = zsvsheet_builtin_proc_subcommand, }, { .ch = '?', .proc_id = zsvsheet_builtin_proc_help, }, - { .ch = '\n', .proc_id = zsvsheet_builtin_proc_newline, }, - { .ch = '\r', .proc_id = zsvsheet_builtin_proc_newline, }, + { .ch = '\n', .proc_id = zsvsheet_builtin_proc_confirm, }, + { .ch = '\r', .proc_id = zsvsheet_builtin_proc_confirm, }, + { .ch = KEY_ENTER, .proc_id = zsvsheet_builtin_proc_confirm, }, { .ch = -1 } }; @@ -234,8 +236,9 @@ struct zsvsheet_key_binding zsvsheet_emacs_key_bindings[] = { /* No such thing in emacs, find a more suitable binding */ { .ch = 'f', .proc_id = zsvsheet_builtin_proc_filter, }, { .ch = ZSVSHEET_CTRL('h'), .proc_id = zsvsheet_builtin_proc_help, }, - { .ch = '\n', .proc_id = zsvsheet_builtin_proc_newline, }, - { .ch = '\r', .proc_id = zsvsheet_builtin_proc_newline, }, + { .ch = '\n', .proc_id = zsvsheet_builtin_proc_confirm, }, + { .ch = '\r', .proc_id = zsvsheet_builtin_proc_confirm, }, + { .ch = KEY_ENTER, .proc_id = zsvsheet_builtin_proc_confirm, }, { .ch = -1 } }; diff --git a/app/sheet/key-bindings.h b/app/sheet/key-bindings.h index 913afc65..4a217216 100644 --- a/app/sheet/key-bindings.h +++ b/app/sheet/key-bindings.h @@ -1,6 +1,6 @@ #ifndef ZSVSHEET_KEY_BINDINGS_H #define ZSVSHEET_KEY_BINDINGS_H -#include "procedure.h" +#include "../include/zsv/ext/procedure.h" enum zsvsheet_key { zsvsheet_key_unknown = 0, diff --git a/app/sheet/procedure.c b/app/sheet/procedure.c index b821da9b..a8bdfe56 100644 --- a/app/sheet/procedure.c +++ b/app/sheet/procedure.c @@ -1,4 +1,4 @@ -#include "procedure.h" +#include "../include/zsv/ext/procedure.h" #if 0 #define proc_debug(...) fprintf(stderr, __VA_ARGS__) @@ -132,3 +132,9 @@ zsvsheet_proc_id_t zsvsheet_register_proc(const char *name, const char *descript .id = zsvsheet_generate_proc_id(), .name = name, .description = description, .handler = handler}; return zsvsheet_do_register_proc(&procedure); } + +void zsvsheet_unregister_proc(zsvsheet_proc_id_t id) { + struct zsvsheet_procedure *proc = &procedure_lookup[id]; + proc_debug("unregister proc %d %s\n", proc->id, proc->name ? proc->name : "(unnamed)"); + proc->id = ZSVSHEET_PROC_INVALID; +} diff --git a/app/test/Makefile b/app/test/Makefile index 62cf3ed6..225c6aaa 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -825,4 +825,3 @@ benchmark-sheet-index: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "G" && \ ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) - diff --git a/include/zsv/ext.h b/include/zsv/ext.h index 392399a2..6fa2e1fc 100644 --- a/include/zsv/ext.h +++ b/include/zsv/ext.h @@ -19,6 +19,8 @@ #include "utils/sql.h" #include "utils/prop.h" #include "utils/writer.h" +#include "ext/procedure.h" +#include "ext/context-menu.h" /** * @file ext.h @@ -312,6 +314,14 @@ struct zsv_ext_callbacks { */ void *(*ext_sheet_transformation_user_context)(zsvsheet_transformation trn); + int (*ext_sheet_context_menu_init)(struct context_menu *menu); + + int (*ext_sheet_context_menu_new_entry_func)(struct context_menu *menu, const char *name, zsvsheet_proc_fn handler); + /* + * Open provided context menu at the current cell + */ + void (*ext_sheet_open_context_menu)(void *zsvsheet_sheet_context, const struct context_menu *menu); + /** * Get the parser from the context provided to a transformation row handler */ diff --git a/include/zsv/ext/context-menu.h b/include/zsv/ext/context-menu.h new file mode 100644 index 00000000..b5f85710 --- /dev/null +++ b/include/zsv/ext/context-menu.h @@ -0,0 +1,21 @@ +#ifndef _ZSVSHEET_CONTEXT_MENU_H_ +#define _ZSVSHEET_CONTEXT_MENU_H_ +#include "procedure.h" + +struct context_menu_entry; + +struct context_menu { + struct context_menu_entry *entries; + struct context_menu_entry *last_entry; + struct context_menu_entry *selected_entry; +}; + +int context_menu_init(struct context_menu *menu); + +int context_menu_new_entry(struct context_menu *menu, const char *name, zsvsheet_proc_id_t proc_id); + +int context_menu_new_entry_func(struct context_menu *menu, const char *name, zsvsheet_proc_fn handler); + +zsvsheet_status context_menu_confirm(struct context_menu *menu, void *subcommand_context); + +#endif /* _ZSVSHEET_CONTEXT_MENU_H_ */ diff --git a/app/sheet/procedure.h b/include/zsv/ext/procedure.h similarity index 94% rename from app/sheet/procedure.h rename to include/zsv/ext/procedure.h index 5c36a309..567c0728 100644 --- a/app/sheet/procedure.h +++ b/include/zsv/ext/procedure.h @@ -3,6 +3,8 @@ #include #include +struct context_menu_entry; + /* ID's of bulitin procedures, extensions can register more. * * TODO: What specific procedures are bulitin and what are their @@ -30,10 +32,12 @@ enum { zsvsheet_builtin_proc_open_file, zsvsheet_builtin_proc_resize, zsvsheet_builtin_proc_prompt, + zsvsheet_builtin_proc_open_cell_context_menu, zsvsheet_builtin_proc_subcommand, zsvsheet_builtin_proc_help, zsvsheet_builtin_proc_vim_g_key_binding_dmux, zsvsheet_builtin_proc_newline, + zsvsheet_builtin_proc_confirm, }; #define ZSVSHEET_PROC_INVALID 0 @@ -45,6 +49,7 @@ enum { enum zsvsheet_proc_invocation_type { zsvsheet_proc_invocation_type_keypress, zsvsheet_proc_invocation_type_proc, + zsvsheet_proc_invocation_type_context_menu, /* Add more... */ }; @@ -66,6 +71,9 @@ struct zsvsheet_proc_context { struct { zsvsheet_proc_id_t id; } proc; + struct { + struct context_menu_entry *entry; + } context_menu; } u; } invocation; diff --git a/include/zsv/ext/sheet.h b/include/zsv/ext/sheet.h index f3e6b7e6..a08acb04 100644 --- a/include/zsv/ext/sheet.h +++ b/include/zsv/ext/sheet.h @@ -16,6 +16,9 @@ typedef enum zsvsheet_status { zsvsheet_status_busy, } zsvsheet_status; +struct zsvsheet_proc_context; +typedef zsvsheet_status (*zsvsheet_proc_fn)(struct zsvsheet_proc_context *ctx); + typedef struct zsvsheet_context *zsvsheet_context_t; typedef struct zsvsheet_subcommand_context *zsvsheet_subcommand_context_t;