From 8d2e18bd1a30bf65812efab3d9c20793a7c454f4 Mon Sep 17 00:00:00 2001 From: "A. Madani" Date: Thu, 30 Jul 2026 15:00:40 +0300 Subject: [PATCH 1/5] debugging and opt --- Makefile | 2 +- src/editor/editor_tab_lifecycle.inc.c | 1 + .../editor_tab_signals_preferences.inc.c | 206 +++++++++++++++--- src/editor_tab.h | 1 + src/editor_tab_build.c | 8 +- src/editor_tab_gutter.c | 69 ++++++ src/editor_tab_keys.c | 56 ++++- src/editor_tab_undo.c | 4 +- 8 files changed, 313 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index 33ac46f..61d7ca3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ APP_NAME := graptos APP_ID := io.github.graptos.Editor -VERSION := 0.23.68 +VERSION := 0.23.69 PREFIX ?= /usr/local CC ?= cc BUILD_DIR := build diff --git a/src/editor/editor_tab_lifecycle.inc.c b/src/editor/editor_tab_lifecycle.inc.c index 585d114..f584769 100644 --- a/src/editor/editor_tab_lifecycle.inc.c +++ b/src/editor/editor_tab_lifecycle.inc.c @@ -484,6 +484,7 @@ void editor_tab_free(EditorTab *tab) { g_free(tab->completion_prefix); g_free(tab->lsp_completion_retry_key); g_free(tab->hover_word); + g_free(tab->last_typing_debug_key); if (tab->undo_stack) g_ptr_array_free(tab->undo_stack, TRUE); if (tab->redo_stack) g_ptr_array_free(tab->redo_stack, TRUE); diff --git a/src/editor/editor_tab_signals_preferences.inc.c b/src/editor/editor_tab_signals_preferences.inc.c index c8b1e0a..230ebcc 100644 --- a/src/editor/editor_tab_signals_preferences.inc.c +++ b/src/editor/editor_tab_signals_preferences.inc.c @@ -11,6 +11,17 @@ void editor_tab_set_syntax(EditorTab *tab, SyntaxDef *syntax, gboolean manual) { if (!tab) return; + if (tab->active_syntax == syntax && tab->manual_syntax_override == manual) { + return; + } + + /** + * @brief Apply syntax only when it actually changes. + * @details GtkSourceView language and style refresh is expensive because it + * clears transient tags and rebuilds source highlighting state. Auto + * detection can run while typing, so unchanged syntax must not reset + * the highlighter on every buffer edit. + */ tab->active_syntax = syntax; tab->manual_syntax_override = manual; editor_tab_update_highlight_engine(tab); @@ -24,7 +35,6 @@ void editor_tab_set_syntax(EditorTab *tab, SyntaxDef *syntax, gboolean manual) { editor_tab_cancel_live_work(tab); tab->diagnostic_warnings = 0u; tab->low_latency_mode_active = TRUE; - editor_tab_schedule_minimap_update(tab); } editor_tab_update_status(tab); } @@ -65,7 +75,8 @@ void on_mark_set(GtkTextBuffer *buffer, GtkTextIter *location, GtkTextMark *mark has_selection = gtk_text_buffer_get_has_selection(buffer); } - if (tab && tab->win && tab->win->debug_mode) { + if (tab && tab->win && tab->win->debug_mode && + (relevant_mark || tab->regex_tester_active || has_selection)) { g_message("Regex tester: mark-set name=%s has-selection=%d", name ? name : "(anonymous)", has_selection); @@ -99,6 +110,21 @@ void on_mark_set(GtkTextBuffer *buffer, GtkTextIter *location, GtkTextMark *mark } +/** + * @brief Milliseconds between monotonic timestamps. + * @details Typing debug logs use small timing checkpoints around the editor's + * hot path. Keeping conversion in one helper keeps the logs readable + * without changing the code paths being measured. + * @param start_us Start timestamp from g_get_monotonic_time(). + * @param end_us End timestamp from g_get_monotonic_time(). + * @return Elapsed milliseconds. + */ +static double typing_elapsed_ms(gint64 start_us, gint64 end_us) { + if (end_us < start_us) return 0.0; + return (double)(end_us - start_us) / 1000.0; +} + + /** * @brief React to editor buffer changes. * @details This callback is on the text-input critical path. It must not run @@ -117,14 +143,38 @@ void on_buffer_changed(GtkTextBuffer *buffer, gpointer user_data) { EditorTab *tab = user_data; if (!tab || tab->applying_change) return; + gboolean debug_typing = tab->win && tab->win->debug_mode; + gint64 typing_start_us = debug_typing ? g_get_monotonic_time() : 0; + double notes_ms = 0.0; + double undo_ms = 0.0; + double draw_ms = 0.0; + double schedule_ms = 0.0; + double minimap_schedule_ms = 0.0; + double preview_schedule_ms = 0.0; + double selection_schedule_ms = 0.0; + double color_schedule_ms = 0.0; + double diagnostics_schedule_ms = 0.0; + double syntax_schedule_ms = 0.0; + double completion_schedule_ms = 0.0; + double lsp_schedule_ms = 0.0; + double ui_schedule_ms = 0.0; + gboolean notes_saved = FALSE; + gboolean undo_copied = FALSE; + gboolean undo_changed = FALSE; + gboolean undo_cleared = FALSE; + gint line_delta = 0; + gint char_count = gtk_text_buffer_get_char_count(tab->buffer); guint line_count = (guint)gtk_text_buffer_get_line_count(tab->buffer); - if (tab->last_line_count != 0u && line_count != tab->last_line_count) { + if (tab->last_line_count != 0u && line_count != tab->last_line_count && + tab->notes && tab->notes->len > 0u) { + gint64 notes_start_us = debug_typing ? g_get_monotonic_time() : 0; GtkTextIter cursor; GtkTextMark *mark = gtk_text_buffer_get_insert(tab->buffer); gtk_text_buffer_get_iter_at_mark(tab->buffer, &cursor, mark); gint edit_line = gtk_text_iter_get_line(&cursor); gint delta = (gint)line_count - (gint)tab->last_line_count; + line_delta = delta; for (guint i = 0u; tab->notes && i < tab->notes->len; i++) { EditorNote *note = g_ptr_array_index(tab->notes, i); if (!note) continue; @@ -136,26 +186,36 @@ void on_buffer_changed(GtkTextBuffer *buffer, gpointer user_data) { } } editor_tab_save_notes(tab); + notes_saved = TRUE; if (tab->gutter) gtk_widget_queue_draw(tab->gutter); + if (debug_typing) { + notes_ms = typing_elapsed_ms(notes_start_us, g_get_monotonic_time()); + } + } else if (tab->last_line_count != 0u) { + line_delta = (gint)line_count - (gint)tab->last_line_count; } tab->last_line_count = line_count; gboolean large_file = !editor_tab_live_features_allowed(tab); - /* - * The snapshot undo implementation copies the buffer, so keep it only for - * small files. Large files rely on reduced live work rather than spending - * time duplicating megabytes of text after each edit. + /** + * @brief Keep undo snapshots out of the large-buffer path. + * @details The snapshot undo implementation copies the buffer, so keep it only for + * small files. Large files rely on reduced live work rather than spending + * time duplicating megabytes of text after each edit. */ guint undo_capture_limit = tab->win && tab->win->max_undo_capture_bytes > 0u ? tab->win->max_undo_capture_bytes : GRAPTOS_MAX_UNDO_CAPTURE_BYTES; + gint64 undo_start_us = debug_typing ? g_get_monotonic_time() : 0; if (!large_file && (guint)char_count <= undo_capture_limit) { char *current = buffer_text(tab); if (!current) return; + undo_copied = TRUE; if (tab->last_snapshot && strcmp(tab->last_snapshot, current) != 0) { push_limited(tab, tab->undo_stack, tab->last_snapshot); tab->last_snapshot = g_strdup(current); clear_stack(tab->redo_stack); + undo_changed = TRUE; } else if (!tab->last_snapshot) { tab->last_snapshot = g_strdup(current); } @@ -164,60 +224,156 @@ void on_buffer_changed(GtkTextBuffer *buffer, gpointer user_data) { g_clear_pointer(&tab->last_snapshot, g_free); if (tab->undo_stack) g_ptr_array_set_size(tab->undo_stack, 0); if (tab->redo_stack) g_ptr_array_set_size(tab->redo_stack, 0); + undo_cleared = TRUE; + } + if (debug_typing) { + undo_ms = typing_elapsed_ms(undo_start_us, g_get_monotonic_time()); } tab->modified = TRUE; + /** + * @brief Keep text input out of GTK size negotiation. + * @details Text insertion already invalidates the affected view. Queue a draw + * for dependent overlays, but do not force a size negotiation on every + * keypress. + */ + gint64 draw_start_us = debug_typing ? g_get_monotonic_time() : 0; if (tab->text_view) { - gtk_widget_queue_resize(tab->text_view); gtk_widget_queue_draw(tab->text_view); } - if (tab->scrolled) { - gtk_widget_queue_resize(tab->scrolled); - gtk_widget_queue_draw(tab->scrolled); + if (debug_typing) { + draw_ms = typing_elapsed_ms(draw_start_us, g_get_monotonic_time()); } if (large_file) { + gint64 schedule_start_us = debug_typing ? g_get_monotonic_time() : 0; if (!tab->low_latency_mode_active) { editor_tab_cancel_live_work(tab); tab->diagnostic_warnings = 0u; tab->low_latency_mode_active = TRUE; } + gint64 part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_lsp_change(tab); - editor_tab_schedule_minimap_update(tab); + if (debug_typing) lsp_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_lightweight_ui_refresh(tab); + if (debug_typing) ui_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; if (!tab->manual_syntax_override && !tab->file_path) editor_tab_auto_select_syntax(tab); + if (debug_typing) syntax_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + if (debug_typing) { + schedule_ms = typing_elapsed_ms(schedule_start_us, g_get_monotonic_time()); + g_message("Typing: changed key=%s chars=%d lines=%u delta=%d large=1 notes_saved=%d undo_copy=%d undo_changed=%d undo_cleared=%d undo_len=%u redo_len=%u notes_ms=%.3f undo_ms=%.3f draw_ms=%.3f schedule_ms=%.3f syntax_ms=%.3f lsp_ms=%.3f ui_ms=%.3f total_ms=%.3f lsp_timeout=%u ui_timeout=%u", + tab->last_typing_debug_key ? tab->last_typing_debug_key : "(unknown)", + char_count, + line_count, + line_delta, + notes_saved, + undo_copied, + undo_changed, + undo_cleared, + tab->undo_stack ? tab->undo_stack->len : 0u, + tab->redo_stack ? tab->redo_stack->len : 0u, + notes_ms, + undo_ms, + draw_ms, + schedule_ms, + syntax_schedule_ms, + lsp_schedule_ms, + ui_schedule_ms, + typing_elapsed_ms(typing_start_us, g_get_monotonic_time()), + tab->lsp_change_timeout, + tab->ui_refresh_timeout); + } return; } tab->low_latency_mode_active = FALSE; - /* Clear transient editor tags only when they are known to be active. - * Removing tags across a full GtkTextBuffer on every keystroke is a - * visible latency source on large source files. + /** + * @brief Clear transient tags only when they are active. + * @details Removing tags across a full GtkTextBuffer on every keystroke is a + * visible latency source on large source files. */ if (tab->selection_matches_active) clear_selection_matches(tab); if (tab->color_literals_active) clear_color_literals(tab); - /* - * Diagnostics may come from LSP. Do not clear them on every keypress; - * syntax diagnostics and LSP publishDiagnostics replace them on their own - * schedules. Clearing here made LSP warnings disappear before replacement. + /** + * @brief Let diagnostic producers replace their own tags. + * @details Diagnostics may come from LSP. Do not clear them on every keypress; + * syntax diagnostics and LSP publishDiagnostics replace them on their + * own schedules. Clearing here made LSP warnings disappear before + * replacement. */ -/** - * @brief Change timeout cb. - * @details Editor code runs in response to fast input, delayed timeouts, and background language work. The notes here mark the boundary between immediate GTK state and deferred refresh paths so latency fixes do not turn into stale-widget bugs. - * @param user_data The callback context passed through GTK signal data. - * @return TRUE when the condition is satisfied; otherwise FALSE. - */ + /** + * @brief Schedule full live editor work for small buffers. + * @details These paths may scan or retag the buffer, so they stay behind the + * live-feature guard and never run in reduced-work mode. + */ + gint64 schedule_start_us = debug_typing ? g_get_monotonic_time() : 0; + gint64 part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_minimap_update(tab); + if (debug_typing) minimap_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_preview_update(tab); + if (debug_typing) preview_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_selection_matches(tab); + if (debug_typing) selection_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_color_literals(tab); + if (debug_typing) color_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_syntax_diagnostics(tab); + if (debug_typing) diagnostics_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; if (!tab->manual_syntax_override && !tab->file_path) editor_tab_auto_select_syntax(tab); + if (debug_typing) syntax_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_completion(tab); + if (debug_typing) completion_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_lsp_change(tab); + if (debug_typing) lsp_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + part_start_us = debug_typing ? g_get_monotonic_time() : 0; editor_tab_schedule_lightweight_ui_refresh(tab); + if (debug_typing) ui_schedule_ms = typing_elapsed_ms(part_start_us, g_get_monotonic_time()); + if (debug_typing) { + schedule_ms = typing_elapsed_ms(schedule_start_us, g_get_monotonic_time()); + g_message("Typing: changed key=%s chars=%d lines=%u delta=%d large=0 notes_saved=%d undo_copy=%d undo_changed=%d undo_cleared=%d undo_len=%u redo_len=%u notes_ms=%.3f undo_ms=%.3f draw_ms=%.3f schedule_ms=%.3f minimap_ms=%.3f preview_ms=%.3f selection_ms=%.3f color_ms=%.3f diagnostics_ms=%.3f syntax_ms=%.3f completion_ms=%.3f lsp_ms=%.3f ui_ms=%.3f total_ms=%.3f minimap_timeout=%u preview_timeout=%u selection_timeout=%u color_timeout=%u diagnostics_timeout=%u completion_timeout=%u lsp_timeout=%u ui_timeout=%u", + tab->last_typing_debug_key ? tab->last_typing_debug_key : "(unknown)", + char_count, + line_count, + line_delta, + notes_saved, + undo_copied, + undo_changed, + undo_cleared, + tab->undo_stack ? tab->undo_stack->len : 0u, + tab->redo_stack ? tab->redo_stack->len : 0u, + notes_ms, + undo_ms, + draw_ms, + schedule_ms, + minimap_schedule_ms, + preview_schedule_ms, + selection_schedule_ms, + color_schedule_ms, + diagnostics_schedule_ms, + syntax_schedule_ms, + completion_schedule_ms, + lsp_schedule_ms, + ui_schedule_ms, + typing_elapsed_ms(typing_start_us, g_get_monotonic_time()), + tab->minimap_timeout, + tab->preview_timeout, + tab->selection_match_timeout, + tab->color_literal_timeout, + tab->diagnostics_timeout, + tab->completion_timeout, + tab->lsp_change_timeout, + tab->ui_refresh_timeout); + } } /** diff --git a/src/editor_tab.h b/src/editor_tab.h index 2788139..1230d4c 100644 --- a/src/editor_tab.h +++ b/src/editor_tab.h @@ -79,6 +79,7 @@ struct _EditorTab { char *completion_prefix; /**< Completion prefix. */ char *lsp_completion_retry_key; /**< Active LSP completion retry key. */ char *hover_word; /**< Hover word. */ + char *last_typing_debug_key; /**< Last key name shown in typing debug logs. */ SyntaxDef *active_syntax; /**< Active syntax. */ GPtrArray *undo_stack; /**< char*. */ diff --git a/src/editor_tab_build.c b/src/editor_tab_build.c index 5cab834..0d1f611 100644 --- a/src/editor_tab_build.c +++ b/src/editor_tab_build.c @@ -77,7 +77,13 @@ static void tab_create_text_area(EditorTab *tab, EditorWindow *win) { gtk_source_view_set_highlight_current_line(GTK_SOURCE_VIEW(tab->text_view), TRUE); gtk_source_view_set_auto_indent(GTK_SOURCE_VIEW(tab->text_view), FALSE); gtk_source_view_set_indent_on_tab(GTK_SOURCE_VIEW(tab->text_view), FALSE); - gtk_source_view_set_smart_backspace(GTK_SOURCE_VIEW(tab->text_view), TRUE); + /** + * @brief Keep Backspace on the plain GtkTextBuffer path. + * @details GtkSourceView smart backspace performs indentation-aware deletion. + * Graptoς already owns indentation behavior, so keeping deletion plain + * removes one hidden code path while typing latency is being measured. + */ + gtk_source_view_set_smart_backspace(GTK_SOURCE_VIEW(tab->text_view), FALSE); } /** diff --git a/src/editor_tab_gutter.c b/src/editor_tab_gutter.c index c18adff..e1dce6c 100644 --- a/src/editor_tab_gutter.c +++ b/src/editor_tab_gutter.c @@ -23,6 +23,59 @@ int decimal_digits(int value) { return digits; } +/** + * @brief Log editor scroll state. + * @details Scroll callbacks can fire many times in a short burst. The message + * stays on one line so `graptos debug` output can be filtered by the + * `Scroll:` prefix while still showing adjustment bounds and visible + * editor lines. + * @param tab The editor tab whose scroll state is being inspected. + * @param adjustment The vertical adjustment that emitted the scroll change. + * @param phase Short label for the scroll path being logged. + */ +static void debug_log_scroll_state(EditorTab *tab, + GtkAdjustment *adjustment, + const char *phase) { + if (!tab || !tab->win || !tab->win->debug_mode || !adjustment) return; + + double value = gtk_adjustment_get_value(adjustment); + double lower = gtk_adjustment_get_lower(adjustment); + double upper = gtk_adjustment_get_upper(adjustment); + double page = gtk_adjustment_get_page_size(adjustment); + double max = upper - page; + double ratio = max > lower ? (value - lower) / (max - lower) : 0.0; + if (ratio < 0.0) ratio = 0.0; + if (ratio > 1.0) ratio = 1.0; + + gint top_line = -1; + gint bottom_line = -1; + gint total_lines = tab->buffer ? gtk_text_buffer_get_line_count(tab->buffer) : 0; + if (tab->text_view && gtk_widget_get_mapped(tab->text_view)) { + GtkTextView *view = GTK_TEXT_VIEW(tab->text_view); + GdkRectangle visible; + GtkTextIter top; + GtkTextIter bottom; + gint ignored = 0; + gtk_text_view_get_visible_rect(view, &visible); + gtk_text_view_get_line_at_y(view, &top, visible.y, &ignored); + gtk_text_view_get_line_at_y(view, &bottom, visible.y + visible.height, &ignored); + top_line = gtk_text_iter_get_line(&top) + 1; + bottom_line = gtk_text_iter_get_line(&bottom) + 1; + } + + g_message("Scroll: %s value=%.2f lower=%.2f upper=%.2f page=%.2f ratio=%.3f lines=%d-%d/%d minimap=%d", + phase ? phase : "changed", + value, + lower, + upper, + page, + ratio, + top_line, + bottom_line, + total_lines, + tab->minimap_view ? gtk_widget_get_visible(tab->minimap_view) : 0); +} + /** * @brief Update gutter width. @@ -204,6 +257,13 @@ static void sync_minimap_scroll(EditorTab *tab, GtkAdjustment *main_adj) { if (ratio < 0.0) ratio = 0.0; if (ratio > 1.0) ratio = 1.0; gtk_adjustment_set_value(mini_adj, ratio * mini_max); + if (tab->win && tab->win->debug_mode) { + g_message("Scroll: minimap-sync main=%.2f ratio=%.3f mini=%.2f mini_max=%.2f", + gtk_adjustment_get_value(main_adj), + ratio, + ratio * mini_max, + mini_max); + } } /** @@ -214,6 +274,7 @@ static void sync_minimap_scroll(EditorTab *tab, GtkAdjustment *main_adj) { */ void on_vadjustment_value_changed(GtkAdjustment *adjustment, gpointer user_data) { EditorTab *tab = user_data; + debug_log_scroll_state(tab, adjustment, "vadjustment"); if (tab && tab->gutter) gtk_widget_queue_draw(tab->gutter); if (tab) { sync_minimap_scroll(tab, adjustment); @@ -699,6 +760,7 @@ void update_minimap_text(EditorTab *tab) { if (tab->scrolled) { GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(tab->scrolled)); + debug_log_scroll_state(tab, vadj, "minimap-update"); sync_minimap_scroll(tab, vadj); } tab->minimap_updating = FALSE; @@ -863,5 +925,12 @@ void on_minimap_click(GtkGestureClick *gesture, int n_press, double x, if (y < 0.0) y = 0.0; if (y > (double)height) y = (double)height; guint target = (guint)(((double)lines * y) / (double)height) + 1u; + if (tab->win && tab->win->debug_mode) { + g_message("Scroll: minimap-click y=%.2f height=%d target_line=%u total_lines=%d", + y, + height, + target, + lines); + } editor_tab_jump_to_line_internal(tab, target); } diff --git a/src/editor_tab_keys.c b/src/editor_tab_keys.c index 40a8986..8fe618d 100644 --- a/src/editor_tab_keys.c +++ b/src/editor_tab_keys.c @@ -699,7 +699,6 @@ gboolean on_text_view_key_pressed(GtkEventControllerKey *controller, GdkModifierType state, gpointer user_data) { (void)controller; - (void)keycode; EditorTab *tab = user_data; if (!tab) return FALSE; @@ -707,9 +706,31 @@ gboolean on_text_view_key_pressed(GtkEventControllerKey *controller, gboolean alt = (state & GDK_ALT_MASK) != 0; gboolean shift = (state & GDK_SHIFT_MASK) != 0; gboolean ctrl = (state & GDK_CONTROL_MASK) != 0; + gboolean debug_typing = tab->win && tab->win->debug_mode; + + /** + * @brief Log editor key entry before Graptoς handles it. + * @details Normal text keys usually return FALSE here and are inserted by + * GTK afterward. This log shows whether latency starts in our key + * handler or in the later buffer-changed path. + */ + if (debug_typing) { + const char *name = gdk_keyval_name(keyval); + g_free(tab->last_typing_debug_key); + tab->last_typing_debug_key = g_strdup(name ? name : "(unknown)"); + g_message("Typing: key key=%s keyval=%u keycode=%u ctrl=%d alt=%d shift=%d completion=%d", + name ? name : "(unknown)", + keyval, + keycode, + ctrl, + alt, + shift, + completion_is_visible(tab)); + } if (ctrl && shift && key == GDK_KEY_m) { editor_tab_show_add_note(tab); + if (debug_typing) g_message("Typing: key-handled action=add-note"); return TRUE; } @@ -720,28 +741,51 @@ gboolean on_text_view_key_pressed(GtkEventControllerKey *controller, } if (completion_is_visible(tab)) { - if (key == GDK_KEY_Escape) { editor_tab_hide_completion(tab); return TRUE; } - if (key == GDK_KEY_Down) { completion_select_delta(tab, 1); return TRUE; } - if (key == GDK_KEY_Up) { completion_select_delta(tab, -1); return TRUE; } + if (key == GDK_KEY_Escape) { + editor_tab_hide_completion(tab); + if (debug_typing) g_message("Typing: key-handled action=completion-hide"); + return TRUE; + } + if (key == GDK_KEY_Down) { + completion_select_delta(tab, 1); + if (debug_typing) g_message("Typing: key-handled action=completion-down"); + return TRUE; + } + if (key == GDK_KEY_Up) { + completion_select_delta(tab, -1); + if (debug_typing) g_message("Typing: key-handled action=completion-up"); + return TRUE; + } if (key == GDK_KEY_Tab && !shift) { completion_accept_selected(tab); + if (debug_typing) g_message("Typing: key-handled action=completion-accept"); return TRUE; } editor_tab_hide_completion(tab); } - if (handle_close_pair_key(tab, keyval, state)) return TRUE; + if (handle_close_pair_key(tab, keyval, state)) { + if (debug_typing) g_message("Typing: key-handled action=close-pair"); + return TRUE; + } if (key == GDK_KEY_Return || key == GDK_KEY_KP_Enter) { - return handle_return_key(tab); + gboolean handled = handle_return_key(tab); + if (debug_typing) { + g_message("Typing: key-handled action=return handled=%d", handled); + } + return handled; } if (keyval == GDK_KEY_ISO_Left_Tab || (key == GDK_KEY_Tab && shift)) { unindent_selection_or_line(tab); + if (debug_typing) g_message("Typing: key-handled action=unindent"); return TRUE; } if (key == GDK_KEY_Tab) { insert_tab_or_indent(tab); + if (debug_typing) g_message("Typing: key-handled action=tab-indent"); return TRUE; } + if (debug_typing) g_message("Typing: key-pass-through"); return FALSE; } diff --git a/src/editor_tab_undo.c b/src/editor_tab_undo.c index b21a85a..056cdee 100644 --- a/src/editor_tab_undo.c +++ b/src/editor_tab_undo.c @@ -50,7 +50,9 @@ char *pop_stack(GPtrArray *stack) { /** * @brief Reset undo state. - * @details Editor code runs in response to fast input, delayed timeouts, and background language work. The notes here mark the boundary between immediate GTK state and deferred refresh paths so latency fixes do not turn into stale-widget bugs. + * @details The snapshot stack is useful for small buffers, but every snapshot is + * a full text copy. Reset keeps the first snapshot only while the + * configured guard says that cost is still acceptable. * @param tab The editor tab whose buffer or widgets are being inspected. */ void reset_undo_state(EditorTab *tab) { From 6dc7fc85b977f6d1c898651f18dd49df6aa5f17d Mon Sep 17 00:00:00 2001 From: "A. Madani" Date: Sat, 1 Aug 2026 12:47:16 +0300 Subject: [PATCH 2/5] fixes and basic untested plugins --- Makefile | 13 +- data/plugins/example-plugin/plugin.yaml | 16 + data/plugins/example-plugin/syntax/todo.yaml | 80 ++ .../templates/todo-project/files/README.md | 7 + .../templates/todo-project/files/tasks.todo | 6 + .../templates/todo-project/template.yaml | 31 + src/app.h | 3 + src/app/app_file_search_actions.inc.c | 21 +- src/app/app_window_lifecycle.inc.c | 9 + src/app_actions.c | 1 + src/app_layout.c | 2 +- src/app_private.h | 1 + src/editor/editor_tab_context_menu.inc.c | 25 + src/editor_tab_diagnostics.c | 182 +++++ src/editor_tab_private.h | 10 + src/git.h | 10 + src/git/git_core.inc.c | 49 ++ src/plugin.c | 688 ++++++++++++++++++ src/plugin.h | 158 ++++ src/plugin_api.h | 53 ++ src/project_init.c | 1 + src/project_init/project_init_apply.inc.c | 20 + src/syntax_loader.c | 18 + tests/unit_tests.c | 90 +++ 24 files changed, 1485 insertions(+), 9 deletions(-) create mode 100644 data/plugins/example-plugin/plugin.yaml create mode 100644 data/plugins/example-plugin/syntax/todo.yaml create mode 100644 data/plugins/example-plugin/templates/todo-project/files/README.md create mode 100644 data/plugins/example-plugin/templates/todo-project/files/tasks.todo create mode 100644 data/plugins/example-plugin/templates/todo-project/template.yaml create mode 100644 src/plugin.c create mode 100644 src/plugin.h create mode 100644 src/plugin_api.h diff --git a/Makefile b/Makefile index 61d7ca3..038edb0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ APP_NAME := graptos APP_ID := io.github.graptos.Editor -VERSION := 0.23.69 +VERSION := 0.23.71 PREFIX ?= /usr/local CC ?= cc BUILD_DIR := build @@ -44,6 +44,7 @@ SOURCES := \ $(SRC_DIR)/project_init.c \ $(SRC_DIR)/project_init_ui.c \ $(SRC_DIR)/project_search.c \ + $(SRC_DIR)/plugin.c \ $(SRC_DIR)/git.c \ $(SRC_DIR)/codex_protocol.c \ $(SRC_DIR)/codex_client.c \ @@ -89,7 +90,7 @@ else $(error GtkSourceView 5 is required but not detected. Install gtksourceview-5 development files.) endif -GTK_PKG := gtk4 gtksourceview-5 json-glib-1.0 vte-2.91-gtk4 +GTK_PKG := gtk4 gtksourceview-5 json-glib-1.0 vte-2.91-gtk4 gmodule-2.0 GTK_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(GTK_PKG)) GTK_SYSTEM_CFLAGS := $(foreach flag,$(GTK_CFLAGS),$(if $(filter -I%,$(flag)),-isystem $(patsubst -I%,%,$(flag)),$(flag))) GTK_LIBS := $(shell $(PKG_CONFIG) --libs $(GTK_PKG)) @@ -129,8 +130,8 @@ check: $(VERSION_HEADER) test: $(BUILD_DIR)/unit_tests $< -$(BUILD_DIR)/unit_tests: $(TEST_DIR)/unit_tests.c $(SRC_DIR)/codex_protocol.c $(SRC_DIR)/syntax_diagnostics.c $(SRC_DIR)/project_init.c $(SRC_DIR)/formatter.c $(SRC_DIR)/formatter_lexer.c $(SRC_DIR)/formatter_layout.c $(SRC_DIR)/formatter_spacing.c $(SRC_DIR)/formatter_scope.c $(SRC_DIR)/syntax.c $(SRC_DIR)/editor_notes.c | $(BUILD_DIR) - $(CC) $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(GTK_SYSTEM_CFLAGS) -std=c11 $^ $(GTK_LIBS) -o $@ +$(BUILD_DIR)/unit_tests: $(TEST_DIR)/unit_tests.c $(SRC_DIR)/codex_protocol.c $(SRC_DIR)/syntax_diagnostics.c $(SRC_DIR)/project_init.c $(SRC_DIR)/formatter.c $(SRC_DIR)/formatter_lexer.c $(SRC_DIR)/formatter_layout.c $(SRC_DIR)/formatter_spacing.c $(SRC_DIR)/formatter_scope.c $(SRC_DIR)/syntax.c $(SRC_DIR)/editor_notes.c $(SRC_DIR)/plugin.c | $(BUILD_DIR) + $(CC) $(CPPFLAGS) -DGRAPTOS_PLUGIN_NO_UI $(CFLAGS) $(WARNINGS) $(GTK_SYSTEM_CFLAGS) -std=c11 $^ $(GTK_LIBS) -o $@ smoke-test: $(BUILD_DIR)/smoke_window $< @@ -160,6 +161,10 @@ install: $(BUILD_DIR)/$(APP_NAME) cp -R data/fonts/Inconsolata/. $(DESTDIR)$(DATADIR)/fonts/Inconsolata/ install -d $(DESTDIR)$(DATADIR)/project-templates cp -R data/project-templates/. $(DESTDIR)$(DATADIR)/project-templates/ + @if [ -d data/plugins ]; then \ + install -d $(DESTDIR)$(DATADIR)/plugins; \ + cp -R data/plugins/. $(DESTDIR)$(DATADIR)/plugins/; \ + fi install -Dm644 data/$(APP_ID).desktop $(DESTDIR)$(PREFIX)/share/applications/$(APP_ID).desktop uninstall: diff --git a/data/plugins/example-plugin/plugin.yaml b/data/plugins/example-plugin/plugin.yaml new file mode 100644 index 0000000..d2a7df4 --- /dev/null +++ b/data/plugins/example-plugin/plugin.yaml @@ -0,0 +1,16 @@ +id: example-plugin +name: Git Blame Example +version: 1.0 +description: Adds a Git blame action to the editor line context menu. +graptos_api_version: 1 +enabled: true + +permissions: + - editor.read + - git + +contributes: + commands: + - git-blame-line + menus: + - editor-line:Git Blame This Line:git-blame-line diff --git a/data/plugins/example-plugin/syntax/todo.yaml b/data/plugins/example-plugin/syntax/todo.yaml new file mode 100644 index 0000000..abd1151 --- /dev/null +++ b/data/plugins/example-plugin/syntax/todo.yaml @@ -0,0 +1,80 @@ +name: Todo List +extensions: [.todo] +line_comment: "#" +auto_indent: false +indent_openers: [] +indent_closers: [] +close_pairs: ["(=>)", "{=>}", "[=>]"] +icon: todo +filenames: [] +index: false +import_style: none +lsp_command: +lsp_language_id: +lsp_args: [] +import_keywords: [] +import_from_keywords: [] +import_member_keywords: [] +import_roots: [] +import_env: [] +import_extensions: [] +import_member_files: [] +import_strip_extensions: true +import_dot_modules: false +import_static_modules: [] +completions: [TODO, DONE, WAITING, BLOCKED, NOTE, TODAY] +line_close_pairs: [] +statement_required_enders: [] +statement_exempt_prefixes: ["#", "-", "*", "TODO", "DONE", "WAITING", "BLOCKED", "NOTE"] +statement_exempt_suffixes: [] +formatting: + enabled: false + scope: selection_or_block + profile: none + block_style: none + statement_style: newline + brace_style: attach + one_statement_per_line: false + space_before_block_opener: false + space_after_comma: true + space_after_control_keyword: true + space_around_binary_operators: true + space_around_logical_operators: true + logical_operators: [] + binary_operators: [] + unary_prefix_operators: [] + protected_scopes: [comment] + pointer_alignment: name + continuation_indent: 2 + max_column: 100 + wrap_after_comma: false + case_indent: 0 + preserve_blank_lines: true + max_blank_lines: 1 + collapse_empty_blocks: false +rules: + - name: comment + scope: comment + pattern: "#.*$" + color: "#6A9955" + - name: done + pattern: "^[[:space:]]*DONE\\b.*$" + color: "#57CC99" + bold: true + - name: todo + pattern: "^[[:space:]]*TODO\\b.*$" + color: "#F9C74F" + bold: true + - name: waiting + pattern: "^[[:space:]]*(WAITING|BLOCKED)\\b.*$" + color: "#FF6B6B" + bold: true + - name: note + pattern: "^[[:space:]]*NOTE\\b.*$" + color: "#89B4FA" + - name: tag + pattern: "@[A-Za-z0-9_-]+" + color: "#CBA6F7" + - name: date + pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}" + color: "#B5CEA8" diff --git a/data/plugins/example-plugin/templates/todo-project/files/README.md b/data/plugins/example-plugin/templates/todo-project/files/README.md new file mode 100644 index 0000000..2a9729c --- /dev/null +++ b/data/plugins/example-plugin/templates/todo-project/files/README.md @@ -0,0 +1,7 @@ +# ${project_name} + +Owner: ${owner_name} + +This project was created from the example Graptos plugin. + +Open `tasks.todo` to see the plugin syntax highlighting. diff --git a/data/plugins/example-plugin/templates/todo-project/files/tasks.todo b/data/plugins/example-plugin/templates/todo-project/files/tasks.todo new file mode 100644 index 0000000..253abd0 --- /dev/null +++ b/data/plugins/example-plugin/templates/todo-project/files/tasks.todo @@ -0,0 +1,6 @@ +# Example todo file + +TODO Review the plugin manifest @plugin +WAITING Try syntax reload after editing this file @test +DONE Confirm project template appears in Init Project +NOTE Dates such as 2026-07-31 are highlighted diff --git a/data/plugins/example-plugin/templates/todo-project/template.yaml b/data/plugins/example-plugin/templates/todo-project/template.yaml new file mode 100644 index 0000000..9e73bb3 --- /dev/null +++ b/data/plugins/example-plugin/templates/todo-project/template.yaml @@ -0,0 +1,31 @@ +version: 1 +id: plugin-todo-project +name: Plugin Todo Project +description: Example plugin template that creates a small todo project. +language: Todo List + +variables: + owner_name: + label: Owner name + type: text + default: ${project_name} + + initialize_git: + label: Initialize Git repository + type: boolean + default: false + +files: + - path: README.md + template: files/README.md + + - path: tasks.todo + template: files/tasks.todo + +actions: + - type: git-init + when: initialize_git + +open: + project: . + file: tasks.todo diff --git a/src/app.h b/src/app.h index b86505d..f402650 100644 --- a/src/app.h +++ b/src/app.h @@ -14,6 +14,8 @@ #include "editor_tab.h" #include "syntax.h" +typedef struct GraptosPluginRegistry GraptosPluginRegistry; + /** * @brief Graptoς display name macro. */ @@ -302,6 +304,7 @@ typedef struct _EditorWindow { struct _LspClient *lsp_client; /**< Optional language server protocol client. */ struct _CodexPanel *codex_panel; /**< Codex panel. */ struct _TerminalPanel *terminal_panel; /**< Integrated terminal panel. */ + GraptosPluginRegistry *plugins; /**< Discovered plugin registry. */ } EditorWindow; /** diff --git a/src/app/app_file_search_actions.inc.c b/src/app/app_file_search_actions.inc.c index bd83e2a..84e31c7 100644 --- a/src/app/app_file_search_actions.inc.c +++ b/src/app/app_file_search_actions.inc.c @@ -301,10 +301,23 @@ void action_reload_syntax(GtkWidget *widget, gpointer user_data) { * @param user_data The callback context passed through GTK signal data. */ void action_syntax_diagnostics(GtkWidget *widget, gpointer user_data) { - //Should add functional diagnostics. This is just for show. (void)widget; EditorWindow *win = user_data; - char *diag = syntax_diagnostics(win->syntaxes); - dialog_info(app_window_gtk(win), "Syntax Diagnostics", diag); - g_free(diag); + if (!win) return; + + EditorTab *tab = app_window_current_tab(win); + if (tab) { + g_autofree char *report = editor_tab_build_diagnostics_report(tab); + dialog_output(app_window_gtk(win), + "Diagnostics", + "Diagnostics and File Health", + report ? report : "No diagnostics report is available."); + return; + } + + g_autofree char *diag = syntax_diagnostics(win->syntaxes); + dialog_output(app_window_gtk(win), + "Diagnostics", + "Syntax Definitions", + diag ? diag : "No syntax diagnostics are available."); } diff --git a/src/app/app_window_lifecycle.inc.c b/src/app/app_window_lifecycle.inc.c index cf26d05..b03db95 100644 --- a/src/app/app_window_lifecycle.inc.c +++ b/src/app/app_window_lifecycle.inc.c @@ -220,6 +220,14 @@ EditorWindow *app_window_new(GtkApplication *application) { win->tile_tabs = g_ptr_array_new(); win->locked_paths = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); win->git_file_status = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + win->plugins = graptos_plugin_registry_new(); + if (win->plugins) { + g_autoptr(GError) plugin_error = NULL; + if (!graptos_plugin_registry_discover(win->plugins, &plugin_error) && + plugin_error) { + g_warning("Plugin discovery failed: %s", plugin_error->message); + } + } win->syntaxes = syntax_load_all(); graptos_config_load(win); win->lsp_client = lsp_client_new(win); @@ -364,6 +372,7 @@ void app_window_free(EditorWindow *win) { if (win->tile_tabs) g_ptr_array_free(win->tile_tabs, TRUE); if (win->locked_paths) g_hash_table_destroy(win->locked_paths); if (win->git_file_status) g_hash_table_destroy(win->git_file_status); + graptos_plugin_registry_free(win->plugins); g_free(win->status_error_title); g_free(win->status_error_detail); g_free(win->editor_bg_color); diff --git a/src/app_actions.c b/src/app_actions.c index e92ad31..8d766a6 100644 --- a/src/app_actions.c +++ b/src/app_actions.c @@ -7,6 +7,7 @@ */ #include "app_private.h" +#include "editor_tab_private.h" #include #include diff --git a/src/app_layout.c b/src/app_layout.c index 4e06909..bdd489a 100644 --- a/src/app_layout.c +++ b/src/app_layout.c @@ -890,7 +890,7 @@ static void show_code_tools(GtkWidget *widget, gpointer user_data) { "Toggle selected-regex tester popover", G_CALLBACK(action_tool_toggle_regex_tester), win)); - tool_panel_append(win, tool_button_new("Diagnostics", "Show loaded syntax definitions", G_CALLBACK(action_syntax_diagnostics), win)); + tool_panel_append(win, tool_button_new("Diagnostics", "Show diagnostics and file health", G_CALLBACK(action_syntax_diagnostics), win)); tool_panel_append(win, tool_button_new("Reload Syntax", "Reload YAML syntax definitions", G_CALLBACK(action_reload_syntax), win)); update_policy_buttons(win); tool_panel_show_ready(win); diff --git a/src/app_private.h b/src/app_private.h index 5ed8899..6de7d17 100644 --- a/src/app_private.h +++ b/src/app_private.h @@ -13,6 +13,7 @@ #include "dialogs.h" #include "ui.h" #include "project.h" +#include "plugin.h" #include "git.h" #include "config.h" #include "codex_client.h" diff --git a/src/editor/editor_tab_context_menu.inc.c b/src/editor/editor_tab_context_menu.inc.c index c1cac83..64de795 100644 --- a/src/editor/editor_tab_context_menu.inc.c +++ b/src/editor/editor_tab_context_menu.inc.c @@ -112,6 +112,7 @@ on_text_view_right_click(GtkGestureClick *gesture, GtkWidget *popover; GtkWidget *box; GdkRectangle rect; + guint clicked_line = 0u; (void)n_press; @@ -161,6 +162,23 @@ on_text_view_right_click(GtkGestureClick *gesture, box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); graptos_set_all_margins(box, 6); + if (GTK_IS_TEXT_VIEW(widget)) { + int buffer_x = 0; + int buffer_y = 0; + GtkTextIter iter; + gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), + GTK_TEXT_WINDOW_WIDGET, + (int)x, + (int)y, + &buffer_x, + &buffer_y); + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), + &iter, + buffer_x, + buffer_y); + clicked_line = (guint)gtk_text_iter_get_line(&iter) + 1u; + } + gtk_box_append(GTK_BOX(box), context_button(tab, "Undo", @@ -215,6 +233,13 @@ on_text_view_right_click(GtkGestureClick *gesture, context_button(tab, "Add Note", G_CALLBACK(menu_add_note))); + if (tab->win && tab->win->plugins) { + graptos_plugin_append_editor_context_items(tab->win->plugins, + tab, + box, + popover, + clicked_line); + } /* * Use the click position as a tiny target rectangle so the popover opens diff --git a/src/editor_tab_diagnostics.c b/src/editor_tab_diagnostics.c index 9d466d9..73fed58 100644 --- a/src/editor_tab_diagnostics.c +++ b/src/editor_tab_diagnostics.c @@ -7,6 +7,8 @@ */ #include "editor_tab_private.h" +#include "git.h" +#include "project.h" /** * @brief Editor tab diagnostics type definition. @@ -547,6 +549,186 @@ EditorDiagnostic *editor_tab_diagnostic_at_iter(EditorTab *tab, GtkTextIter *ite return NULL; } +/** + * @brief Append one on/off report field. + * @details Diagnostics reports are plain text by design, so compact helpers keep + * boolean editor state readable without turning the builder into a long + * chain of repeated ternaries. + * @param out Report buffer receiving text. + * @param label Field label. + * @param value Boolean value to render. + */ +static void diagnostics_report_append_bool(GString *out, + const char *label, + gboolean value) { + if (!out || !label) return; + g_string_append_printf(out, "%s: %s\n", label, value ? "yes" : "no"); +} + +/** + * @brief Append one diagnostic entry to the report. + * @details Stored diagnostics use character offsets because hover lookups need + * cheap range checks. The report converts those offsets back to + * user-facing line and column numbers at display time. + * @param out Report buffer receiving text. + * @param tab The editor tab that owns the buffer. + * @param diagnostic Diagnostic entry to format. + * @param index One-based diagnostic index. + */ +static void diagnostics_report_append_entry(GString *out, + EditorTab *tab, + EditorDiagnostic *diagnostic, + guint index) { + if (!out || !tab || !tab->buffer || !diagnostic) return; + + GtkTextIter start; + GtkTextIter end; + gtk_text_buffer_get_iter_at_offset(tab->buffer, &start, + diagnostic->start_offset); + gtk_text_buffer_get_iter_at_offset(tab->buffer, &end, + diagnostic->end_offset); + g_string_append_printf(out, + "%u. Ln %d, Col %d - Ln %d, Col %d: %s\n", + index, + gtk_text_iter_get_line(&start) + 1, + gtk_text_iter_get_line_offset(&start) + 1, + gtk_text_iter_get_line(&end) + 1, + gtk_text_iter_get_line_offset(&end) + 1, + diagnostic->message ? diagnostic->message : "Diagnostic"); +} + +/** + * @brief Build an active-tab diagnostics report. + * @details The report is intentionally cheap. It reads current editor state, + * cached diagnostics, cached Git status, and configured limits without + * refreshing any subsystem or scanning the file contents. + * @param tab The editor tab whose health report should be built. + * @return Newly allocated report text, or NULL when no tab is available. + */ +char *editor_tab_build_diagnostics_report(EditorTab *tab) { + if (!tab || !tab->buffer) return NULL; + + EditorWindow *win = tab->win; + gint lines = gtk_text_buffer_get_line_count(tab->buffer); + gint chars = gtk_text_buffer_get_char_count(tab->buffer); + const char *syntax = tab->active_syntax && tab->active_syntax->name + ? tab->active_syntax->name : "Plain Text"; + const char *lsp_command = tab->active_syntax && tab->active_syntax->lsp_command + ? tab->active_syntax->lsp_command : NULL; + const char *git_status = tab->file_path && win + ? graptos_git_status_for_file(win, tab->file_path) : NULL; + const char *project_root = tab->file_path && win + ? project_root_for_path(win, tab->file_path) : NULL; + + guint live_limit = win && win->live_feature_max_chars > 0u + ? win->live_feature_max_chars : GRAPTOS_LIVE_FEATURE_MAX_CHARS; + guint lsp_limit = win && win->lsp_sync_max_chars > 0u + ? win->lsp_sync_max_chars : GRAPTOS_LSP_SYNC_MAX_CHARS; + guint diagnostics_limit = win && win->diagnostics_max_chars > 0u + ? win->diagnostics_max_chars : GRAPTOS_DIAGNOSTICS_MAX_CHARS; + guint minimap_limit = win && win->minimap_max_bytes > 0u + ? win->minimap_max_bytes : GRAPTOS_MINIMAP_MAX_BYTES; + guint color_limit = win && win->color_literal_max_chars > 0u + ? win->color_literal_max_chars : GRAPTOS_COLOR_LITERAL_MAX_CHARS; + guint completion_limit = win && win->auto_completion_max_chars > 0u + ? win->auto_completion_max_chars : GRAPTOS_AUTO_COMPLETION_MAX_CHARS; + guint undo_limit = win && win->max_undo_capture_bytes > 0u + ? win->max_undo_capture_bytes : GRAPTOS_MAX_UNDO_CAPTURE_BYTES; + + GString *out = g_string_new("Diagnostics Health Report\n"); + g_string_append(out, "=========================\n\n"); + + g_string_append(out, "File\n"); + g_string_append(out, "----\n"); + g_autofree char *title = editor_tab_basename(tab); + g_string_append_printf(out, "Title: %s\n", title ? title : "Untitled"); + g_string_append_printf(out, "Path: %s\n", tab->file_path ? tab->file_path : "Unsaved"); + g_string_append_printf(out, "Project root: %s\n", project_root ? project_root : "None"); + g_string_append_printf(out, "Git status: %s\n", git_status ? git_status : "None cached"); + diagnostics_report_append_bool(out, "Modified", tab->modified); + diagnostics_report_append_bool(out, "Locked", tab->locked); + g_string_append_printf(out, "Lines: %d\n", lines); + g_string_append_printf(out, "Characters: %d\n", chars); + g_string_append_printf(out, "Syntax: %s\n", syntax); + g_string_append_printf(out, "Indent: %s, width %u\n\n", + tab->insert_spaces ? "spaces" : "tabs", + tab->tab_width ? tab->tab_width : 4u); + + g_string_append(out, "Diagnostics\n"); + g_string_append(out, "-----------\n"); + diagnostics_report_append_bool(out, "Enabled", win ? win->diagnostics_enabled : FALSE); + diagnostics_report_append_bool(out, "Active tags", tab->diagnostics_active); + g_string_append_printf(out, "Warnings: %u\n", tab->diagnostic_warnings); + g_string_append_printf(out, "Stored details: %u\n", + tab->diagnostics ? tab->diagnostics->len : 0u); + g_string_append_printf(out, "Source: %s\n\n", + lsp_command && lsp_command[0] != '\0' + ? "Language server" + : "Graptoς syntax checks"); + + if (tab->diagnostics && tab->diagnostics->len > 0u) { + g_string_append(out, "Warning Details\n"); + g_string_append(out, "---------------\n"); + for (guint i = 0u; i < tab->diagnostics->len; i++) { + diagnostics_report_append_entry(out, + tab, + g_ptr_array_index(tab->diagnostics, i), + i + 1u); + } + g_string_append_c(out, '\n'); + } else { + g_string_append(out, "Warning Details\n"); + g_string_append(out, "---------------\n"); + g_string_append(out, "No diagnostic details are currently stored.\n\n"); + } + + g_string_append(out, "Language Server\n"); + g_string_append(out, "---------------\n"); + g_string_append_printf(out, "Command: %s\n", + lsp_command && lsp_command[0] != '\0' + ? lsp_command : "None"); + diagnostics_report_append_bool(out, "Sync allowed", editor_tab_lsp_sync_allowed(tab)); + g_string_append_printf(out, "Document version: %u\n", tab->lsp_version); + g_string_append_printf(out, "Pending didChange timeout: %u\n\n", + tab->lsp_change_timeout); + + g_string_append(out, "Feature Guards\n"); + g_string_append(out, "--------------\n"); + diagnostics_report_append_bool(out, "Live features allowed", editor_tab_live_features_allowed(tab)); + diagnostics_report_append_bool(out, "Low latency mode", tab->low_latency_mode_active); + g_string_append_printf(out, "live_feature_max_chars: %u\n", live_limit); + g_string_append_printf(out, "diagnostics_max_chars: %u\n", diagnostics_limit); + g_string_append_printf(out, "minimap_max_bytes: %u\n", minimap_limit); + g_string_append_printf(out, "color_literal_max_chars: %u\n", color_limit); + g_string_append_printf(out, "auto_completion_max_chars: %u\n", completion_limit); + g_string_append_printf(out, "lsp_sync_max_chars: %u\n", lsp_limit); + g_string_append_printf(out, "max_undo_capture_bytes: %u\n\n", undo_limit); + + g_string_append(out, "Editor State\n"); + g_string_append(out, "------------\n"); + diagnostics_report_append_bool(out, "Autocomplete enabled", tab->autocomplete_enabled); + diagnostics_report_append_bool(out, "Selection matches active", tab->selection_matches_active); + diagnostics_report_append_bool(out, "Color literals active", tab->color_literals_active); + g_string_append_printf(out, "Notes: %u\n", tab->notes ? tab->notes->len : 0u); + g_string_append_printf(out, "Undo states: %u\n", + tab->undo_stack ? tab->undo_stack->len : 0u); + g_string_append_printf(out, "Redo states: %u\n\n", + tab->redo_stack ? tab->redo_stack->len : 0u); + + g_string_append(out, "Pending Work\n"); + g_string_append(out, "------------\n"); + g_string_append_printf(out, "minimap_timeout: %u\n", tab->minimap_timeout); + g_string_append_printf(out, "preview_timeout: %u\n", tab->preview_timeout); + g_string_append_printf(out, "selection_match_timeout: %u\n", tab->selection_match_timeout); + g_string_append_printf(out, "color_literal_timeout: %u\n", tab->color_literal_timeout); + g_string_append_printf(out, "diagnostics_timeout: %u\n", tab->diagnostics_timeout); + g_string_append_printf(out, "completion_timeout: %u\n", tab->completion_timeout); + g_string_append_printf(out, "lsp_change_timeout: %u\n", tab->lsp_change_timeout); + g_string_append_printf(out, "ui_refresh_timeout: %u\n", tab->ui_refresh_timeout); + + return g_string_free(out, FALSE); +} + /** * @brief Editor tab apply syntax diagnostics. * @details Editor code runs in response to fast input, delayed timeouts, and background language work. The notes here mark the boundary between immediate GTK state and deferred refresh paths so latency fixes do not turn into stale-widget bugs. diff --git a/src/editor_tab_private.h b/src/editor_tab_private.h index ee137eb..177cbec 100644 --- a/src/editor_tab_private.h +++ b/src/editor_tab_private.h @@ -17,6 +17,7 @@ #include "import_complete.h" #include "editor_notes.h" #include "lsp_client.h" +#include "plugin.h" #include "ui.h" #include @@ -663,6 +664,15 @@ void editor_diagnostic_free(gpointer data); * @return The resolved value for the caller, or NULL when no suitable value is available. */ EditorDiagnostic *editor_tab_diagnostic_at_iter(EditorTab *tab, GtkTextIter *iter); +/** + * @brief Build an active-tab diagnostics report. + * @details The report uses cached editor, Git, LSP, and diagnostic state. It + * must not refresh Git, start language servers, or scan the whole file + * beyond cheap GtkTextBuffer counters. + * @param tab The editor tab whose health report should be built. + * @return Newly allocated report text, or NULL when no tab is available. + */ +char *editor_tab_build_diagnostics_report(EditorTab *tab); /** * @brief Show diagnostic hover at an iterator. * @details Editor code runs in response to fast input, delayed timeouts, and background language work. The notes here mark the boundary between immediate GTK state and deferred refresh paths so latency fixes do not turn into stale-widget bugs. diff --git a/src/git.h b/src/git.h index cfa6667..2774daf 100644 --- a/src/git.h +++ b/src/git.h @@ -43,6 +43,16 @@ void graptos_git_refresh_and_rebuild(EditorWindow *win); * @return The resolved value for the caller, or NULL when no suitable value is available. */ char *graptos_git_repo_for_path(const char *path); +/** + * @brief Return Git blame information for one file line. + * @details Plugin and UI callers use this helper instead of spawning Git + * themselves so Graptoς keeps argv-based execution and bounded output + * in one place. + * @param path The tracked file path. + * @param line One-based line number. + * @return Newly allocated blame output, or an explanatory error string. + */ +char *graptos_git_blame_line(const char *path, guint line); /** * @brief Action git status. diff --git a/src/git/git_core.inc.c b/src/git/git_core.inc.c index 294d38c..df23aad 100644 --- a/src/git/git_core.inc.c +++ b/src/git/git_core.inc.c @@ -271,6 +271,55 @@ static gboolean run_git_args(const char *repo, return ok; } +char *graptos_git_blame_line(const char *path, guint line) { + if (!path || path[0] == '\0' || line == 0u) { + return g_strdup("Git blame needs a saved file and a valid line."); + } + + g_autofree char *repo = graptos_git_repo_for_path(path); + if (!repo) return g_strdup("This file is not inside a Git repository."); + + g_autofree char *canonical_repo = g_canonicalize_filename(repo, NULL); + g_autofree char *canonical_path = g_canonicalize_filename(path, NULL); + const char *relative_start = NULL; + gsize repo_len = canonical_repo ? strlen(canonical_repo) : 0u; + if (repo_len > 0u && g_strcmp0(canonical_repo, canonical_path) == 0) { + relative_start = "."; + } else if (repo_len > 0u && + g_str_has_prefix(canonical_path, canonical_repo) && + canonical_path[repo_len] == G_DIR_SEPARATOR) { + relative_start = canonical_path + repo_len + 1u; + } + if (!relative_start || relative_start[0] == '\0') { + return g_strdup("Git blame could not resolve the file inside the repository."); + } + + g_autofree char *range = g_strdup_printf("%u,%u", line, line); + GraptosGitResult result = {0}; + (void)run_git_args(repo, + NULL, + &result, + "blame", + "-L", + range, + "--", + relative_start, + NULL); + + char *text = NULL; + if (result.exit_code == 0 && result.out && result.out[0] != '\0') { + text = g_strdup(result.out); + } else if (result.err && result.err[0] != '\0') { + text = g_strdup(result.err); + } else if (result.message && result.message[0] != '\0') { + text = g_strdup(result.message); + } else { + text = g_strdup("Git blame returned no output."); + } + git_result_clear(&result); + return text; +} + /** * @brief Chomp dup. * @details Git actions are user-facing wrappers around command output. The comment keeps the boundary clear between collecting results and presenting them in Graptoς dialogs. diff --git a/src/plugin.c b/src/plugin.c new file mode 100644 index 0000000..ebb335e --- /dev/null +++ b/src/plugin.c @@ -0,0 +1,688 @@ +/** + * @file src/plugin.c + * @brief Graptoς plugin manifest and registry implementation. + * @details Plugin manifests are intentionally declarative and small. The parser + * accepts a bounded YAML-like shape matching the rest of Graptoς data + * files, then stores contribution directories for syntax, theme, + * template, and future command loading. + */ + +#include "plugin.h" + +#ifndef GRAPTOS_PLUGIN_NO_UI +#include "app.h" +#include "dialogs.h" +#include "editor_tab.h" +#include "git.h" +#include "ui.h" +#endif + +#include +#include + +#ifndef DATADIR +/** + * @brief Datadir macro. + */ +#define DATADIR "/usr/local/share/graptos" +#endif + +/** + * @brief Plugin host backing struct. + */ +struct _GraptosPluginHost { + GraptosPlugin *plugin; /**< Plugin currently registering native code. */ +}; + +GQuark graptos_plugin_error_quark(void) { + return g_quark_from_static_string("graptos-plugin-error-quark"); +} + +guint graptos_plugin_host_api_version(GraptosPluginHost *host) { + (void)host; + return GRAPTOS_PLUGIN_API_VERSION; +} + +const char *graptos_plugin_host_plugin_id(GraptosPluginHost *host) { + return host && host->plugin ? host->plugin->id : NULL; +} + +/** + * @brief Strip a line comment. + * @details Plugin manifests do not support quoted hashes yet. Matching the + * existing project-template parser keeps the v1 format easy to audit. + * @param line Mutable line text. + */ +static void strip_comment(char *line) { + if (!line) return; + char *hash = strchr(line, '#'); + if (hash) *hash = '\0'; +} + +/** + * @brief Parse one key-value line. + * @details The manifest parser only accepts simple scalar keys and explicit + * list sections so unsupported shapes fail predictably. + * @param line The stripped input line. + * @param key_out Output key. + * @param value_out Output value. + * @return TRUE when parsing succeeds. + */ +static gboolean parse_key_value(const char *line, char **key_out, char **value_out) { + const char *colon = strchr(line, ':'); + if (!colon) return FALSE; + char *key = g_strndup(line, (gsize)(colon - line)); + char *value = g_strdup(colon + 1); + if (!key || !value) { + g_free(key); + g_free(value); + return FALSE; + } + g_strstrip(key); + g_strstrip(value); + *key_out = key; + *value_out = value; + return TRUE; +} + +/** + * @brief Duplicate a scalar manifest value. + * @details Simple quote stripping is enough for Graptoς manifests because paths + * and ids are line-local scalar values. + * @param value Raw scalar text. + * @return Newly allocated unquoted value. + */ +static char *scalar_dup(const char *value) { + char *copy = g_strdup(value ? value : ""); + if (!copy) return NULL; + g_strstrip(copy); + gsize len = strlen(copy); + if (len >= 2u && + ((copy[0] == '"' && copy[len - 1u] == '"') || + (copy[0] == '\'' && copy[len - 1u] == '\''))) { + copy[len - 1u] = '\0'; + char *out = g_strdup(copy + 1); + g_free(copy); + return out; + } + return copy; +} + +/** + * @brief Validate a stable plugin identifier. + * @details Plugin ids become config keys and directory names, so keep them to + * lowercase identifiers plus dot, dash, and underscore separators. + * @param id Candidate id. + * @return TRUE when the id is valid. + */ +static gboolean valid_plugin_id(const char *id) { + if (!id || !id[0]) return FALSE; + for (const char *p = id; *p; p++) { + if (g_ascii_islower(*p) || g_ascii_isdigit(*p) || + *p == '.' || *p == '-' || *p == '_') { + continue; + } + return FALSE; + } + return TRUE; +} + +/** + * @brief Add a manifest list value. + * @details Values are kept relative until contribution lookup so plugin + * manifests remain movable between system and user directories. + * @param array Destination string array. + * @param value Raw scalar value. + */ +static void add_list_value(GPtrArray *array, const char *value) { + if (!array || !value) return; + g_autofree char *item = scalar_dup(value); + if (item && item[0] != '\0') g_ptr_array_add(array, g_strdup(item)); +} + +/** + * @brief Allocate an empty plugin manifest. + * @details Arrays are owned by the manifest and always available to callers so + * contribution loops do not need null checks. + * @return New manifest object. + */ +static GraptosPlugin *plugin_new(void) { + GraptosPlugin *plugin = g_new0(GraptosPlugin, 1); + if (!plugin) return NULL; + plugin->enabled = TRUE; + plugin->graptos_api_version = GRAPTOS_PLUGIN_API_VERSION; + plugin->permissions = g_ptr_array_new_with_free_func(g_free); + plugin->syntax_dirs = g_ptr_array_new_with_free_func(g_free); + plugin->theme_dirs = g_ptr_array_new_with_free_func(g_free); + plugin->template_dirs = g_ptr_array_new_with_free_func(g_free); + plugin->snippet_dirs = g_ptr_array_new_with_free_func(g_free); + plugin->commands = g_ptr_array_new_with_free_func(g_free); + plugin->menus = g_ptr_array_new_with_free_func(g_free); + return plugin; +} + +void graptos_plugin_free(GraptosPlugin *plugin) { + if (!plugin) return; + if (plugin->native_handle) g_module_close((GModule *)plugin->native_handle); + g_free(plugin->id); + g_free(plugin->name); + g_free(plugin->version); + g_free(plugin->description); + g_free(plugin->base_path); + g_free(plugin->native_library); + if (plugin->permissions) g_ptr_array_free(plugin->permissions, TRUE); + if (plugin->syntax_dirs) g_ptr_array_free(plugin->syntax_dirs, TRUE); + if (plugin->theme_dirs) g_ptr_array_free(plugin->theme_dirs, TRUE); + if (plugin->template_dirs) g_ptr_array_free(plugin->template_dirs, TRUE); + if (plugin->snippet_dirs) g_ptr_array_free(plugin->snippet_dirs, TRUE); + if (plugin->commands) g_ptr_array_free(plugin->commands, TRUE); + if (plugin->menus) g_ptr_array_free(plugin->menus, TRUE); + g_free(plugin); +} + +/** + * @brief Parse a plugin manifest. + * @details The supported v1 shape has top-level scalar metadata, permissions as + * a list, and contribution lists below contributes.. + * @param plugin_dir Directory containing plugin.yaml. + * @param error Return location for parse errors. + * @return Parsed plugin, or NULL. + */ +GraptosPlugin *graptos_plugin_load_manifest(const char *plugin_dir, GError **error) { + g_autofree char *manifest = g_build_filename(plugin_dir, "plugin.yaml", NULL); + g_autofree char *text = NULL; + gsize len = 0u; + if (!g_file_get_contents(manifest, &text, &len, error)) return NULL; + + GraptosPlugin *plugin = plugin_new(); + if (!plugin) return NULL; + plugin->base_path = g_canonicalize_filename(plugin_dir, NULL); + + enum { + SECTION_TOP, + SECTION_PERMISSIONS, + SECTION_CONTRIBUTES, + SECTION_SYNTAX, + SECTION_THEMES, + SECTION_TEMPLATES, + SECTION_SNIPPETS, + SECTION_COMMANDS, + SECTION_MENUS + } section = SECTION_TOP; + + char **lines = g_strsplit(text, "\n", -1); + for (guint i = 0u; lines && lines[i]; i++) { + char *line = lines[i]; + strip_comment(line); + g_autofree char *trim_copy = g_strdup(line); + char *trim = g_strstrip(trim_copy); + if (trim[0] == '\0') continue; + guint indent = 0u; + while (line[indent] == ' ') indent++; + + if (indent == 0u) { + if (g_strcmp0(trim, "permissions:") == 0) { + section = SECTION_PERMISSIONS; + continue; + } + if (g_strcmp0(trim, "contributes:") == 0) { + section = SECTION_CONTRIBUTES; + continue; + } + section = SECTION_TOP; + } else if ((section == SECTION_CONTRIBUTES || + section == SECTION_SYNTAX || + section == SECTION_THEMES || + section == SECTION_TEMPLATES || + section == SECTION_SNIPPETS || + section == SECTION_COMMANDS || + section == SECTION_MENUS) && indent == 2u) { + if (g_strcmp0(trim, "syntaxes:") == 0) section = SECTION_SYNTAX; + else if (g_strcmp0(trim, "themes:") == 0) section = SECTION_THEMES; + else if (g_strcmp0(trim, "templates:") == 0) section = SECTION_TEMPLATES; + else if (g_strcmp0(trim, "snippets:") == 0) section = SECTION_SNIPPETS; + else if (g_strcmp0(trim, "commands:") == 0) section = SECTION_COMMANDS; + else if (g_strcmp0(trim, "menus:") == 0) section = SECTION_MENUS; + else { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_INVALID_SCHEMA, + "Plugin error in %s: unsupported contribution line %u: %s", + manifest, i + 1u, trim); + goto fail; + } + continue; + } + + if (g_str_has_prefix(trim, "-")) { + char *value = g_strstrip(trim + 1); + if (section == SECTION_PERMISSIONS) add_list_value(plugin->permissions, value); + else if (section == SECTION_SYNTAX) add_list_value(plugin->syntax_dirs, value); + else if (section == SECTION_THEMES) add_list_value(plugin->theme_dirs, value); + else if (section == SECTION_TEMPLATES) add_list_value(plugin->template_dirs, value); + else if (section == SECTION_SNIPPETS) add_list_value(plugin->snippet_dirs, value); + else if (section == SECTION_COMMANDS) add_list_value(plugin->commands, value); + else if (section == SECTION_MENUS) add_list_value(plugin->menus, value); + else { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_INVALID_SCHEMA, + "Plugin error in %s: misplaced list item on line %u", + manifest, i + 1u); + goto fail; + } + continue; + } + + g_autofree char *key = NULL; + g_autofree char *value = NULL; + if (!parse_key_value(trim, &key, &value)) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_INVALID_SCHEMA, + "Plugin error in %s: malformed line %u: %s", + manifest, i + 1u, trim); + goto fail; + } + + if (section != SECTION_TOP) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_INVALID_SCHEMA, + "Plugin error in %s: unsupported line %u: %s", + manifest, i + 1u, trim); + goto fail; + } + if (g_strcmp0(key, "id") == 0) { + g_free(plugin->id); + plugin->id = scalar_dup(value); + } else if (g_strcmp0(key, "name") == 0) { + g_free(plugin->name); + plugin->name = scalar_dup(value); + } else if (g_strcmp0(key, "version") == 0) { + g_free(plugin->version); + plugin->version = scalar_dup(value); + } else if (g_strcmp0(key, "description") == 0) { + g_free(plugin->description); + plugin->description = scalar_dup(value); + } else if (g_strcmp0(key, "graptos_api_version") == 0) { + plugin->graptos_api_version = (guint)g_ascii_strtoull(value, NULL, 10); + } else if (g_strcmp0(key, "native") == 0) { + g_free(plugin->native_library); + plugin->native_library = scalar_dup(value); + } else if (g_strcmp0(key, "enabled") == 0) { + plugin->enabled = g_ascii_strcasecmp(value, "false") != 0 && + g_ascii_strcasecmp(value, "no") != 0 && + strcmp(value, "0") != 0; + } else { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_INVALID_SCHEMA, + "Plugin error in %s: unsupported key %s", manifest, key); + goto fail; + } + } + g_strfreev(lines); + lines = NULL; + + if (!plugin->id || !plugin->name || !plugin->version) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_INVALID_SCHEMA, + "Plugin error in %s: id, name, and version are required", + manifest); + goto fail; + } + if (!valid_plugin_id(plugin->id)) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_INVALID_ID, + "Plugin error in %s: invalid plugin id %s", + manifest, plugin->id); + goto fail; + } + if (plugin->graptos_api_version != GRAPTOS_PLUGIN_API_VERSION) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_UNSUPPORTED_API, + "Plugin %s requires API %u, Graptoς supports API %u", + plugin->id, + plugin->graptos_api_version, + GRAPTOS_PLUGIN_API_VERSION); + goto fail; + } + return plugin; + +fail: + if (lines) g_strfreev(lines); + graptos_plugin_free(plugin); + return NULL; +} + +GraptosPluginRegistry *graptos_plugin_registry_new(void) { + GraptosPluginRegistry *registry = g_new0(GraptosPluginRegistry, 1); + if (!registry) return NULL; + registry->plugins = g_ptr_array_new_with_free_func((GDestroyNotify)graptos_plugin_free); + return registry; +} + +void graptos_plugin_registry_free(GraptosPluginRegistry *registry) { + if (!registry) return; + if (registry->plugins) g_ptr_array_free(registry->plugins, TRUE); + g_free(registry); +} + +/** + * @brief Discover plugin manifests below one root. + * @details Directory names are not trusted as plugin ids. Only parsed manifests + * determine plugin identity. + * @param registry Plugin registry receiving valid plugins. + * @param by_id Deduplication table. + * @param root Root directory to scan. + */ +static void discover_root(GraptosPluginRegistry *registry, + GHashTable *by_id, + const char *root) { + if (!registry || !by_id || !root) return; + GDir *dir = g_dir_open(root, 0, NULL); + if (!dir) return; + const char *name = NULL; + while ((name = g_dir_read_name(dir))) { + g_autofree char *child = g_build_filename(root, name, NULL); + if (!g_file_test(child, G_FILE_TEST_IS_DIR)) continue; + g_autoptr(GError) error = NULL; + GraptosPlugin *plugin = graptos_plugin_load_manifest(child, &error); + if (!plugin) continue; + if (g_hash_table_contains(by_id, plugin->id)) { + graptos_plugin_free(plugin); + continue; + } + g_hash_table_add(by_id, g_strdup(plugin->id)); + g_ptr_array_add(registry->plugins, plugin); + } + g_dir_close(dir); +} + +gboolean graptos_plugin_registry_discover(GraptosPluginRegistry *registry, + GError **error) { + (void)error; + if (!registry) return FALSE; + if (registry->plugins) g_ptr_array_set_size(registry->plugins, 0u); + if (g_getenv("GRAPTOS_DISABLE_PLUGINS")) return TRUE; + g_autoptr(GHashTable) by_id = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + g_autofree char *system = g_build_filename(DATADIR, "plugins", NULL); + g_autofree char *user = g_build_filename(g_get_user_data_dir(), "graptos", "plugins", NULL); + discover_root(registry, by_id, user); + discover_root(registry, by_id, system); + discover_root(registry, by_id, "data/plugins"); + return TRUE; +} + +/** + * @brief Select a contribution array from a plugin. + * @param plugin Plugin manifest. + * @param kind Contribution kind. + * @return Contribution array, or NULL. + */ +static GPtrArray *plugin_contribution_array(GraptosPlugin *plugin, + GraptosPluginContributionKind kind) { + if (!plugin) return NULL; + switch (kind) { + case GRAPTOS_PLUGIN_CONTRIBUTION_SYNTAX: return plugin->syntax_dirs; + case GRAPTOS_PLUGIN_CONTRIBUTION_THEME: return plugin->theme_dirs; + case GRAPTOS_PLUGIN_CONTRIBUTION_TEMPLATE: return plugin->template_dirs; + case GRAPTOS_PLUGIN_CONTRIBUTION_SNIPPET: return plugin->snippet_dirs; + case GRAPTOS_PLUGIN_CONTRIBUTION_COMMAND: return plugin->commands; + case GRAPTOS_PLUGIN_CONTRIBUTION_MENU: return plugin->menus; + default: return NULL; + } +} + +/** + * @brief Check whether a plugin declared a permission. + * @details Native loading is intentionally gated even when callers explicitly + * invoke it, because loading a shared library gives that plugin the + * same process trust as Graptoς. + * @param plugin Plugin manifest. + * @param permission Permission id. + * @return TRUE when the permission is present. + */ +static gboolean plugin_has_permission(GraptosPlugin *plugin, + const char *permission) { + if (!plugin || !plugin->permissions || !permission) return FALSE; + for (guint i = 0u; i < plugin->permissions->len; i++) { + if (g_strcmp0(g_ptr_array_index(plugin->permissions, i), + permission) == 0) { + return TRUE; + } + } + return FALSE; +} + +#ifndef GRAPTOS_PLUGIN_NO_UI +/** + * @brief Context carried by one plugin editor menu row. + */ +typedef struct { + GraptosPlugin *plugin; /**< Plugin that owns the command. */ + EditorTab *tab; /**< Editor tab under the menu. */ + GtkWidget *popover; /**< Context popover to close before running. */ + char *command; /**< Command id from the manifest. */ + char *label; /**< Human-facing menu label. */ + guint line; /**< One-based editor line. */ +} GraptosPluginEditorMenuAction; + +/** + * @brief Free a plugin editor menu action. + * @param data Menu action data. + */ +static void plugin_editor_menu_action_free(gpointer data) { + GraptosPluginEditorMenuAction *action = data; + if (!action) return; + g_free(action->command); + g_free(action->label); + g_free(action); +} + +/** + * @brief Check whether a plugin declared a command. + * @param plugin Plugin manifest. + * @param command Command id. + * @return TRUE when the command is declared. + */ +static gboolean plugin_declares_command(GraptosPlugin *plugin, + const char *command) { + if (!plugin || !plugin->commands || !command) return FALSE; + for (guint i = 0u; i < plugin->commands->len; i++) { + if (g_strcmp0(g_ptr_array_index(plugin->commands, i), command) == 0) { + return TRUE; + } + } + return FALSE; +} + +/** + * @brief Run one declarative editor command. + * @details V1 command ids are owned by Graptoς. Plugins expose where commands + * appear, while execution stays inside reviewed core code until the + * native permission UI exists. + * @param action Menu action data. + */ +static void plugin_run_editor_command(GraptosPluginEditorMenuAction *action) { + if (!action || !action->tab || !action->command) return; + EditorTab *tab = action->tab; + if (g_strcmp0(action->command, "git-blame-line") == 0) { + if (!plugin_has_permission(action->plugin, "git")) { + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Plugin Permission", + "Git Permission Required", + "This plugin command requires the git permission."); + return; + } + g_autofree char *output = graptos_git_blame_line(tab->file_path, + action->line); + g_autofree char *heading = g_strdup_printf("Git Blame: line %u", + action->line); + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Git Blame", + heading, + output ? output : "Git blame returned no output."); + return; + } + + g_autofree char *body = g_strdup_printf("Plugin command '%s' is not implemented.", + action->command); + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Plugin Command", + "Unsupported Plugin Command", + body); +} + +/** + * @brief Handle a plugin editor context menu click. + * @param button Menu button. + * @param user_data GraptosPluginEditorMenuAction. + */ +static void plugin_editor_menu_clicked(GtkButton *button, gpointer user_data) { + (void)button; + GraptosPluginEditorMenuAction *action = user_data; + if (action && action->popover && GTK_IS_POPOVER(action->popover)) { + gtk_popover_popdown(GTK_POPOVER(action->popover)); + } + plugin_run_editor_command(action); +} +#endif /* GRAPTOS_PLUGIN_NO_UI */ + +GPtrArray *graptos_plugin_registry_contribution_dirs(GraptosPluginRegistry *registry, + GraptosPluginContributionKind kind) { + GPtrArray *dirs = g_ptr_array_new_with_free_func(g_free); + if (!dirs) return NULL; + if (!registry || !registry->plugins) return dirs; + for (guint i = 0u; i < registry->plugins->len; i++) { + GraptosPlugin *plugin = g_ptr_array_index(registry->plugins, i); + if (!plugin || !plugin->enabled || !plugin->base_path) continue; + GPtrArray *items = plugin_contribution_array(plugin, kind); + if (!items) continue; + for (guint j = 0u; j < items->len; j++) { + const char *item = g_ptr_array_index(items, j); + if (!item || item[0] == '\0') continue; + g_autofree char *path = g_path_is_absolute(item) + ? g_strdup(item) + : g_build_filename(plugin->base_path, item, NULL); + if (g_file_test(path, G_FILE_TEST_IS_DIR)) { + g_ptr_array_add(dirs, g_canonicalize_filename(path, NULL)); + } + } + } + return dirs; +} + +gboolean graptos_plugin_registry_load_native(GraptosPluginRegistry *registry, + GError **error) { + if (!registry || !registry->plugins) return TRUE; + if (!g_module_supported()) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_NATIVE, + "Native plugin loading is not supported on this platform"); + return FALSE; + } + for (guint i = 0u; i < registry->plugins->len; i++) { + GraptosPlugin *plugin = g_ptr_array_index(registry->plugins, i); + if (!plugin || !plugin->enabled || !plugin->native_library || + plugin->native_library[0] == '\0') { + continue; + } + if (!plugin_has_permission(plugin, "native")) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_NATIVE, + "Plugin %s declares native code without native permission", + plugin->id); + return FALSE; + } + g_autofree char *path = g_path_is_absolute(plugin->native_library) + ? g_strdup(plugin->native_library) + : g_build_filename(plugin->base_path, plugin->native_library, NULL); + GModule *module = g_module_open(path, G_MODULE_BIND_LOCAL); + if (!module) { + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_NATIVE, + "Failed to load plugin %s: %s", + plugin->id, + g_module_error()); + return FALSE; + } + GraptosPluginRegisterFunc register_func = NULL; + if (!g_module_symbol(module, + "graptos_plugin_register", + (gpointer *)®ister_func) || + !register_func) { + g_module_close(module); + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_NATIVE, + "Plugin %s does not export graptos_plugin_register", + plugin->id); + return FALSE; + } + GraptosPluginHost host = {0}; + host.plugin = plugin; + if (!register_func(&host)) { + g_module_close(module); + g_set_error(error, GRAPTOS_PLUGIN_ERROR, + GRAPTOS_PLUGIN_ERROR_NATIVE, + "Plugin %s registration failed", + plugin->id); + return FALSE; + } + plugin->native_handle = module; + } + return TRUE; +} + +void graptos_plugin_append_editor_context_items(GraptosPluginRegistry *registry, + EditorTab *tab, + GtkWidget *menu_box, + GtkWidget *popover, + guint line) { +#ifdef GRAPTOS_PLUGIN_NO_UI + (void)registry; + (void)tab; + (void)menu_box; + (void)popover; + (void)line; +#else + if (!registry || !registry->plugins || !tab || !menu_box || line == 0u) { + return; + } + + gboolean added_any = FALSE; + for (guint i = 0u; i < registry->plugins->len; i++) { + GraptosPlugin *plugin = g_ptr_array_index(registry->plugins, i); + if (!plugin || !plugin->enabled || !plugin->menus) continue; + for (guint j = 0u; j < plugin->menus->len; j++) { + const char *spec = g_ptr_array_index(plugin->menus, j); + if (!spec || spec[0] == '\0') continue; + g_auto(GStrv) parts = g_strsplit(spec, ":", 3); + if (!parts || g_strcmp0(parts[0], "editor-line") != 0 || + !parts[1] || !parts[2] || + !plugin_declares_command(plugin, parts[2])) { + continue; + } + if (!added_any) { + gtk_box_append(GTK_BOX(menu_box), + gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); + added_any = TRUE; + } + + GraptosPluginEditorMenuAction *action = g_new0(GraptosPluginEditorMenuAction, 1); + action->plugin = plugin; + action->tab = tab; + action->popover = popover; + action->label = g_strdup(parts[1]); + action->command = g_strdup(parts[2]); + action->line = line; + + GtkWidget *button = graptos_flat_button_new(parts[1], + NULL, + G_CALLBACK(plugin_editor_menu_clicked), + action); + g_object_set_data_full(G_OBJECT(button), + "graptos-plugin-menu-action", + action, + plugin_editor_menu_action_free); + gtk_box_append(GTK_BOX(menu_box), button); + } + } +#endif +} diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 0000000..8f8eeab --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,158 @@ +/** + * @file src/plugin.h + * @brief Graptoς plugin manifest, registry, and contribution API. + * @details The plugin layer is the stable boundary between Graptoς internals + * and extensions. Plugins contribute data through manifests and may + * later register native behavior through the opaque host ABI. + */ + +#ifndef GRAPTOS_PLUGIN_H +#define GRAPTOS_PLUGIN_H + +#include + +#include "plugin_api.h" + +typedef struct _EditorTab EditorTab; + +/** + * @brief Plugin error domain macro. + */ +#define GRAPTOS_PLUGIN_ERROR (graptos_plugin_error_quark()) + +/** + * @brief Plugin loading failure codes. + */ +typedef enum { + GRAPTOS_PLUGIN_ERROR_INVALID_SCHEMA, + GRAPTOS_PLUGIN_ERROR_INVALID_ID, + GRAPTOS_PLUGIN_ERROR_UNSUPPORTED_API, + GRAPTOS_PLUGIN_ERROR_IO, + GRAPTOS_PLUGIN_ERROR_NATIVE +} GraptosPluginError; + +/** + * @brief Plugin contribution kind. + */ +typedef enum { + GRAPTOS_PLUGIN_CONTRIBUTION_SYNTAX, + GRAPTOS_PLUGIN_CONTRIBUTION_THEME, + GRAPTOS_PLUGIN_CONTRIBUTION_TEMPLATE, + GRAPTOS_PLUGIN_CONTRIBUTION_SNIPPET, + GRAPTOS_PLUGIN_CONTRIBUTION_COMMAND, + GRAPTOS_PLUGIN_CONTRIBUTION_MENU +} GraptosPluginContributionKind; + +/** + * @brief Parsed plugin manifest. + */ +typedef struct { + char *id; /**< Stable plugin id. */ + char *name; /**< Human-facing plugin name. */ + char *version; /**< Plugin version string. */ + char *description; /**< Human-facing summary. */ + char *base_path; /**< Canonical plugin root directory. */ + char *native_library; /**< Optional native library path relative to base. */ + guint graptos_api_version; /**< Required plugin API version. */ + gboolean enabled; /**< FALSE when manifest disables the plugin. */ + GPtrArray *permissions; /**< char*. */ + GPtrArray *syntax_dirs; /**< char*. */ + GPtrArray *theme_dirs; /**< char*. */ + GPtrArray *template_dirs; /**< char*. */ + GPtrArray *snippet_dirs; /**< char*. */ + GPtrArray *commands; /**< char*. */ + GPtrArray *menus; /**< char*. */ + gpointer native_handle; /**< Private GModule handle. */ +} GraptosPlugin; + +/** + * @brief Plugin registry. + */ +typedef struct GraptosPluginRegistry { + GPtrArray *plugins; /**< GraptosPlugin*. */ +} GraptosPluginRegistry; + +/** + * @brief Return the plugin error quark. + * @details Callers use the domain to distinguish manifest, API-version, IO, + * and native loader failures from generic GLib errors. + * @return The plugin error domain. + */ +GQuark graptos_plugin_error_quark(void); +/** + * @brief Load one plugin manifest. + * @details The loader parses `plugin.yaml` from a plugin root and validates the + * stable id and API version before exposing any contribution paths. + * @param plugin_dir Directory containing `plugin.yaml`. + * @param error Return location for parser or validation errors. + * @return A parsed plugin manifest, or NULL on failure. + */ +GraptosPlugin *graptos_plugin_load_manifest(const char *plugin_dir, + GError **error); +/** + * @brief Free a plugin manifest. + * @details This also closes a native module if one was explicitly loaded. + * @param plugin Plugin manifest to free. + */ +void graptos_plugin_free(GraptosPlugin *plugin); +/** + * @brief Create an empty plugin registry. + * @details The registry owns loaded plugin manifests and is the handoff point + * between discovery and Graptoς subsystems that consume contributions. + * @return New registry, or NULL on allocation failure. + */ +GraptosPluginRegistry *graptos_plugin_registry_new(void); +/** + * @brief Free a plugin registry. + * @details All plugin manifests owned by the registry are released. + * @param registry Registry to free. + */ +void graptos_plugin_registry_free(GraptosPluginRegistry *registry); +/** + * @brief Discover installed plugins. + * @details Discovery scans user, system, and development plugin roots. User + * plugins win on duplicate ids because they are scanned first. + * @param registry Registry receiving discovered plugins. + * @param error Reserved for fatal discovery errors. + * @return TRUE when discovery completed. + */ +gboolean graptos_plugin_registry_discover(GraptosPluginRegistry *registry, + GError **error); +/** + * @brief Return contribution directories of one kind. + * @details Relative contribution paths are resolved against each plugin root + * and only existing directories are returned. + * @param registry Registry to inspect. + * @param kind Contribution kind. + * @return Owned string array of canonical directories. + */ +GPtrArray *graptos_plugin_registry_contribution_dirs(GraptosPluginRegistry *registry, + GraptosPluginContributionKind kind); +/** + * @brief Load native plugin libraries. + * @details Native code is not loaded by normal discovery. Callers must invoke + * this explicitly after permission policy has approved native loading. + * @param registry Registry containing parsed plugin manifests. + * @param error Return location for native loader errors. + * @return TRUE when all requested native plugins registered successfully. + */ +gboolean graptos_plugin_registry_load_native(GraptosPluginRegistry *registry, + GError **error); +/** + * @brief Append plugin editor context menu items. + * @details Plugin menus are declarative rows that map a label to a registered + * command id. The editor passes the current line so commands can act + * on the right-click location without direct access to private state. + * @param registry Plugin registry to inspect. + * @param tab Active editor tab. + * @param menu_box Context menu box receiving button widgets. + * @param popover Popover to close before running the command. + * @param line One-based line number under the pointer. + */ +void graptos_plugin_append_editor_context_items(GraptosPluginRegistry *registry, + EditorTab *tab, + GtkWidget *menu_box, + GtkWidget *popover, + guint line); + +#endif /* GRAPTOS_PLUGIN_H */ diff --git a/src/plugin_api.h b/src/plugin_api.h new file mode 100644 index 0000000..5594a54 --- /dev/null +++ b/src/plugin_api.h @@ -0,0 +1,53 @@ +/** + * @file src/plugin_api.h + * @brief Public ABI surface for trusted native Graptoς plugins. + * @details Native plugins must use this small host object instead of reaching + * into EditorWindow, EditorTab, or GTK internals. Keeping the ABI + * opaque lets Graptoς change editor implementation details without + * breaking plugin source. + */ + +#ifndef GRAPTOS_PLUGIN_API_H +#define GRAPTOS_PLUGIN_API_H + +#include + +/** + * @brief Graptoς plugin API version. + */ +#define GRAPTOS_PLUGIN_API_VERSION 1u + +/** + * @brief Opaque host passed to native plugins. + */ +typedef struct _GraptosPluginHost GraptosPluginHost; + +/** + * @brief Native plugin registration function. + * @details Shared libraries export this symbol when they need code-backed + * editor automation. Declarative-only plugins do not need a native + * entrypoint. + * @param host The host capability object supplied by Graptoς. + * @return TRUE when registration succeeds. + */ +typedef gboolean (*GraptosPluginRegisterFunc)(GraptosPluginHost *host); + +/** + * @brief Return the plugin API version implemented by the host. + * @details Plugins can use this during registration to reject incompatible + * Graptoς builds before registering commands. + * @param host The host capability object supplied by Graptoς. + * @return The integer API version. + */ +guint graptos_plugin_host_api_version(GraptosPluginHost *host); + +/** + * @brief Return the id of the plugin currently being registered. + * @details The id is owned by Graptoς and remains valid only while the host is + * alive. Plugins should copy it if they need to keep it. + * @param host The host capability object supplied by Graptoς. + * @return The plugin id, or NULL when unavailable. + */ +const char *graptos_plugin_host_plugin_id(GraptosPluginHost *host); + +#endif /* GRAPTOS_PLUGIN_API_H */ diff --git a/src/project_init.c b/src/project_init.c index a9d27ac..dde376d 100644 --- a/src/project_init.c +++ b/src/project_init.c @@ -8,6 +8,7 @@ */ #include "project_init.h" +#include "plugin.h" #include #include diff --git a/src/project_init/project_init_apply.inc.c b/src/project_init/project_init_apply.inc.c index db08f10..75b42c0 100644 --- a/src/project_init/project_init_apply.inc.c +++ b/src/project_init/project_init_apply.inc.c @@ -199,6 +199,26 @@ GPtrArray *graptos_project_init_discover_templates(GError **error) { g_autofree char *user = g_build_filename(g_get_user_config_dir(), "graptos", "project-templates", NULL); discover_from_root(templates, by_id, builtin, GRAPTOS_PROJECT_TEMPLATE_BUILTIN); discover_from_root(templates, by_id, "data/project-templates", GRAPTOS_PROJECT_TEMPLATE_BUILTIN); + { + GraptosPluginRegistry *plugins = graptos_plugin_registry_new(); + if (plugins) { + if (graptos_plugin_registry_discover(plugins, NULL)) { + GPtrArray *plugin_dirs = + graptos_plugin_registry_contribution_dirs(plugins, + GRAPTOS_PLUGIN_CONTRIBUTION_TEMPLATE); + if (plugin_dirs) { + for (guint i = 0u; i < plugin_dirs->len; i++) { + discover_from_root(templates, + by_id, + g_ptr_array_index(plugin_dirs, i), + GRAPTOS_PROJECT_TEMPLATE_PLUGIN); + } + g_ptr_array_free(plugin_dirs, TRUE); + } + } + graptos_plugin_registry_free(plugins); + } + } discover_from_root(templates, by_id, user, GRAPTOS_PROJECT_TEMPLATE_USER); return templates; } diff --git a/src/syntax_loader.c b/src/syntax_loader.c index 2c1c4fd..ef56f03 100644 --- a/src/syntax_loader.c +++ b/src/syntax_loader.c @@ -7,6 +7,7 @@ */ #include "syntax_private.h" +#include "plugin.h" #include #include @@ -954,6 +955,23 @@ GPtrArray *syntax_load_all_for_roots(GPtrArray *project_roots) { } load_syntax_dir(syntaxes, local); load_syntax_dir(syntaxes, parent_syntax); + { + GraptosPluginRegistry *plugins = graptos_plugin_registry_new(); + if (plugins) { + if (graptos_plugin_registry_discover(plugins, NULL)) { + GPtrArray *plugin_dirs = + graptos_plugin_registry_contribution_dirs(plugins, + GRAPTOS_PLUGIN_CONTRIBUTION_SYNTAX); + if (plugin_dirs) { + for (guint i = 0u; i < plugin_dirs->len; i++) { + load_syntax_dir(syntaxes, g_ptr_array_index(plugin_dirs, i)); + } + g_ptr_array_free(plugin_dirs, TRUE); + } + } + graptos_plugin_registry_free(plugins); + } + } load_syntax_dir(syntaxes, system); g_free(cwd); diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 48fc63b..a578e9d 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -13,6 +13,7 @@ #include "../src/codex_protocol.h" #include "../src/editor_notes.h" #include "../src/formatter.h" +#include "../src/plugin.h" #include "../src/project_init.h" #include "../src/syntax.h" @@ -812,6 +813,92 @@ static void test_editor_notes_path_collision(void) { g_assert_cmpstr(first, !=, second); } +/** + * @brief Create a temporary plugin fixture. + * @details Plugin parser tests use a real directory because contribution paths + * are resolved relative to the manifest location. + * @param manifest The plugin manifest text to write. + * @return The temporary plugin directory. + */ +static char *test_plugin_dir(const char *manifest) { + g_autoptr(GError) error = NULL; + char *dir = g_dir_make_tmp("graptos-plugin-XXXXXX", &error); + g_assert_no_error(error); + g_assert_nonnull(dir); + g_autofree char *syntax = g_build_filename(dir, "syntax", NULL); + g_autofree char *templates = g_build_filename(dir, "templates", NULL); + g_assert_cmpint(g_mkdir_with_parents(syntax, 0755), ==, 0); + g_assert_cmpint(g_mkdir_with_parents(templates, 0755), ==, 0); + g_autofree char *manifest_path = g_build_filename(dir, "plugin.yaml", NULL); + g_assert_true(g_file_set_contents(manifest_path, manifest, -1, &error)); + g_assert_no_error(error); + return dir; +} + +/** + * @brief Verifies plugin manifests expose contribution paths. + * @details The manifest remains declarative; Graptoς resolves directories only + * after loading so plugin folders can move between user and system + * install locations. + */ +static void test_plugin_manifest_contributions(void) { + const char *manifest = + "id: example.plugin\n" + "name: Example Plugin\n" + "version: 1.0\n" + "description: Test plugin\n" + "graptos_api_version: 1\n" + "permissions:\n" + " - editor.read\n" + "contributes:\n" + " syntaxes:\n" + " - syntax\n" + " templates:\n" + " - templates\n"; + g_autofree char *dir = test_plugin_dir(manifest); + g_autoptr(GError) error = NULL; + GraptosPlugin *plugin = graptos_plugin_load_manifest(dir, &error); + g_assert_no_error(error); + g_assert_nonnull(plugin); + g_assert_cmpstr(plugin->id, ==, "example.plugin"); + g_assert_cmpuint(plugin->permissions->len, ==, 1u); + g_assert_cmpuint(plugin->syntax_dirs->len, ==, 1u); + g_assert_cmpuint(plugin->template_dirs->len, ==, 1u); + + GraptosPluginRegistry *registry = graptos_plugin_registry_new(); + g_ptr_array_add(registry->plugins, plugin); + GPtrArray *syntax_dirs = + graptos_plugin_registry_contribution_dirs(registry, + GRAPTOS_PLUGIN_CONTRIBUTION_SYNTAX); + g_assert_cmpuint(syntax_dirs->len, ==, 1u); + g_assert_true(g_str_has_suffix(g_ptr_array_index(syntax_dirs, 0u), "syntax")); + + g_ptr_array_free(syntax_dirs, TRUE); + graptos_plugin_registry_free(registry); + test_remove_tree(dir); +} + +/** + * @brief Verifies invalid plugin ids are rejected. + * @details Plugin ids become directory, config, and permission keys, so the + * parser rejects names that are not stable lowercase identifiers. + */ +static void test_plugin_manifest_rejects_invalid_id(void) { + const char *manifest = + "id: Bad Plugin\n" + "name: Bad Plugin\n" + "version: 1.0\n" + "graptos_api_version: 1\n"; + g_autofree char *dir = test_plugin_dir(manifest); + g_autoptr(GError) error = NULL; + GraptosPlugin *plugin = graptos_plugin_load_manifest(dir, &error); + g_assert_null(plugin); + g_assert_nonnull(error); + g_assert_cmpint(error->domain, ==, GRAPTOS_PLUGIN_ERROR); + g_assert_cmpint(error->code, ==, GRAPTOS_PLUGIN_ERROR_INVALID_ID); + test_remove_tree(dir); +} + /** * @brief Registers and runs the unit test suite. * @@ -821,6 +908,7 @@ static void test_editor_notes_path_collision(void) { */ int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); + g_setenv("GRAPTOS_DISABLE_PLUGINS", "1", TRUE); g_test_add_func("/codex/protocol/request_round_trip", test_codex_protocol_request_round_trip); g_test_add_func("/codex/protocol/rejects_invalid_json", test_codex_protocol_rejects_invalid_json); g_test_add_func("/syntax/diagnostics/empty", test_syntax_diagnostics_empty); @@ -848,5 +936,7 @@ int main(int argc, char **argv) { g_test_add_func("/project-init/templates/languages", test_project_init_language_templates); g_test_add_func("/editor-notes/round-trip", test_editor_notes_round_trip); g_test_add_func("/editor-notes/path-collision", test_editor_notes_path_collision); + g_test_add_func("/plugin/manifest/contributions", test_plugin_manifest_contributions); + g_test_add_func("/plugin/manifest/rejects-invalid-id", test_plugin_manifest_rejects_invalid_id); return g_test_run(); } From 33bc9d00e97967e723fe70540753b7c17a07f21b Mon Sep 17 00:00:00 2001 From: "A. Madani" Date: Sat, 1 Aug 2026 19:54:31 +0300 Subject: [PATCH 3/5] plugin system tests --- Makefile | 2 +- data/plugins/full-demo/plugin.yaml | 40 ++++++ .../full-demo/syntax/full-demo-log.yaml | 70 ++++++++++ .../full-demo-project/files/demo.fulldemo | 4 + .../templates/full-demo-project/template.yaml | 19 +++ data/plugins/full-demo/themes/full-demo.css | 6 + data/plugins/permission-command/plugin.yaml | 13 ++ .../permission-editor-read/plugin.yaml | 15 +++ .../permission-editor-write/plugin.yaml | 15 +++ data/plugins/permission-git/plugin.yaml | 16 +++ data/plugins/permission-native/plugin.yaml | 14 ++ .../permission-project-read/plugin.yaml | 15 +++ .../permission-project-write/plugin.yaml | 13 ++ data/plugins/permission-syntax/plugin.yaml | 13 ++ .../plugins/permission-syntax/syntax/qml.yaml | 73 +++++++++++ data/plugins/permission-template/plugin.yaml | 13 ++ .../templates/plugin-note/files/README.md | 3 + .../templates/plugin-note/template.yaml | 19 +++ data/plugins/permission-terminal/plugin.yaml | 13 ++ data/plugins/permission-theme/plugin.yaml | 13 ++ .../permission-theme/themes/oled-dark.css | 9 ++ data/plugins/permission-ui/plugin.yaml | 15 +++ src/plugin.c | 124 ++++++++++++++++++ 23 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 data/plugins/full-demo/plugin.yaml create mode 100644 data/plugins/full-demo/syntax/full-demo-log.yaml create mode 100644 data/plugins/full-demo/templates/full-demo-project/files/demo.fulldemo create mode 100644 data/plugins/full-demo/templates/full-demo-project/template.yaml create mode 100644 data/plugins/full-demo/themes/full-demo.css create mode 100644 data/plugins/permission-command/plugin.yaml create mode 100644 data/plugins/permission-editor-read/plugin.yaml create mode 100644 data/plugins/permission-editor-write/plugin.yaml create mode 100644 data/plugins/permission-git/plugin.yaml create mode 100644 data/plugins/permission-native/plugin.yaml create mode 100644 data/plugins/permission-project-read/plugin.yaml create mode 100644 data/plugins/permission-project-write/plugin.yaml create mode 100644 data/plugins/permission-syntax/plugin.yaml create mode 100644 data/plugins/permission-syntax/syntax/qml.yaml create mode 100644 data/plugins/permission-template/plugin.yaml create mode 100644 data/plugins/permission-template/templates/plugin-note/files/README.md create mode 100644 data/plugins/permission-template/templates/plugin-note/template.yaml create mode 100644 data/plugins/permission-terminal/plugin.yaml create mode 100644 data/plugins/permission-theme/plugin.yaml create mode 100644 data/plugins/permission-theme/themes/oled-dark.css create mode 100644 data/plugins/permission-ui/plugin.yaml diff --git a/Makefile b/Makefile index 038edb0..8a18381 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ APP_NAME := graptos APP_ID := io.github.graptos.Editor -VERSION := 0.23.71 +VERSION := 0.23.69 PREFIX ?= /usr/local CC ?= cc BUILD_DIR := build diff --git a/data/plugins/full-demo/plugin.yaml b/data/plugins/full-demo/plugin.yaml new file mode 100644 index 0000000..a4cdeba --- /dev/null +++ b/data/plugins/full-demo/plugin.yaml @@ -0,0 +1,40 @@ +id: full-demo +name: Full Demo Plugin +version: 1.0 +description: Shows every current permission in one manifest. +graptos_api_version: 1 +enabled: true +native: lib/libgraptos_full_demo.so + +permissions: + - editor.read + - editor.write + - project.read + - project.write + - ui + - syntax + - theme + - template + - git + - terminal + - command + - native + +contributes: + syntaxes: + - syntax + themes: + - themes + templates: + - templates + commands: + - show-plugin-info + - line-word-count + - insert-section-banner + - project-summary + - git-blame-line + - run-current-file-in-terminal + - run-external-formatter + - native-demo-command + menus: + - editor-line:Full Demo Info:show-plugin-info diff --git a/data/plugins/full-demo/syntax/full-demo-log.yaml b/data/plugins/full-demo/syntax/full-demo-log.yaml new file mode 100644 index 0000000..82047ff --- /dev/null +++ b/data/plugins/full-demo/syntax/full-demo-log.yaml @@ -0,0 +1,70 @@ +name: Full Demo Log +extensions: [.fulldemo] +line_comment: "#" +auto_indent: false +indent_openers: [] +indent_closers: [] +close_pairs: ["(=>)", "{=>}", "[=>]"] +icon: demo +filenames: [] +index: false +import_style: none +lsp_command: +lsp_language_id: +lsp_args: [] +import_keywords: [] +import_from_keywords: [] +import_member_keywords: [] +import_roots: [] +import_env: [] +import_extensions: [] +import_member_files: [] +import_strip_extensions: true +import_dot_modules: false +import_static_modules: [] +completions: [INFO, WARN, ERROR, TODO] +line_close_pairs: [] +statement_required_enders: [] +statement_exempt_prefixes: ["#", "INFO", "WARN", "ERROR", "TODO"] +statement_exempt_suffixes: [] +formatting: + enabled: false + scope: selection_or_block + profile: none + block_style: none + statement_style: newline + brace_style: attach + one_statement_per_line: false + space_before_block_opener: false + space_after_comma: true + space_after_control_keyword: true + space_around_binary_operators: true + space_around_logical_operators: true + logical_operators: [] + binary_operators: [] + unary_prefix_operators: [] + protected_scopes: [comment] + pointer_alignment: name + continuation_indent: 2 + max_column: 100 + wrap_after_comma: false + case_indent: 0 + preserve_blank_lines: true + max_blank_lines: 1 + collapse_empty_blocks: false +rules: + - name: comment + scope: comment + pattern: "#.*$" + color: "#6A9955" + - name: error + pattern: "^ERROR\\b.*$" + color: "#FF6B6B" + bold: true + - name: warn + pattern: "^WARN\\b.*$" + color: "#F9C74F" + bold: true + - name: info + pattern: "^INFO\\b.*$" + color: "#57CC99" diff --git a/data/plugins/full-demo/templates/full-demo-project/files/demo.fulldemo b/data/plugins/full-demo/templates/full-demo-project/files/demo.fulldemo new file mode 100644 index 0000000..7feba54 --- /dev/null +++ b/data/plugins/full-demo/templates/full-demo-project/files/demo.fulldemo @@ -0,0 +1,4 @@ +# Full demo plugin project +INFO Owner: ${owner} +WARN This is a showcase template +ERROR This line is only here to demonstrate highlighting diff --git a/data/plugins/full-demo/templates/full-demo-project/template.yaml b/data/plugins/full-demo/templates/full-demo-project/template.yaml new file mode 100644 index 0000000..70a3684 --- /dev/null +++ b/data/plugins/full-demo/templates/full-demo-project/template.yaml @@ -0,0 +1,19 @@ +version: 1 +id: full-demo-project +name: Full Demo Project +description: Project template contributed by the full demo plugin. +language: Full Demo Log + +variables: + owner: + label: Owner + type: text + default: ${project_name} + +files: + - path: demo.fulldemo + template: files/demo.fulldemo + +open: + project: . + file: demo.fulldemo diff --git a/data/plugins/full-demo/themes/full-demo.css b/data/plugins/full-demo/themes/full-demo.css new file mode 100644 index 0000000..100952f --- /dev/null +++ b/data/plugins/full-demo/themes/full-demo.css @@ -0,0 +1,6 @@ +/* Full demo theme contribution. */ +@define-color graptos_editor_bg #101014; +@define-color graptos_editor_fg #f1f5f9; +@define-color graptos_tab_active_border #f9c74f; +@define-color graptos_dialog_bg #15151c; +@define-color graptos_dialog_fg #f1f5f9; diff --git a/data/plugins/permission-command/plugin.yaml b/data/plugins/permission-command/plugin.yaml new file mode 100644 index 0000000..efb616b --- /dev/null +++ b/data/plugins/permission-command/plugin.yaml @@ -0,0 +1,13 @@ +id: permission-command +name: External Command Demo +version: 1.0 +description: Demonstrates the command permission shape for future external process commands. +graptos_api_version: 1 +enabled: true + +permissions: + - command + +contributes: + commands: + - run-external-formatter diff --git a/data/plugins/permission-editor-read/plugin.yaml b/data/plugins/permission-editor-read/plugin.yaml new file mode 100644 index 0000000..ab4af74 --- /dev/null +++ b/data/plugins/permission-editor-read/plugin.yaml @@ -0,0 +1,15 @@ +id: permission-editor-read +name: Editor Read Demo +version: 1.0 +description: Adds a line word-count action that only reads editor text. +graptos_api_version: 1 +enabled: true + +permissions: + - editor.read + +contributes: + commands: + - line-word-count + menus: + - editor-line:Count Words On This Line:line-word-count diff --git a/data/plugins/permission-editor-write/plugin.yaml b/data/plugins/permission-editor-write/plugin.yaml new file mode 100644 index 0000000..46e8151 --- /dev/null +++ b/data/plugins/permission-editor-write/plugin.yaml @@ -0,0 +1,15 @@ +id: permission-editor-write +name: Editor Write Demo +version: 1.0 +description: Adds an action that inserts a section banner above the clicked line. +graptos_api_version: 1 +enabled: true + +permissions: + - editor.write + +contributes: + commands: + - insert-section-banner + menus: + - editor-line:Insert Section Banner:insert-section-banner diff --git a/data/plugins/permission-git/plugin.yaml b/data/plugins/permission-git/plugin.yaml new file mode 100644 index 0000000..896bdbd --- /dev/null +++ b/data/plugins/permission-git/plugin.yaml @@ -0,0 +1,16 @@ +id: permission-git +name: Git Blame Demo +version: 1.0 +description: Adds Git blame to the right-click editor line menu. +graptos_api_version: 1 +enabled: true + +permissions: + - editor.read + - git + +contributes: + commands: + - git-blame-line + menus: + - editor-line:Git Blame This Line:git-blame-line diff --git a/data/plugins/permission-native/plugin.yaml b/data/plugins/permission-native/plugin.yaml new file mode 100644 index 0000000..f7ff8ec --- /dev/null +++ b/data/plugins/permission-native/plugin.yaml @@ -0,0 +1,14 @@ +id: permission-native +name: Native ABI Demo +version: 1.0 +description: Demonstrates the native permission and ABI manifest shape. +graptos_api_version: 1 +enabled: true +native: lib/libgraptos_native_demo.so + +permissions: + - native + +contributes: + commands: + - native-demo-command diff --git a/data/plugins/permission-project-read/plugin.yaml b/data/plugins/permission-project-read/plugin.yaml new file mode 100644 index 0000000..28e3225 --- /dev/null +++ b/data/plugins/permission-project-read/plugin.yaml @@ -0,0 +1,15 @@ +id: permission-project-read +name: Project Read Demo +version: 1.0 +description: Shows a small project summary without writing files. +graptos_api_version: 1 +enabled: true + +permissions: + - project.read + +contributes: + commands: + - project-summary + menus: + - editor-line:Show Project Summary:project-summary diff --git a/data/plugins/permission-project-write/plugin.yaml b/data/plugins/permission-project-write/plugin.yaml new file mode 100644 index 0000000..dda48c4 --- /dev/null +++ b/data/plugins/permission-project-write/plugin.yaml @@ -0,0 +1,13 @@ +id: permission-project-write +name: Project Write Demo +version: 1.0 +description: Demonstrates the project.write permission shape for future scaffolding commands. +graptos_api_version: 1 +enabled: true + +permissions: + - project.write + +contributes: + commands: + - create-project-note diff --git a/data/plugins/permission-syntax/plugin.yaml b/data/plugins/permission-syntax/plugin.yaml new file mode 100644 index 0000000..2fe44aa --- /dev/null +++ b/data/plugins/permission-syntax/plugin.yaml @@ -0,0 +1,13 @@ +id: permission-syntax +name: QML Syntax Demo +version: 1.0 +description: Contributes a small QML syntax definition. +graptos_api_version: 1 +enabled: true + +permissions: + - syntax + +contributes: + syntaxes: + - syntax diff --git a/data/plugins/permission-syntax/syntax/qml.yaml b/data/plugins/permission-syntax/syntax/qml.yaml new file mode 100644 index 0000000..46e018f --- /dev/null +++ b/data/plugins/permission-syntax/syntax/qml.yaml @@ -0,0 +1,73 @@ +name: QML Demo +extensions: [.qml] +line_comment: "//" +auto_indent: true +indent_openers: ["{"] +indent_closers: ["}"] +close_pairs: ["(=>)", "{=>}", "[=>]", "\"=>\""] +icon: qml +filenames: [] +index: true +import_style: none +lsp_command: +lsp_language_id: qml +lsp_args: [] +import_keywords: [import] +import_from_keywords: [] +import_member_keywords: [] +import_roots: [.] +import_env: [] +import_extensions: [.qml, .js] +import_member_files: [] +import_strip_extensions: true +import_dot_modules: false +import_static_modules: [] +completions: [import, Item, Rectangle, Text, Image, MouseArea, property, signal, function, anchors, parent, width, height, color] +line_close_pairs: [] +statement_required_enders: [] +statement_exempt_prefixes: ["//", "import", "property", "signal"] +statement_exempt_suffixes: ["{", "}", ";"] +formatting: + enabled: false + scope: selection_or_block + profile: brace_semicolon + block_style: braces + statement_style: semicolon + brace_style: attach + one_statement_per_line: false + space_before_block_opener: true + space_after_comma: true + space_after_control_keyword: true + space_around_binary_operators: true + space_around_logical_operators: true + logical_operators: ["&&", "||"] + binary_operators: ["=", ":", "+", "-", "*", "/"] + unary_prefix_operators: ["!"] + protected_scopes: [comment, string] + pointer_alignment: name + continuation_indent: 4 + max_column: 100 + wrap_after_comma: false + case_indent: 0 + preserve_blank_lines: true + max_blank_lines: 1 + collapse_empty_blocks: false +rules: + - name: comment + scope: comment + pattern: "//.*$" + color: "#6A9955" + - name: string + scope: string + pattern: "\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'" + color: "#CE9178" + - name: keyword + pattern: "(?buffer || line == 0u) return g_strdup(""); + GtkTextIter start; + GtkTextIter end; + gtk_text_buffer_get_iter_at_line(tab->buffer, &start, (gint)(line - 1u)); + end = start; + if (!gtk_text_iter_ends_line(&end)) gtk_text_iter_forward_to_line_end(&end); + return gtk_text_buffer_get_text(tab->buffer, &start, &end, FALSE); +} + +/** + * @brief Count whitespace-delimited words. + * @param text Text to inspect. + * @return Word count. + */ +static guint plugin_count_words(const char *text) { + guint words = 0u; + gboolean in_word = FALSE; + for (const char *p = text ? text : ""; *p; p = g_utf8_next_char(p)) { + gunichar ch = g_utf8_get_char(p); + if (g_unichar_isspace(ch)) { + in_word = FALSE; + } else if (!in_word) { + words++; + in_word = TRUE; + } + } + return words; +} + +/** + * @brief Insert a section banner above one line. + * @param tab Editor tab. + * @param line One-based line number. + */ +static void plugin_insert_section_banner(EditorTab *tab, guint line) { + if (!tab || !tab->buffer || line == 0u) return; + const char *comment = tab->active_syntax && tab->active_syntax->line_comment + ? tab->active_syntax->line_comment : "#"; + g_autofree char *banner = g_strdup_printf("%s ---- Section ----\n", comment); + GtkTextIter iter; + gtk_text_buffer_get_iter_at_line(tab->buffer, &iter, (gint)(line - 1u)); + gtk_text_buffer_insert(tab->buffer, &iter, banner, -1); +} + /** * @brief Run one declarative editor command. * @details V1 command ids are owned by Graptoς. Plugins expose where commands @@ -521,6 +574,77 @@ static void plugin_run_editor_command(GraptosPluginEditorMenuAction *action) { output ? output : "Git blame returned no output."); return; } + if (g_strcmp0(action->command, "line-word-count") == 0) { + if (!plugin_has_permission(action->plugin, "editor.read")) { + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Plugin Permission", + "Editor Read Permission Required", + "This plugin command requires the editor.read permission."); + return; + } + g_autofree char *text = plugin_editor_line_text(tab, action->line); + guint words = plugin_count_words(text); + glong chars = g_utf8_strlen(text ? text : "", -1); + g_autofree char *body = g_strdup_printf("Line: %u\nWords: %u\nCharacters: %ld\n\n%s", + action->line, + words, + chars, + text ? text : ""); + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Line Word Count", + "Line Word Count", + body); + return; + } + if (g_strcmp0(action->command, "insert-section-banner") == 0) { + if (!plugin_has_permission(action->plugin, "editor.write")) { + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Plugin Permission", + "Editor Write Permission Required", + "This plugin command requires the editor.write permission."); + return; + } + plugin_insert_section_banner(tab, action->line); + return; + } + if (g_strcmp0(action->command, "project-summary") == 0) { + if (!plugin_has_permission(action->plugin, "project.read")) { + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Plugin Permission", + "Project Read Permission Required", + "This plugin command requires the project.read permission."); + return; + } + guint roots = tab->win && tab->win->project_roots ? tab->win->project_roots->len : 0u; + guint tabs = tab->win ? app_window_tab_count(tab->win) : 0u; + g_autofree char *body = g_strdup_printf("Project roots: %u\nOpen tabs: %u\nCurrent file: %s", + roots, + tabs, + tab->file_path ? tab->file_path : "Unsaved"); + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Project Summary", + "Project Summary", + body); + return; + } + if (g_strcmp0(action->command, "show-plugin-info") == 0) { + if (!plugin_has_permission(action->plugin, "ui")) { + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Plugin Permission", + "UI Permission Required", + "This plugin command requires the ui permission."); + return; + } + g_autofree char *body = g_strdup_printf("%s\n\n%s\nVersion: %s", + action->plugin && action->plugin->name ? action->plugin->name : "Plugin", + action->plugin && action->plugin->description ? action->plugin->description : "No description.", + action->plugin && action->plugin->version ? action->plugin->version : "unknown"); + dialog_output(tab->win ? app_window_gtk(tab->win) : NULL, + "Plugin Info", + "Plugin Info", + body); + return; + } g_autofree char *body = g_strdup_printf("Plugin command '%s' is not implemented.", action->command); From bd9c32ee325b74a20d1cf4ca12fa74515b980816 Mon Sep 17 00:00:00 2001 From: "A. Madani" Date: Sat, 1 Aug 2026 23:31:32 +0300 Subject: [PATCH 4/5] plugin system workable --- Makefile | 2 +- README.md | 25 +- data/logos/cleaf-logo-dark.png | Bin 39570 -> 0 bytes data/logos/cleaf-logo-light.png | Bin 43774 -> 0 bytes data/logos/graptos-logo-dark.png | Bin 39570 -> 51901 bytes data/logos/graptos-logo-light.png | Bin 43774 -> 51901 bytes data/plugins/example-plugin/plugin.yaml | 16 - data/plugins/example-plugin/syntax/todo.yaml | 80 --- .../templates/todo-project/files/README.md | 7 - .../templates/todo-project/files/tasks.todo | 6 - .../templates/todo-project/template.yaml | 31 -- data/plugins/full-demo/plugin.yaml | 2 +- data/plugins/permission-command/plugin.yaml | 2 +- .../permission-editor-read/plugin.yaml | 2 +- .../permission-editor-write/plugin.yaml | 2 +- data/plugins/permission-git/plugin.yaml | 2 +- data/plugins/permission-native/native_demo.c | 44 ++ data/plugins/permission-native/plugin.yaml | 6 +- .../permission-project-read/plugin.yaml | 2 +- .../permission-project-write/plugin.yaml | 2 +- data/plugins/permission-syntax/plugin.yaml | 2 +- data/plugins/permission-template/plugin.yaml | 2 +- data/plugins/permission-terminal/plugin.yaml | 2 +- data/plugins/permission-ui/plugin.yaml | 2 +- data/plugins/run-active-file/plugin.yaml | 13 + .../plugins/run-active-file/run_active_file.c | 167 ++++++ docs/PLUGINS.md | 426 +++++++++++++++ src/app/app_window_lifecycle.inc.c | 5 + src/app_layout.c | 40 ++ src/plugin.c | 510 ++++++++++++++++-- src/plugin.h | 15 + src/plugin_api.h | 181 +++++++ 32 files changed, 1401 insertions(+), 195 deletions(-) delete mode 100644 data/logos/cleaf-logo-dark.png delete mode 100644 data/logos/cleaf-logo-light.png delete mode 100644 data/plugins/example-plugin/plugin.yaml delete mode 100644 data/plugins/example-plugin/syntax/todo.yaml delete mode 100644 data/plugins/example-plugin/templates/todo-project/files/README.md delete mode 100644 data/plugins/example-plugin/templates/todo-project/files/tasks.todo delete mode 100644 data/plugins/example-plugin/templates/todo-project/template.yaml create mode 100644 data/plugins/permission-native/native_demo.c create mode 100644 data/plugins/run-active-file/plugin.yaml create mode 100644 data/plugins/run-active-file/run_active_file.c create mode 100644 docs/PLUGINS.md diff --git a/Makefile b/Makefile index 8a18381..99b1a38 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ APP_NAME := graptos APP_ID := io.github.graptos.Editor -VERSION := 0.23.69 +VERSION := 0.23.73 PREFIX ?= /usr/local CC ?= cc BUILD_DIR := build diff --git a/README.md b/README.md index 0eff2cf..5f42006 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,18 @@ -![cleaf](https://i.snipboard.io/1oBVMY.jpg) +
+ +![graptos](data/logos/graptos-logo-dark.png) + +### γραπτός: Grap-Tos; adj, written or inscribed -# Graptoς _Previously Cleaf_ +[![C CI](https://github.com/Logic-gate/Graptos/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/Logic-gate/Graptos/actions/workflows/c-cpp.yml) +![C](https://img.shields.io/badge/language-C-blue) +![Open Source Friendly](https://img.shields.io/badge/open--source-friendly-22c55e) +![Pull Requests Welcome](https://img.shields.io/badge/pull%20requests-welcome-2563eb) + +
+ +![cleaf](https://i.snipboard.io/1oBVMY.jpg) -γραπτός: Grap-Tos; adj, written or inscribed Graptoς is a small text editor for Linux, written in pure C with GTK 4 and GtkSourceView 5. It is still under active development and is not ready for @@ -477,6 +487,15 @@ icon: LOG index: false ``` +## Plugins + +Graptoς plugins are local folders with a `plugin.yaml` manifest. The plugin +layer is the boundary between Graptoς internals and extensions: plugins declare +what they contribute, and Graptoς decides how those contributions are loaded and +executed. + +See [docs/PLUGINS.md](docs/PLUGINS.md) for more information. + ### AI Policy AI contributions are based on [AI USAGE POLICY VERSION 1](https://github.com/Logic-gate/AI-USAGE-POLICY/blob/main/POLICY/VERSION_1.0.0.md) [docs/AI_USAGE_POLICY.md](docs/AI_USAGE_POLICY.md). diff --git a/data/logos/cleaf-logo-dark.png b/data/logos/cleaf-logo-dark.png deleted file mode 100644 index db41a065af5530cd9a17d30572bda204c9c13699..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39570 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 Tua`dyS>`EigX0B9?XLYlq7p8p literal 39570 zcmeFZcT`hZ_b?oXQN{){I%63Wa4ZZW5Jf-|0=AJ3Dk2~)NRy!o34w$F_L;GuB27%N z0MSqt6)6Ff87mS^q*y{^6vL%OArJ`d+b1Z$=Xuw=*7vUO`|n+5Ef(jT-S^ph-*fLd z_x`!v(Q59jpJ(B4xVbjgTX*7cs?X5hcQat*{D!~d;NQ&vH=95ko7K4QpfdwEZ<-2j z8nn>g&(o$W|L=yj#^<&-w154K^9{7;fAc^1!NIZjOAaNk^<1e={Sk-J@|h zwZGBdv{XGc4IFNIUhpnAikpKy!7n1rz}G)wZ-7B;SR|stnOnw2`uc?gP_*|31O*p!jOXhJ}Vl z6Jkj^O1=bWqhUiGK#KAYB<$R}?K2AWNILr{lt_Z1A&o{epcxrNLS>($Sfv{h9S93MDFd z?-cj&Xais62o;eSfYFA&{?UeO4e&@UZEf>Oe8MLGXw;n);vBGF*_rHx1}h~+`bI?u z?23yFAnEK3h>oB}`2{FBAfnGqwnPQ^QUd%f*5TKg=;KZG@f*wy{}&LF@jnDxeJQ>c zj8*%o5fq|-gx_R?F!^umCTpj)J9p4sw?$75teN~rjSh(VFGQD3Tld;U>W79!{x{;@ zDBp1Z|H9miFexTc)}|Z&izOu>EHV@{|8L|_hy`t6q3yaam=d7v5F8#H<{KIuP0`+D zqir7%6fud;oU&1A&3_yCTP(%K-@?qq&p6O*gP(rjUQ<8)_3O;m>-!q7_toFKcfFs9 z-v)nQ-vF~IE|Vnx&1TzP;9|Vac%6x{+1mAMP0frq;HL@n22akqvh#V`rzj?4ViNH`;_m-E7XAIE;xLjL6{?JJe?P;3(15Uja0-gT zwV$KV-;Y2Eri2FkPYiAfrTljcLUIv8eZzxDIPPl7Hxg)wf`GaK2w_; z{s-g#ABIH!N4}zrQkD&rO8@{Xj6PcdJ;Ej`WnV;;#ip=;DD6$5VZQ#p;lbtz?+XMH zS?TTnPacIzGv(6%QY6+l@;{Oq&E5Rx$Z4vkK?Jo&^)XXPG5Qxbd->};+*}w zmv2ncvV1?}-QT|chs{^ry8b9#Vw##2^$J@-C5Zb6D?DE=YJVaBz!aHlzV1)F<1Ou~ zu*e>H16Pu-zpozn>VdBw`09bL9{B2kuO9g7fv+C;>Vf~;JdpHhmy4dT71PB&Qnu1J z5mxb5EzNec<>VR(RfP|PLBe!lv+y_U7}j^wq|hnR-V|;_c7<`^Q}}V*Qvlv+`)Rv1&GvZ>^K(nk(UOa&>a8@>rFYPVn#whg)92P0JMt6R=FILzXA2 z{y70-wy{(nTjuKbGj*rqlBSm|^GZoLZ(yBW)Rt^1&hXw@r}dYbg_g8wEN&^{kde!! zlV2Gv&0wdWa)@x!y4GeHJ!3+!c$5+J?jT!t8jfr}*TS^eEIC~y9&hRlX zd{gM)Q`mOpU!SzWuGYnf3tsU1kU?J}b4P;X@Z}{q+^Uyp6cWIvm8KfN;^O@Gvs z=Lh`o3E>V4Ika3!&QBtu@t>{t zefixpt4VDQR~mJMjqT!g9Ip0WrI}ZTqm7djkt$t@a8`M`6k79brS@XStXBKCf-x1(l9{8t!fy+j)*F? zpxH8G@h3fjS#XGyq=dXm7S6Ab0svR*^)S+Mm8d0tBXp92*QT`kXm%lySC(&K@+mgb zr_jdbHE9VhiR6ejYg?pfzF`_dE9qLwrUW+yv)>mwxFILgoGTSl z6ax64*4?8yNKCYlSCcAfJN7g(tx)sNbI*AzmZqBw^d>@=tRXtdbIelDcAd;hSTV%Gl zeuNW}bqrCLl!V*zd#mlBS!?~!+WTxP`?jo?3f+s(&4Q2h5}|V8Za%^%#42q1H>yHs zTU~^3TqH-^Om2~j|F+6G`LqtIJ#LZyA)OaIeSNIj3wpIxPjJBK8k60Hkw`pFlj&^9s=5H>rg-kv)lgv`DSN_V=4eJ$&5Q9bZh&`{EsFWtTKdBY1 z^m=1{6J{vsQ%vxy+>r(}S@JzcdxS!Vrz*%l8FLbCRH!a< zD0}5SXnJnjZ0jz<9)DPiGQ<9ML=LM$stwJ^^mU$cr^Ikg*f7J@Qii-eQUetCXVLvOh5A*%e{h zhh>O+ByBuROqa=NeVMgX`+aCK{ErtIE%=Unk0LpamG8N;9xlu zCN62_=>z3fd&HH|NN`~L*_+XF78+lh<{M}Is>M7KjeoeOxM?NlDaX%kii@8s{z)0v zx-$}g+>kTU3x250c=(6so;unggl~UYA2Sx2qPg7WdH>yCKUdF3dXfdKlr)Oi51NZM zEk04n5wBNwg-+5DndoNKhymDG``x4M@w+OD;!2QjDR)*Ec2}dR$A7c#d#&v7P)#m$ zaI6_l?XP(wjzF;S%A7hC!Tk>hP_*G^&2W-1?BzVrE@{T%!>SyQtYhdJQu}MUF6r7x zIFj(qUyF1R^|B1Fl@1ihNAYvj-%pyVbh0c zN(pMc)>biM*kFXK_u_aRTCpQElhzsyEx(svDJ4Ls{?Z&?X+`rYXx1)bM9Z0~Nr@#i zo!*M30A4NsC~wnuTYp?{v-WBI`gNuDm2-p_PUY^-OLt$8ni*gl7qwY9gF~Q4{c%&U z{-n(9(GP|Ad&_pNe-X7I@A991wrWY*DcvJ)jtA)MXfbCoA zdnzlD%6~MC`}$hZClJlIq*|tzMC;xm6uPw!$E%|^($%3%*BZ#)#uPTZzNexD%)8K9 z9!S?hfYgoyJr%484b3IUi)rs!tgamSW@yGA(R^{Ld(`Y1NA zWG6BubuZ0O+K`_&jbCq5U?k|&ULGLF#1>AZ8UX<3uGO)G-MSD+*6dXGZ|)rpiNzyc zZ)>W`qI+bmP`JoD=-(4_`inkpL{+S|nI0rLEgMIGALuyZ)&9Yc+Dd>6!$WdWrU?l~ z-badE+7f3Mi&L`dWVM>%c}@^}i*^=j@ttO?Cm_-G^7{?O0tWae=~BVmJ?*i|OOh3b zGH547>3g)qgiLjutB#gDxbE_r-cToLCNu*JRrAAGu18eO5n!-YPf!VAk>uWWw^J@m zQ7*nUKF|as%E8V9ozJm95$B}2@)B7q(r!2XbcGN7EX)Ot?A+b`hR90<-7=17UhI8S zzTW&Ha&k$WLGPPoykh`x3npz4RYWJmCMhZUScW+j=CK4jl)PF~S_f?llm-9FvEFUkYTuuZ)Rd7xL9r6rM}I{)+rDn7Myq|7+DQ6{?PnGGlTsQjk)O(iE;;NAvACA zQr}!A^_-_U2_P+`R_pO5{~I7iEi73Dn(M`fEG(5M~)V9SzOiZEkv#F)XC7n z6;#V(iZWY+!1B<)G-G2ZtxGhk5FjJjlrrcby#-AZPj#YBj*bBu4wN_v82ZSkjfWcM z3sTUc4p%yGRh9sY8uAudQnXlh7z!J%wxFkbbUU37?Gjgdi5WL~M{F%9h9i#~bi6U= zNs*GvNzaxDhOJ|j)rq`Bt9!}0RtBijou=u3o9Z}I9o1Ug35hW#g2iNQIJW6SnIHxH zjKkFt40sd3BO^yOqmmMn$N3eF9;LcVI%Jw)GdZ0eX?7#pF*ZplG(Nj$$(hz;z}xeh zrrV&jO3!?Qa<-R5NV~12{{U_EHg!EP6M_eaTOdgmGS-5bdL5hEeKDJvZe}5tl8=`f z7PFeEj=dLN3?JV!aVTpcb-bkiO@k59scZD;AMjhVE`;}XkJFtdjiP}+47mgB{e`r6 z7fA$>6XPh2FX=D;=FVl+Jh>l;Nbj1~P9&!1a8{7*;&1eSXm~isG%N^+M)q*a@Z#X=$(pjRf<5h`iO5a4f#3>_PU6a)x9Z#)lNlNa?+#o$ezOpusv8ht~S6 zT-*3toGW$;!{TN!_t=Xuq#mUjpb}P9xz1EKSfO5(@Q4_{}@)-jvA!9n{#)BkFS*-FO zzFE+dBa=XY;O}UfCzz3jYwulnZFa5yj|P1qx}T`+?eZRf#Tucq#lz&t>ZFzQNCm2@ zi*~6mG41lqVAILdni76#OAZ;c+#aK73c|DK@l$E%{#qt*lC zqRsr-;%9N2C0ia%n=#|Y;9T`GyP7cY=?8krhMWD;wrQ%{N(QO>)w`CQO=t#N<7IQM z7xs^_(kay$OMWNgw0b;^#)_ulk`{Me`qvnHG$XLPlqI2@vaJa~=}o`A;ZFZ-R@pY4 zr5R^Ju8J1PmyF8Rs~AOXg1J2@kV5vmonmi_p`601h=d>RR$X{q?yDGs5HZ@(Ra5+->n-{G zt%^FW4pnKpPIJRi8(q8-F9$jy{8kdAk_Sz%n&MDU}j0&eb<&wRh$G>^|-1> zRdaO~xNVB)dSf(Z0dX|An{RTgQIVMAld-gv-2TQ8jVAphJaq3?$XG~z5>?tex=#@X z#GyCY)FQE2b@cw2?t`hHG8>j*WpGI=IITI)o6X)u{;jH}zqU_90`v8j-fMQF|6W7- zW#>f&M;CI$DG^wQCwklKY145uI%tPdT&cvKE2&p2`Qkm$2G~tEZVpQ@$6mO zJ0S`Z$n+>JmC;LMI%!Xh-Dh`ZU5-YKxFhk->%;uj8`M$qfMuY{yX1eyBE%aXwoW3=2b?B}P-J!a< zkc_-4`>Xm!I@a(T8ss@*ZBX_f`u8ox(Ys@{4;0=W)cP@G!sa?@YzFx2!$k?lBc}EA z(suF_=EZJ^ak4x8yCIQm#Wx<^i8M948WI$3v(C8A2gIOb5<-h@O=bcO)LDQ{d;6hX zuf^?@Q(AEdWI;;^$z$oOxZv3PhYF)?BzYJO215^ok58;69jIO4HuChwEpZod!`KJ# z%{D=FlZc@8;8n5AVoFwz%B9WN-qTuv^&8KDk|Iyx;Xnd#UKd5`XQ;tSc$z2f1`vEheL-Yo53ij@IrcW!uh8MSq>CZ4>U zdA%Rf4FZMSOph744+~hM6`?t0Ip~hNB)#JLM7hj=nnnkS6=74O2MqJD#js|6#Cme$ z-HOl*$aWYct5dfmMoKIN&eKCri>2M!39f+IUCoyCz8KyWTXgv3$43nZFjFM@K=_C3 zO!lv2l>%nKNOGQdK43C9L6vnx&aoi-mhYS#Yjl94DRp^bO2bMl_?tn2JIiAdPvc5c zjbN<2&3*o0({HThLMbFq%Usdl*%|B=WJA`g^4$u3a6`!_dMjhQHzw3lY>q*iilwABop|4pMpdU;|~?5ZOq>T4xN%%!YBBZ0F@_kl8snnR5i zh=`&ajQ-Qm@G*W?W!DSYYf!CdvuU^7-f2#Ws<#NrX96HS>>B=P!8(2~dUbcv6=4KK z@SS<0f*L)Ner-5?H(W$}0I7sPcdTMcO55Uoih1B6M2}F0R*SBR4N1DS;cg?g)!9xp z=yra$GyjD6g_)jt<-OiJt_t)>z^7@h!h)4(=QI=l{0K|Sb`sBnf9e>m;X8f_zOg1x z3~Q~A0iugqDeUjbm-*E2lmv7ktlz>v-P|$i5Sw=7n&xRVR1z!7kaoncAuX-_R;%;P zim^aI%Shid@qxGSXwaUFyZgoB-0hG|BsI5T*%$ub?ibCM=3)N`6L$(#?`YxMzEKdwe z2Hd+2v;IFZ--Si@mkXpYuM0RDpT-E;c1|iKvqK*AhkVAu;`*;+mlnpf30XZDdL(sR z;4oyEt23?TNaE@T(ZI-LhuP)QR|zI#+aTE1R!kFf`rAH!k8^EzU>#C&PWm)3I;@8+ zTU2y!gz#DrfrMSkl8$h6aCc^bC>7;UQv0t~l-=;8PYMBp;ANc3#5?np)@+rTD@tVB z=eYiRQ}!CdtBv}ODWOQzUH>Jq;35SCN$s8Gvb9*-qN2ZP95Kx4oWiW$6rq=8e5yLN zC@TTY=(B#zS-53geCm85T5cb0oGUNOB_<55&hCAKA47L2Z!HHdD5_*7b9{V*Bpw1K z){CcucEY8W@kjWy@XQ3b^5QrXm4MkX8dg0iz2O>cPXn~8#4QlRTxgHDJH{|4G zR|9#tXq#!vv6$+N-o8s-P2~{^2f(WRkyO{)9LJ0~ck~IX8CIdIz}er4JS{CmZFT8O zQxenxsf0*R8>?s_3RR46PMkqQc*d>qbU_fOlCjUpx&{&g*D{|ZOkhgH!b8T#<)0b` zWeiZ?y(>?VK?zdtiNneO2Uf>HuOp-vvNcQGtKgajYS!;DZRBt&=?kQMB1|FVAG%nV z(R$)_&4Wrqi=Ap<5;6+IuSR6{cDPqH#|%2uK#jt+{7RCH+tXVDKZU0yfPB22$X+YE z_k;UQQ)=^cU3*_jEz1y2-NYqgYXi=LLcX<<-&l}L-}A4E;VPd;`( zjbaI3T8^P>-R_D%6Cx{8g035ME=fhCxROlzps1$*!^b%dQDgp!x!|}0%6#UwisM1o zH8YA5kw&vfDn6C{f6BBQeqp7dp^TH^7eVc}?gf(EE>vu8@8sVTzbs!tI$dgQYS(ND z=5IFTC!XtVb)tEK+wqPyxN)?xs+T_sB z5O@)f_h(QW@&?3qY2X<5zTbZJ6t?KZ71bD(vvX$TQek(~UTp!O>vW3OM4 z^OvL+!IZk$w3pp(*@JE&Q5WZCOfs}wO}bh7>jO>*;^1idxVghKqqoWZ#B748Q!@#W z3b^bnpDfK*_Y?gDqQDp#m7Dc`ghQk*XtxrgJ}$o?NK!`(;;6 z*qZyLf&rKnT=X*|nMkn3{P?Fn3Qq-B`?zDq@I|=(7_fee4GI!do+6VxWlP51$ngAM z;;%<6Yj&+pw275kEsiNjd%pFE?9aspZr865``kOI)-zQ2VxA5s_8}Jjj~Ao$EJjT zKRu%qs=y-!(|^>RXdmWCM;p#ydmzA)%DaXYx3Fc|ZDa8j@N8F`oAs|Lr!wlO{22vZ z!D_#w->>$DMewHVOFRQF35XKT{WMpw6a+C50gk+a6k@ra)NOS0RyMuLTgl{dR@;MI zV${)?aESsVATiHnRby$gXZ&XgE4|(Uljl!ARtiKLwv1e(oJ3;&pnn+u(C`m-H`|wI zj4(>tD}Jc9shA(MPlpqN9vE(GDwXfY+OjW+@Je`Y#mt1LirGPK*&E6=NpJW9=`Fp-#fvN;J|4e5g8xTCUDXs9*~n=zyzT)Lr- zPOYd$=Gf2|ByOWGt}Qub-E0cA>(B6nWCxmfPH>N5YsC4Y2oU6Umi>=YR3t@C!n7PG#uP z{eeMN6?rg6NnTyUV=~#i1HPlbRj?t4>L`aQdC|TVPXZUo9g$>QjUD~ZT6|MN zLqs;>TzjWvkSqFpIzlgaaJm+QOSYPpCEL-O=e&C$7(frp?3+tR z<(%}HbKbcM(SXL~tk=zXl_5tzw)MGq0Ro;#;%EQds*6ABni~Qf2KNk)MVmAj<{);s z7A8q3HsI|df0v-T6t-pcROV^C>uyNE=nx*bLHpsOn@#T1a+HL&rnex(8t1fq!uBYk zp8P?Yz2U+|_llGW9iEYG^97?uN3_F;ksbq~4Y%XC5;^_8xD}Sy&9$SV)m~3v#Hu2bXj9wD-pAZ0u44fzrL@orxK$@7Q;bH+u<-LQE8S5amvmBCI(`>!LT0N5_M~Ej z8Mq|T?yj1|@go$|rv+ow&q&Qi6Q8hLmC_HiXVY*Q+2Q^XS-m-#yemQ~vL>{OJscr; zndxng4-vDA{J?$sqQ5N&qTh@{f^tI8jz^0*X{2T~FP_|_$u9DpL||w1Ue3IwnmdIc zCLsjxv@Zw=>w$yE!kD5nXaq;@mvz?^!qYLegTsoA6-U9G8{uSi8``t_SJ`=!T;g2@ zUSli2EsY^7;b+6WrDuBCnYXm8oAJP+WSHA|UkT?>=0Z%Z5LH=Nh)6~rAwHtp`d)Q_ zQrmQu+kA}nLw$2`ZWpYZR$(6o{-((5LrKP7HA?t3ZnnMQ@{GdtDfktvH}hr1{)zD}tHv6(NCX2D*uBk;9L>xM-px8=ptIhyFgBBux~biq|Oa@ z125Ls3nWCMU|=?2_@@@{58@BGZ$vi~ruj4{+LL;Ll!Juf9_Dj{a?*dS7qEo40N0h9 z)%Z>zT31SWJt;iSg<2^)3&M9H;csoJrHUJrvik1$HC|;vUfX$<>y(%NQ+h9dd~ zHZ(f64IITBGhe=ZX|_UX9}*GP9`fcT(4M`6~=p|+rxg!x& zmqsJOli7johp_V?Vxm~%#h7sEy3+ps&kS5$L?m7nd3W`Tm!?WB%flTbQhQHl!X7{u zdSdV9b!%K*BRy~7qWZ`BN?)dTeUOb~y&3Mq&6KA~J&U>C+1`8cdX{IA)H|N0oEmoT zsm#k^tPw3W(v#aauK^Jy3T~$wwsJ;k-@k}qZF_|o zX*s|(7E%YDrZAEk0@}Q0^zWxDEsYXgNLYq&W}re!zQLt4=$UszFJu+Dg3cOy)$C~Z z>mO_7C>6*^<`OJ;_2k`ZHi+HqW(TZL;I+22@9m@k{X1MNQ@sQ6dS%vM;3Ofyqq3&p zrgnx#8L5?dz%S`YSDtJcR+RDbPC@B%WD#tr?`kx|a%Yt8x3BP?hI6|UZkuQ%`pHN` zYTv8}aJ;3BCKU(jE zrs)OQ#L*VvBLgatf}fZe9Yym8h?I2i-{AqK(-*h|!S`=O>cG(wE|;IPP^11HM{sKv zuJ#~x3!PlCJkSJEQ>mVh%YZu8KBKgx6bvecXA-+iuZS=7{*igoHKz-n(BLwPx%32j z`Q6YZnaIHlxLMDMxs|?w@AiLIAfKyw#ycFZQ8uYSI8)>TVDdP(b2he8we({pw&ycE0KvV} zr{GT!JaTgdJZ5#f8oq5}t!S>%Y(tx7*b&3w6I})l%FZ5g->}jvf6_duBdxCfp0g1A z0si%IA(`;rlNU5tBmCK*{sVysU;6Pl5(oY*=4xhmN8x8fh5rnX!@@J%@3hcPwiur*i;VfjWQvGd8_1u-}T@2rV zm)74qptRq&VkU2Y#q7WZ1`d<@pW-UE(qGe)lNz>DiQp^=qouBX{6xH*h-_V*%h^ z@fWy4h#6$K$NoNP-~L^VaD4~(JEi+UJ?2)VUV`ov{WK*U?jRt%@uI^PYemzIX2U{D z>HbsP3J;_{6aeIYd(%sNd!&Bhr29`?;Z_rQ-Q)#qHkwXk-om^}a$wuXDfP4Rkox^o z>fHeKJMEfvL3?}}x5AMwzPqMs#TV|0sl5l?;}&wd(Ei^dJC~jm?^yh7Lnr0r2^~6Y zz_?VJV}&D{yidCt%duQlJ!!Fk3X_MN1NRJp?aXyv9ORr>ZfAi^a41#YNnuSI$3BnP zYuQeUhca(P$L>BRnjw5-94EJuyt|cw*uj%t&ZwWNtCWfnAaIfA>=5%Y=PBVfO5ryr znYZWPxI>Sy|H4^uTq(Kfi{xseXnJYiXcMxlq>bBoSP4Eg1-3+B@5~}+fRoK#1}-b% z>@V~0p%SheNtemui z6qSm?x1DAe(aiz;{Pa{QdHXl2PWJBaR_m~TmxOM>;di2N$+OJ+)6hLK{1$nbT{JqW zLYVB~Z%1tm`dO7S*&~OxCpD~B>zM4-wmhE33SI(hQ}j!^o~l9+Zulw?IpeEKzXr|M zw17nTT4}!4QdAIO+3uderff*9PJ~2<-U8kZu*lS*Zt=ILqX_*X{oC{d`}0g_+!EMyH=ia zPP=mY>WjZ@*E#pyulc7pCcS^db5aX)Kl}0h1v#@=+#he5ojGE8r^cvx%u~8^7Ull0 zUzSg|QomuXo%Lm*6_&d(=)^almt<}py3ZD0`LcLx+WnOb_thnkcI2;70{0zX69+=! zYf%6vd@TxJi^Au-uCGPmYf%6X!0&5O04IE1DS#8ct`tx{d|fGk6aLSO0uu{N(^cN2 zC6QcREL-|x{S16Hv(R@NO!BsLxXv_N`x*rtE*vqJktMA3FVJ%A$NC0_C*28c ztHAEIVV)MLB`(`e3fnXq!eufeC4BD`yf{Y*xBmi<5a?#GsSY0#u?fobX*B+%fu+2A7aT5Ou>Ia z@Wj(o^i@iFCPspt89&0|YEuNCFy<+@edyfI>6n#zOlu{wxAs2eJ7y4JdTGCR_7r`k z68?G;zGUVlWM1@D5RPLMalO-!b9ieJx6t}C-=BB9+g)X8Go_q^Qn`vr<*YXEL%c2i zN4Ez`-ueH~Q3zE(7n8_&d%Bza>KrHsgSOY}gdJG*^q5anI5osf&X01K9rKxH&QwR|}^;o?;Kw z@*=OuiY9}qI0YI126Z7OuX2-f5P74Zaxx~y20{AIu8vYY0OM=8gcs2p#-82%Q7u;v z>do!chR@y1G&+)%tF%7D$AuZBICDrh-azTxUB{j$bAyxivzn)F3bZj!s!E77t(S>8-DH! z?KQA_eXEicj_mC+@JjY>hVV{2;?+{3mLtiVzt;KAMala~-J-eNQem=&ecODcU*8eV zjRzBcGCB#hK)F9};X<`l>^Kx$rN@KQ522pN1g^QxYlRy~s0(kkaXW9yuFMb)@i8XE zG>}`Evi{1vUy3Y+X@N?}PIy87nQxCw)o8v1r|oxvdS}0IA5g`t=H`9+h_or$ztnc( zHzYlJioC(m@^}16Mh=RZlU>{Cm6X+it4$rH=yVJ|nOm_(vG&k4ctsZ8y=}RpmMdmC z{$t6i?=hZrlD8Mp;F9@S|BAIYp&(%do7QbvX?XG$2 zImGqO_G7~IT;W>ha_SUDnYHC!d}?|Q4G#Snyl9hQ7^Qw!f*FzsZ$ajBRXJK~GakIf&Ll_ewG&Yv)=IeAfZ#P99qrH&Eljt}hHBl*`b9sY5VANMOo;<}5t^mu?@ zi(ij)`bZ_@RIg>7&3x>`K`P@~yj%u8$lgtNjtf)j&_%HxJ4vw(B$zrVKT1@>;C7w^ z*B+al*LOnJicW=Lrxm9WhPvGpMiFK;J1=_T3!Huo!HXTI;OI&^aZ6Ap)`pEBS?WIRa`|8yRMBk^8gMAz(y)zqK2IAL` zuDW}{;!Gh)z>f+iv9Hm%xFj!>3gX5t1oKu>Xkp=A2t-ipX zJmJ!)B250?zUv_S35Ny;TaQwc#*S0|3f!ZGnVp-&NgNoPXpk)j8BR#X+nw0=BliI1 zcTO&%z_nO1N5tl1L$xp1>4@UaF2djA_JrSzZi$_w%0sG&iouDKvUe)Zl|D!4ag9 z*3yhu3kAeEINXDrx^Asp;d*CcUrI-_wG@sX<4S0XCd+G*;<^a6`W`Aqf+N6w0^dPh zZ2eOi*bS$7ab&5iFi|S=QxW!ie~YWVK&T)0CpZ}KoaCMsyeEKA+uA{#a1tcXwU<5+ zGCm7376hMP2`>z%BTe2ln~n!#Rx|QwcO;K7FC?Vtz+hsYv$2p^Danzc@0{QsaHJy> z=MwzMbf`U`+miIY#Ah!xcal2;I(y<=j4VvQ#!!?iLUo7cQc!f~IrB}P47JM8Ct7gg zJ>rk1YigJctwUKd3z$B3=jo9V?A+6Jt>-5d_2|+Vhjs3^X`k+WL154c0j1{{IW%-$ z|4uAbR>n89w$v^;0eQO~j*(weD{r>rgr-I9<_8EF$`kuHt6wMTjK$XOEMm6?gB{UJ zDVC=lU#)51fw7Ps)$ZbMQ_So_!UpnUs6>d%akWamBPT2^Dts72)8AsoEY8kMSWnh< zvipKCv4(Zqyb=k!0t5?}#yIIFRv+h4{jD~Rl5rg2`iCbJYJX%J3KbDFOiSq5kjOkVbo$hLf|lp&b3AWO3V4`2Yh?W zStU8DO6h7_myQ|rmT0lrli;(}|4364=8$cy#xEbR6szHCV<@-;2H{9)POn9)Br5RBz_L zY_)cp+XW27Cm&;sWMWscbI0Q858!Q~%XJ@53WG1SkJ0IT zNK*1jW{8-3YP{XF#jx2<3K{k{MLWTVaHh1%gCj?8@$7zPI%q@Eve#0q>0x0cc#rC; z+CRxooTxO~ZoV$&1*f`_UJfwF-pkJL`!rdt(eP=M&h7D~l+{7=q1sV_MTWb$iIZw6 zY8daAzzeY#4}BUDKDyvAWHm{>c))>~%1+KzXQav5vLf)8V@Kp@3xyCgf1E3^#h6MF zGSb*%*$)0H^L4x~2&4IgC5cwU^T|t2h;5`q5K^BLfsN!Q$CEWerFh>~wq-KS*H1db%NB@P%3jG+PengO_(L6 zx~8^L5+q}RTD{a>Ck7Zf2`8-SQl!>G*~$2qZ292`>g{%Yj0e-eYPwXD;nx42&nOZd)m^r0payuo{n`%!vgYj!wZ(d%Qcpe=^p8&4fxzc}CbCRP? zBvou@B^j}J^rq3`pd6YOu$ZSW@iBgcLJunKWwj;(#s+KVY>4$@ zfvQ=StPUFkwR9=57BBkKv2XdSmJSPj#VO;cT)fqm&5hs7&usI!5Ju4BO~7R~Rqc7Q z11Hv`tAOn+gcJb7%c>Kz&s*GkSzwhX2L z)oL1{u{7=&qc?3Fty15{%EB1AY)f@<%iEK%Fg|5ySG;(v5mDD%`Aa{#<3!oC`F)?iym zRoN(pmI*Q2#rc-07UyAPy%OUrAyjMv7#gEHpuV7&m*HEGOit^SQ{(pyyZ`dYtC9VaV}l@j#jkEidi5o-w7A&SS?vUPcoO{!Z%Y ztdyydd}fIsd_0-3ZVWCUwE_Yq`$hjJN*~}A$mBp*Yck-VP$Bu)Yu*1ix1rZ;*%%WB z`v|d?t6Af1d(DkdA6KGJy=iW#!~DMPw^%pD2rzf(_{cm0S7SrmokcV_ECOadX+Xw{Yi1W8mU`dWV3ja38ik=3)lE2~hLjkw9>XAOd^a$1 zvl3^@JIl4>rT13G?NbmzoI@u*VVLcdWBAR`f^i1FT_^?*;`+Nr`l?CCN`nkMFDDOp zt{N)@bpxr|6U|sYZbnr(6h$c#8#mxJoFv;nTSfC65ZrjTav$n}OGV3XOJ7K{s~5ao zWL~o*6Bxw5J`$l()-xPBXn(}6$3jVU=ewm%PMyP%ioHpGA78N}oD#PH=6jh}Y#Bje zSVLYlkuNzclY=nN6KdUZ2cNl-Cr*thT`Lm3`ibHp#ISY`@y<9U#v z@aWL8_fd;HVIk~K7K(oWo~l0T&)p>!msQs7Ed_=omgCL!XtX z9q)WCy-(7bCB7JGJ|3(fLORW$CMAX_4ko>m`OqD0677&Dc-8V=0o)?@9)7n!P>E%5 zMD3HN|A*Fz;ixOb&W1Ii%L_T70E_d42CgALm$4{v{a=f}%FJS^ma>(eRgd zT8XJeGu%g^PjD=`q9#*!^7*Ocn726k=_KNv>UX{3=BUyk+VJPWJXNS){hAqFXyZk#wZ>n;zWy{jO2&fMV zK@v04n_njo!|hIO(?HlDjnO*4`UhXj_;QIfrv_>gIhpz_F-Gw!i77kJ4#}%A0+#xn zjP7xgdv z?>5^br)Cq7do`L0+vy3{B^SDfXZ%p+lz0f9AXsaCYiQ@Q;`^vg{C-B!nQ=8p^%xtm zjl9E5k8xvDy`4a;4Xer*R(FjV7FpHy+8xoPDV*e$mR#}@hvlp?GjS}Qw>|*rXcy)`m7$+8tf(QxpBjM{*Vlt z@w3LJpO<}maNX+XNrc$h_vf5noAI~Z<;!Gz`rSL*mfP0f`7z*Rdeg&;TaU%s{qltT zalxT0TdTJ2-}U0V%s)(DeE;b$+jGx;ne%F+P-g1nzT#2|#*J<$qIdO;6b_6wid-0p zLCx+1RX)uIc@K5Ys_w2a0b454ib{j}{(XP`)vM{wes)phfe^^njCF+*q-V7k)mO=j z6PRE~!S0HunveQZ@_!FmV9yHLU1I^2UiaLM5|d$js^0u;5mtiO)WsXrTX z)^&yIu9^fe;h!^VH%x|~zP~Kk_HAh{t!r>(a(r>hFQ4vpny46Eb-gEMnXh*D7=yQp zaUYx&_gN|FtI`(^YGt_a3lre=Tb$9a0ef}JODdk%&KYs+vwF0tu{a3gcre$aTcF4` znXr5s_R2TcBCX(B0tun7KJxFyrW#}8k|^HYrnpDf9~m?jL*9~a=UBZVnUP+4x`&e0 zJX5=U#?FJtK+nJaS=-n%I|MurVTy%I`!Uv%qt9ZeaU}=;XWcMrO<7;%~SPNTINh zhx@z}!ul@yJNG^E_wf2B=}3QfCtADKF0sShYz+#+ElMuZx7@CA^1d*-_r`4n`s7WC zUtCQ>^ZVdOm1pz2e*bpO&E%CK+(cMP4?#~>MLc)+Y<(__%Kb$Sc8;gt?KfG{@htf)W7h;5%vs?1*Q5FQuYbVD z@T9I2Tl;RmPh92XpqaMh%#v4%Ee}sN)r=cp zhR}5On7G~d3;b@xV!VajJe_WyX*bU*-mj}BsGCQNr}dRRI_5E9NlmykU|Lrkiks-Y z**T0Zi#0Yj?ynVD{@`jH-bTwVjVtkkrpnG;gYa1ubI2KZ$KA6jJ{OSjg*)APvqJR^ z91(VXO;-xTg$0XiXRfU%vRDmlwc;cm04m7L#SN@my zlFDQ-D9V`1amNe5DmI_r78Oj@QH*h<`YzoMjIbM%|3etXk7^3P&Fh$B;dMY3IG%ehM7OA@!dnsw zbKPQPrNbl%{EQXw&wOLNZmV}Yz$(z6(Y0#eUh_C8mCCqrv3KHE=@bAv@Lvu1%kjtVRJMD%Ul2xdt0Y!7HuG0HK zCTU#0x9}oM;9&va3#30hi$1S?cv$i&36j716 zb4iMZ#+IzXA}+@WHe68Q`LdboR0*)hj_J!mEz$~5d99A**i-3CIS}ziVaGdN%e6%w zLU6vL`n}J!Xd>GmeEtu>Fxo|Ps9%Q*B|8DLQv37J67l$h?TVJ%-{)T4*K8iw0a9mK z-`qBlH}r1%5{T8+%Hm!fD3Mlx1io&YqjtMR#jQK*q5C~u%DewiN>hAKG?^O|%N~3V z7=U?`gjc2H$!yS_vGlsndEzvyL`8KwXku*ty&Y#;N+-ZXf6KLBhSOtj+%blO8L+wx zf@`G}P#K%l`eTXWE4{sN{?ae$rWrPF!qb3$`ICx3328`LsSB7oob0imxts7Lpx1x5 zq9YucOGe^sS6kfoNiax-A+zOB-e^Q2a=$J*4t&Ab-R_I?9@NHtETYvGPZ zP0j;XWBlE54Q{LaK!i5%HdF?tMp+$KO%DfqX?IekehzKB*{}dKpkCxne|K7kD=`DK zilYhc+lni9@}TwHq;YkxPBcftz$ma=(Hrk%sBc&Z7}PqtmVxChI(QroN{8**gWCjl zPf=Fb={Xu?2a)iaTCjUXUl$^O@BpGmz*mH{p*?se@V0<+`Ig1bejcO%z@Q!u@{zrh z>QLPc=f9F)11OXU)g5^5jIawOhJb9!Q+brA>(M;r4H$+CDNEfQ4>2boA>UIoo3xjt zDD%F*&j-O5O2F^8sHT%DO?cMyI#plT;lfN}P_9%CtrMxcJ<@rsthXM5PO17KY}J0D zJ0LUm>E+a3=-5pH2@^=#THWvtaQx4%F$kL~u2g;wmWdj&8?{~3+K0D=Ehn%2sF@mD zaOWSerQ>X8tvTwR}&67lekd8z+%{T*)_pe7$zB=V)du_l_l;A*rWK?AJUFz4(r^Uh$l6 zu7TlGz|_UY#DmO39lBM5<4NE2>T0=P6kzgv+F6qQLKx2ht$Tl1B97Jcey;?$ro>T< zav=x0U){OPA2cd*Z>tapmXfvyG+p5h7;QA3Dhb)kcC_>LP$o3s_mMg8;O zbg4LrUjxz*RK^+)ta1ql)2Z0Nun&qw zRcKr3c@=Wt)&S&Qwd6K-zReH%vC~gPc+%+uGSBJUn@eIVMAVi7TU4ZRo$a$yE9e(a zXVb)UX#XvSGQiXY`JSJrOp>6p7Pzb!A7+^*B|$ti-XOuuxe0Xu8Bmyy)5;?Gl6t|$ zG2YA^80#*Sz>qXH-Fr>h(a^wf0x)(00=pxhJ_1OTDfTQqZ0t&q&tX1DaZvk1h#=Vy zo2-Vu5$yeFvW6{enTiEWz*$09tQ0PTPoxHr<$95Dy}IsZ3}POdkJOi@1a}oUa4HE<>~3d&xG3Tr@HaIJ=XgwjN}8 zhy*TV_ec)X$&IlFr19t8AI~$P zpnYZW)DpP!1Ei2JrZ^L6AvzcX)~TF{Y0#Aw#z26rd&w7FWHn$_-CTkIo3U}ph!#ef zmOg|QcngQp#( zsw$f4JiZMk9w2MfHDC|BbV3f!wAHf{W4X^! zs(**9yLX|59c?B!4mKns*w(R&HSbvF)`?X#dxwMtQofgUFM`Nt(A(KejH{!$+w>05 zm2vj>S;6u6gON!96&Wl1T^1cEyQ#JiQ(p!1_zC8WW4VW*f4|H-C>c!W@#mzYoVi@a=@V1~GbmP-vZy$XHMl>ddI}y^d zQ-}OJp_8N5pBA;bVqJGVaX#T@`7_vq409h=B&*wbs(6G91)O$=gbBngtQJhSfjWF~ zlUs!)ZTlD(+*>)&E9%mI;Ax0vy9cM=1o`x!z>Wuy?q*e6$#a6brJ4$fRX^~CbJWwT z&EzL~jzR%5y5Qhaz}OJDrin;9P;i|q~!sZ z`J!XQ)h?Q8xZ+9MKPZnL->?1!f? zQ1ya$h>W$2J%_F3;PNNQYnLQQ1#+D=s%zmAg#$RyM|B@-7RA@LSMg`Fz=Ituxb8rsFId{TD1Yqow8%B zUz~%?0soKBjU`lkD?c{25JVhDj)NpRI&AE;sCLV&Ew=l}!swdO8uI*JS#9Ecl&m&) zzDl(8&#PT?O%}S>(O9gjbBc3{<4Af3y0vOH4u<)s&;Qi|PFzvjE@V(}{*aqi_cbLi z=Jv;3&9>>yzrD#l&KQW~?-Awt~nSGr`72p3FS!LUF z;uej}%=ZS;djXbPs#(sRBzNAWoy{sv*OhUF2P#PK^m?kTcJy>n!_Xt4q~nj#EM?1@ zS@nyJlkms_AC0olEPkUm70idG_}@#A^TLQJMTGP}jw)(vfukrfv`(o^vm9%izKqRH z3{4^La#=qXO~30MM`-MMXGg~BR)$&-KG^kyp(MG~WU(V_X+&+uOjW#hoXb_pM~!xJ)~KsX+Y8qw$Rw-mMK~9keVwbuvRkPE*e& z*#wzZPfHRzq}>|Sag6-Qgn8?9k+on?#k-Blvp0YBg%`8+d~So>-uzz7;w$sTVXniB z;E>B~_Fq<`SKD10SeV_q%v zeb~n%8fx~KFf}DKUfM^C_VZf0&yppEMv+}slluEK3H5z`=Z7qcBYpJVJ5tndUvzAe zndFpB5s~fo<`pq1K0STpjXgJ)qFW??+`r^i%ot_ z8XfGdsC@O2;fSL7-`AMm5=v*RjMn#9F-OO&JRW^C6tif0P1p>t-H13fG(%I~Rje^q z?SWk$EeEg8+5cgg55DNg)0UZ(g#Y31bzPeK;{MTz{bRiB?0T(YWRN zKzw@PZ6IcN>=S5b23p6R>02B((YjC5QNRCFA1+}{%Cq-c_#QTYx_p%vlV;|w#FP=MI9J%g8CC zP0q;qwbqg$1b_(R-e^s2x>+55rf-4$cdZvzI_fVXF*GNE zrTK?t=D&r7{$@82@Rlh@7F^Ruo6yMY-?ds!AnWW?LWq=?x=dMr;0L$+pvwjF&i?gN zPKIifY#+AHxs=g?HKI3HPW63=zIT#W1@~fR)%%&(13H(!$I2En1n_tpQ9R)igL>7nyr&f%-% zDI@2B;~!jpodSG<4mx+^f=nwV+nXC`#IRfKk8`(P4vcsoJst{>VY4T3^fSo=No{8c zkMV`-LJn_fq1UF|%A|Eyc;8*6Gry%1HBcc3t{Id;Hn@Z30}2*9;I(^ zM&XVr8}DEGxa)6i8X2UTp)iS@;#GFJk4pWsA^OhqZOHPu$B~0d8YSF6Ydzp68a}KO zYEX-%HK~hDb+TnTHYA@9E~J?L8Lz1I9>TXbPqVEy`BijcmLPo$yy- z0GI5T6fWV3;UBT)$w!}%M+W|Vd?v!2FbGle%pV~ zp{Sv-^Y8{)OBGeVSrG2u%xgUK;78fb3f}AeQ;)xZw`sUjhX8ouN>ARU>xAV=Mz`>v z5hCy{g78|tc^h_2=-U(6ZU?WpAO(SHWGy#;>OuZpX~J0tCu;XNmjC3od0TY7R_&<< z{z>?-27G)sfWuTdG_R!VsnW!J&V9Bl5%;8CVTG>5Tfhpn)>cO|nIX#*xhr^jvK1U} znlJ7AYjVQ#B*GT7D)Y7FN~&zir%`Tu=!}^)Ig@$#ufhk^2Ck>PUQhPvykM=>_Je>D zKf6^^_+ecK->*whagt+e^G&O@xZ7xm*G}doAJd->VV`!0#iy<9tgf_^NWbs_Uw&D@wB$TrkocqNS&y1;3~t9$JR&Z*%bb*zRvT z@cYc}7&`EqgYLiM-S6z#9`6e7Q~&J2hdR%nZ$R|{6U`oQM?y+cg7)FgZ(1+|4RZIs zq=W=5a%V;gg@%R>xie!A6AcahU%$Z=G=oi|Dd3CQSI5$yhK8+$`k_rd%XW;0hC$ig z^lHFWgG*2+A1`qSXCFrw@enUx>RU8Qsv*7(P981+Cmdbe+`W~Bm+Dc%C)}Ntg)L+a zBn*5tT&}t6g!#Fcgc+JTg?TtBI18()uqcHTi@%efyKjKIkM{}coDPmYfdR_G!Yn6tr@jyn;OFl6pV__r#T~Zq z1X5oS2aAR{IQv5+#U-e0oj9SiGah=u*`Ip3L&U`8#`eX|m8bhQC0_?We;3nGUl(QJ zD=z*%fqqUd+cQvK+MPth&&46Y#aTs4LP}0dLS9TtUJ3GlJfhP0?+cm^0S+qRLN@|^ z0u-HnoOXUdcfK?)=oowYN=a+ET;92HZs#k|-^K5L2AW;abkz41^Yrrd|DS=5eh%Kw z|1(mZIx;}cPe=az&VBfQ_}k{>f0?@&5}@m>;w-D+>?kE8CFUrpAS)*0bY4>IysNXM zn7pi`v$KPYtdop_#D8YldF6kn(Q*W{$V$md$;m26%1Fv9$e#c23)>I>cf6@jpwl&~ zoK>X$Gw*-zZI4xgP*v~gzN4}2i(Qrd=ZzhG?hO13Gyl(VEh+$9Y95ppJRXMnJZwx5G< zfKT_$6JjSM|8G{bJBbqHulfH!tjG5+lcO5Nw&hdJ0XzVUk=|VlxPo2?47ldwr*gr| z#qY!gPcH{&2XA*J>bO5ofGutd{r{C=F!vZrhWOpQ(?Jr;3Jy zcaXygU?j?a8O8~!F)06K04KIVulyI#l_3A;O#ips?<@#-DB!Cg|M6(x;y*so#Tz(6 zKj863M1CjG(43&r)x2ODk~ROHmg>l(9+1lx_fo^jzrTMH_$PsX68I;9e-ij7fqxSC zCxL$w_$PsX68I;9e-ij7f&X7iAnIE^2l7ihq3b8RjP)OGWRr&HaWDRbBfN)r zxw4NpoDG8ogyGOw4s=m|w>Fn1*F_4a?Ynsj=?+#J-Igz%x31}vF#bqu;@0tlyqpUh z4JQcA*J8*hC8}+vb2A2r_%H_097UGqk@7h5r1MwveY6LQRwlBl#*ULbc=oR%QnV&s z&+{%EC>?C3o_3*8E8TVU5^Zt`_`8V-U3&Qs-{Iu>ZE-KL^uxiqQFU7MZ_ZQ>z)|fj?8^-++9_$|LI=M3(}a4g3%c&*Mh2BrHN!u{I-K$cwP5;F zsLMyKFxq#Xp;h&$1CWe=2B;B3r?s_UiI1PHOq^hC7|CP5IPwa6ks1Y~Sa(HZ++&Cq z&JPC+u~cn|$Fb?CKzOOorFNe`_-2JZo96?TxA)rDdV#QT9=NzNmCP^4T`63Ogd{Ky z9RoAa$b>GO!gt39C7J6}hx9;pSB{&%fqsI97jGrN#)2%XEU1TxqR2;k3f^(|&?1F1 zHH(CEeN=8Wax7#~-$RD)t^j*gF$-~`PB;~XKI_A!r6@#wicO@oZO^3TrGc)bWBaj- zrewyl3b@d)C{@@^xIZc{NXTE;3@%ua5Bl1dj73~Dx^E@y1IC))4FovFuLy$^;t>QpJ$HESQGH3_3EHvUf}jKC8b@SN}9UR zfq*0HRcoR*u5lT?PTaqmd~Lk9YnA`++iu|z2yP;iBuG`{!iSpvnDw4MRWAzt`uS_E(F{VMbJMMnwgg& zFdP8SXkwdpvbiRE&uvB`3fu`1Ra#UW*`L|f3hM_pG(~^P=iL=9+XJQ9)Yp#E?at}H zdQjeZlZgQTK9>nVimZ%@bfXS%aYg)8!d0)f`J zFW5Tl3+xKk8hzRwcWpU7jnth6A);W%#tw{Kd7lgCQ#`RPOP}4vbtE*HY#y6?hg-$+ z=NoA*-A{}o%Y>QZh%X5@OY+)^(3r3Mhq5`dQ?=-~0uN_3pTl>Q8}b8wqlW(w-`9P2 z(QwMAq|%(owt`6Px^Ks!EI;hqQMuGNjLwrG#7?ES^c28L)j(G~K6@j;t#2BA z<8$D0X5y1sneCaQ|IA#ag_|-ZYgw7-=f&q~^kF428nylb-T??v%J8GvajZxmpCFa_ zi+>icyC7jj5VF!=I5j+xrj;axc`BDc79}K^lB-40W@xBM?zUAF?E0j11s~lhAEYc0 zcPBcwj~8Qq?*90LScPYZH!-VJ|A}PXg@Lq78r3NGS2?TRWs4ukS~+*;`j`yi^!>~$ zn}bqr;ej_osEjzf?RZOj*Q%r+WHV93>bbAoc^Y|N3zc>FUSFs>UniT2@bd2`e`s0? zys>?H(b;A96``u78giN``J-iCeSU2bTCr$m{3E&u%&(xGCWxX6ebqiwhH(44CAOu! zQHthq+pe`-+AtPH^qoU1pnoc!S2bVUL?@YS4!&DTT%2tJ&~UWNXs3)gnpyYuanrEI z?Fx6cvzMK*ZnolDkvLmYc?oa!@v*da7(M)}N;^FN6&ou!6YBVwob9Q`l%BKu$_p>v zu_x^l68>Mw_LH}+t$`Qgs*^Ofv_uigxUWq0ilRVbH1yD>*Sqq4K467T1*(>^7`zw{ zRZytwi8SA^&Ij15Mh7mizphzWd=41w07Y8!Zk$0{6+K&#Z?~~M;5F1)jxLAyMyN1M zCX2HJZ}h1>icR9&E{tB_?gK|ST8%U}oh3VBp-}3_y#3^?qushlcrCFloto zXu))*%5uf6N2jOW0(3bb;#R!>HRASxiB35#Yj~&EC z>AyvxMLaW;3ubOpAzrOQzNF889vXJ#gCbM~9BxIz7z3glBzHoRaY7&P0ZzHuLt~dO z$NeUL^yqsCUY9T)+$? zL{d@Hg$vJLv4fMo*xR(c_E&dZ6=$_KjvrV=`P}gG)&sWgI6^h$!KR%kV!@!#DDP|i zHXHtbv(YaCpzu10ZE58}I(Jf%x8YP7${mVaH34F+1Bup-(m-Y3>Uj`gae}pek=K^* zkTk~V%BF{JhWHX0xaO!tkW2hW0g{N^_KCgqi=&1{cP#_0L=j?!eR+#X00r0wfAhM0 z6JTX15Eegx1;i~qE?PWW8T|FE0Yy9oh(n$KBK}S6Ze&*b#Gd+jBAP%Yzr>!WXtL?y zpX`0td~Sz@zm*WA4V2m>UVnPHs*l6b?l^#AZc2?cI|fV~(EJx*E3Wh3AoqbZX2?uG z<>|vagDPH=OvPJ)P+i2xb|O3Ww<=25K#YsD#n*Tz-BGC07e!k0 zGd=b%ZQ&MO0Itej=6qZJCUMS4cdBgi1~CXC3xv?p=kkJs;S#L9B_#MY ze#NC?K-ie;FZp1<$^qEDiXYalE&B>4ymY57hNzkMe47i<0brKAZ?s4To`@D9{ZknRTN>H)Mr6d0A^yZ z{`a~w<$*t|bN4{derLAjXN_6UG9?f56%q4g!xVr|vp@9r;P|h3bHMn=sv1h?VxnCl zliOhvKdbar+W`GbO@DzYZ21R3w7i#U(g0xA{)q`Nk%+9s84S>76!YKsgouO8#;-5P z9#cd|nYmrOC97gswB`@|d()o3w2t(*27JD8Ck}QQF+>hR-ErT36qK!*EqsVf1cFbG z`b$9pi?%>P-oyBrEz|E$#essrIBn%@Fit|{Z_)Q(N3T&wW<&0!)K(Rt12iyp_f-96Es8lz|^YSz( zB`i8EtUs@Rh97T?fu5x<5smc2t$RA{^T3cTV2nA3q-QJ-SncINvwlNL=i!%q7jc zw($mg){KYEO~)BUI94uyJSIQ)s@{9K%&QB4vZojW{7S8RxGvD!7kQ7|7;hQ6>ph9~ ziqFvLYcH_BuqNnM^kFpQN)APcELeAj;ERjBHsOMJ6&Oxzk3JmgiR+Ldw5D<7C27MF zA|DfoOHB)CS#)q-x3*6b`5Yntnga?ZezfQ5l@U#10lEP@iaiEWB#6m4Dj21gFZ>xU zPatB;`mkvqgXWj?i`*D-?ZW|m2AQ@&-=?Akwtl4;d3PzVk4}5Qh*tishb?3VZG&z7 z=Spkk6l@|lr#cldS$$c)ADBD8*x@UfVhO!&(!#(YVf(eOY-Wzggsv+nD64?z(&pX` zAokrs8JZBglG2N1LcIOB4D99V<_s2+hbcnWPeyxtKGZUby{0NRdloH*&Mw}XAWj8( z@sMg?!sUs5z}v)v9R`KbJU4AG5|AJo?b(cy52g`uIr1Quu^N7f{&r#?K@l z{8;dW>ld+F&m-rq8gJRSodHoQ+fup?M8~%VeT~_xV*$O0WW3zR5;?im_HjOSwpN|Q4j6N)OI5?F*^w+~qH&x=d>BrXIA`LOY zi}(>ApT><=h+J9N*OwWcaw8=hp%yu`yir|Mx!+6)ALR^G4aebzk(8`<81dyWF$_v? z|3)ssjypShMRlMeVBx_)<6IHx_?J$QsHSU-0HOaF4*rx%CJtLHpj3yoXH}~6rJX?R z?Ux{GG{Q5JqcsJyhkAR#MyIoRcR0({k&6HKOG-@Bj#T z*QfZg8GeKDROsAr_p~@%tn~UFTma)s#|t=NcN}`Z2K>%Mj7P3Ly7h%%WK_e-*ULLQi-1Myb5Tr}ZTo zL5_mQPK^O(ES??@rfYBS;pq#HaW z^+m{)%{dpo>q{035P>Sx{nz&Q2cwsd_6T1?=Uwas0Qx!#?6Y7U`y_C+Q(k@vz7(D* zRx^~hG517)YIqxtq6m{rp(MRM=B_EGWR%&@Dks&po_IWySbQIrmlfU+ACVEXv~V^T zhByE}6Dn+!Hg+WYp%%Yxlcxj{0wcz(X{1a>j5hK%glzqszQBrYe@NS4=uv=%c-ua1 zhn-p-T>~X)oBi7RsK$Gxwc}x(Xl;H{lj8H4uHFKD{*V@8yd5sjb?vJKgba221Ue`E z!-!i|Noo)vw5qZIgkaP0>#9ceI7HdU8F$ig4Pm*vZ1+?$wUS_NUf@LyUpWx5`IMAr_+Ba!{e3v_{K+hxumfE z{ClWHWsqjuUk?Y7rOG|beO;TbLm!{KP_|)*^JJOb8bLco( zpmU&4DApCQ)i@D7u9Et}_nVihPCiV_8h(F}cJS$nvs#%3u|Ef2{$m~FRV_Upl>I~G z`5p+upe>3lv4EnGJLOTEoxPbW_Sr$h?wyRsJdL-YSOK6%6QaE+LL;*ZhWF)bidbR8 zeXde{16A%Ec!2xx%SZ4RXD3b=0x0a`mqIN^vYF~%&n2@Jd8M|>V=vSIT(d7sK3;9>zMbOrwSY2 zW8z^PylzxUsDdQ>T1rqD$kscA=?O2aAy6n`(T{9Xna;m*4o2je$Y@8Y@O*6~Qu;nS zNqI=&Xi3Frl-cj<{RR7avDxI3bXr>lDP-F6Et#;k%`#9YsGVK>@$2VZAqO>>IdSN> zbl3TO%L-@RnXz=Hx42&YOtIS0iVfM`%b>|!SYa!Qkj^Z`1OdQgNg>NddC+tDnZg^Z z16j1BV6P)^&@Hg7+IkEcK?9L*xE^@MLXoxn-)>x&vVSy7kB&VHPmTQ&_3Aix&nf}p z9x$<7mGbQ+Y43AkW!&j02i8b6*lYU>{vDESGV)^qCNtUThu6Mu1KgHd`t^}*5Jl>3{?FnA*)c;#3N$sse)+& zY}oFkSEb+QxWg7WJ_K1W>&sqc44C>9mD7EL+JW?VHFG_eZ6*z6?%hLAcvZhRY!f<_ zT2Slo(RVCxP?oq@@3ZXTb`NBqms}oCMC0G{b>U?;?$jhPi@~Kct1#QvDvea_A4iw5 zz~KtVR-zhDKn3dLQxUu}p@X0i6Z6^Q3_amgKE&d;?6pl>E@h$vtYK1ehjta9-T2E% zefsUbyf(qdZoc${%i6qON8#&Z1?w=VFD5%!E_|sWc-iARwa;;Bz6o@+YF5f2SKir-2P9{|q5jZ#@l+@PYzL%a5^!e0?+f%$dr|@YC z*`2#5^%Q-WYQ&{azI6&vrDgjV?jHW-yV^2eDHr3FBXF2;HU2 z^>X`(Y1eifo?@nUTER+Lar;*?aTTfQvB-q8KoP!ng51+2Y~pkbHLvIt%JjUlm0_(5 ztV*a?WuO{o;Mwbqom7h2lm{jY>GN$%7TAxaoZ7tfSzs;k*TXJ&MjLHIgI(9F@7*tF zt|Q{6qhu~^V|Y9PG~4=%DJ7&cdAHq%ri0YhKj9tfKzg28q)M7;EkhlquI(fdluPBh z#7yei5E{HagNZ5@?*~mP1X!^inz?#Y-Bz0({4?(^+S3^B+@bJ;s`8lfr zhr5&nnmmJ-*VcT{a_(=Y{nREL?(iyAz?&99@;Qx+VH1tyRRIjq2(5sf467Rjivkr$eYmqC`}x`D`S)N+Up|8Xrs=xI#6Gxo<7m?fRxdRl z3WZW}vn)S$I#g`}S|^mM>XceWB97a!2e{cW;_%FeKdx@Rg7o z@iEZ;XqI?J3m?QTvNXn8Wen1ibkUu~TbnO(h7XcH$Gz%kjv7zIaGCzbm{8!S6Cx#4 zm$tzHk61{d^U<4vDRvs_=c(#9OUcs{4r(7?Zn9@Vd3{bk-DwxTV75ir0{s1^d{bO{ zt$443U6olxu*z7FQ+ICz!=8tEIuC>{zK%{P?0psy#aXO(;9~TN1(O#ydq=dH7-(rP z(~9)&Ie74ZL=}zhi9N}yoxW$7-+ivHRP0ufu3v}SuEWA+KKtbO=QlB@C9oYT=lJ$tr)p-HU8NM_4&$HT!i@fIqRM&EI8UO>sA$$OA|%64mF4>aA7BOzRNz%CPOF7xD0M>FQ?(C zDkrh}!*s|Kf22&N=RTYYoDk5Zw0ddI?ueA*=$jkaQ;)5?!h%G4XlYK|ZS1l*9Swn< zRx%b~r@6*%AWmJCO7vjX3j>iJk_mTOJUk~o=@Itpr)o9+nXE@m9_rSlwGWhqL76i> zUn6PKgpq_M@?NYK!^mrKz)s<>t=RVe^9($ixu}0dlW$%~G0yQR@s1uKq z_cTVN20T*dqv_$xutJude1vKE_K1#zXjReg=ti7p+V@DHjSeo3;b4Bpcd5CrAk-M? z#CrwVoi)}Pneh6p-16|6__xE@$}Xq}=HoG+sznC@Co*{*6--!P@{w)k1gDM;w4@0} zL+npy{m3%gS=G57t{aW%O&$hgLY!EpyYR&HGPk`{LL|Z-yM2XI(4)2JP|qelm8(hDilI!e|V#iblsoT-Rv^ z^-nX*IGE6H9tm5Lw`}t>?j2bn(l7Tm2(*QHy;dfKj_71ulw%-h9rNmc0u6i-h+*0P zN;piaCwgkyTOKMAZ7dp|X87-GkXLb^Sl(sll!n7eIfuCDkrkKaI``Uwj+b zEls7sS%~@--uC+>ODK(6{WZPl#>cCrd+cC_(^g30pjS4poDt=j6lV+_Z|3yrWYryW zS(2_@y-^P1@GK+iWET{03&N1yawDGEZG7P!b#AvAD*=oL6#A$NH%?E|rGtIc+o+-{ z1)5=ugq#`kx>^de_MX0qB=VolQ20pR{fYm&4dKDfQot zKj;41hx2&#_G2CFcI+ysp979uwU4fI&7j&oOJ#!KZV(v+TBr-LI`7x&m% zB!c;2Lio21{&3SeMUM9!Pu1FlW^0>mZB}mYXnUVFvx6R?o6?vpugita*G{TgG=F+} z6!dcfJ7u?JGp(cheV--sdhcF(8Y}HZhOk$;lOpNX(PXpoZr02t6}pBC(TN6YWnGcU z!aJflX&q03o`~$xIPH@x)9eUxupPJp^*Zu9G3E%>{jh=C^sc@PPH|8eoLB}r5e-%q8UxB08_U4i2_lrd>pBYMH{Kb8~_ zf@4KRuL8-Pn-O%BLet}r&LI{P4j{w|@~WgZ6$c}B$RuAw;6By1kS19#bs7aNk)R7P znqBVHX0=}2BSl^-Lj)4=3on%?wsmzBWm~k#RjEcg67yDtmzKu;p?`GOx0tUT#cHUl z^Q#pruwLbwR#onX!~leSuYHdqh1h71YT3-)LtZc^S6DqtxC3xf0ufLV*#v?K9Ga#Z z^Lf}=emT8@JZ)8q;;7QCP*Yucw^~wfdQGH$p(Ya_v2D2m1f3is$ao$Vh&{4kywe}q$`qtuwfM-}RS zF)%;}95VOe`0v5V#YAKPs*McLa^0>$tFPFCNx{pdO>2_E6Enb?iRo2fbzgTdP$mRF}-s=_b$b`9U)YcRvJ6^o* zx&Mzi;UG4ZjqhEcH#PAs zZ!3N|S9!?|^okZr>KE&>LE&9jNg)%14bM;B>T2bJjlNy&Qk)=Du1R;RQX<@XkQXwf z4Wq%?&)Zjx4{bP_f1rG&f>$?@fp7q`kNMr>(ar)j)RpMxBARD>vdfM{jCtLtTtR0%G>ujP>aGYR%8Y_K+$U{hf-q1E zJ*Ayk;>CN!+OxcCI@4!d0gH@*4%+w|xP7?AJaD=H9wqqfwEoT-G!AX}nSX%Ge4vRk zuXr$oD7=mB4kBKpE6Zn|HbxSQ&XxN2;>0l)`2}YD{yMdztEVdiV>7%t&c2Sl5Y0E} zmW9!&ZLvFP&s+8THX*p6e)U$M+`ubi$uCgg4xpY+(Tl;j-)%Ej-Fz0Nx=2|b=J6)` zDe`#}vNI8wvyQoG1rXhJho@OOFVuX90}Dw?E1yPgzeTVh5*4N;U`g(4Mx{&jOO4sY zCDa5|D9{d+bhSRy#_h@5mcA?t+=m)7>e)7~M_F*Z93lG+ZfNM{PQ<|t%h$BqHoVTL z9+ni=-wIAf+=IX3AqYnkTjF0x8Y3+Rb1UJ+0l7$hZq(Ps^(@K(V#m2Hr?tHvz!%TG zPGBGywWtK2^+{QruzSnvF4?mgI78;0EUO(*u-ahyJg4X>#X!KQQbOm(`)`5Vc4?t> zh|4zM@~Z5a+1Zz6;|f3TOGC>V@EL&-*Y9&nYn@eP2>{#ME#%~;i>O=tecPb%*0xm2 zZSuHwk9>dO(Xt!Al^druOs0%e`k5wnX>>%va9%ob0dsP?pYu%5m$_UPMZG#q)AYwP zGklZZYZt4#$_-HmPrc(oQblbY-S}=*th~FyF!#aY!2GF`ztZR;>(}YClkUQ60_$P7 zwq{Sp(ZO>gRyiywogkgSq-}6U zc{tT1LpUUkJCstw__L){j(D)Xc5-{iGI;ws**@|Abji3{tmGu}L|lc%Oi*;*~F z@3s1EHR{pcs|t(yZ&RMt z%Pt=2N>aqkx|9yL3_GF>%d@asdZQu9Zf=-8<=rZ=CZ|c62D7nr4e8|KR{gBGM|ukI z$H|MRZYTz-1ij*F%!-h?yOwe1UH8)`5!&;Zmu#eiyd&q`;yA2VHjbUza=VWz?}QSe zO7e!Dv*}z|kFtiEK%*C3b7iP;>JH6#q~i$G$3T_5oD|F#h8385lfOJ*?GqTw%6&*t zcn2r+$xW@wJ@wnH|5(ziCEIvZG%7WGRnV$CJ)3vioWYxmSRFsv&^x{os4(MAf4zZG zQc1v1?yW}jcs<$Y{?r%7EbfZk{Lp(<4&du(jZHJ?+J@o^5p}uvt1y|tTl4KQ;6#?8$V|i_m6Mi zmFe+WpC4pkAlzSb88*r$=@(@}$nKrTDK( z`9`hoFNYly^ND}_n6Hq50OaH~mh@tonxzDdhq}#i%lLzyC_*u2^ex!Ra&*(6>b*fK zNzhjkGI^QDfkw?^k{LHk@VEEzU0k#|b1o7~ix{Y1%P{|%Ydi5vT2SNYcO|3w!Dlb( z+t`Gh&h&)iZ_KD9jJZC3tQ5Q<`hyeW2v~cf7R~lJ;>tWm?~C^BV-apGI_~(IMeCM$ zJ%&oU1{f+byC^quxQ^d!5)`^~=PEv(9H?|#{PggYBIMogn`?fiuHRhJ5}xd%raicO z6XxhGb-NF?RJS{z9&I;{c}CpRw)MWa`TlRC=3jPQo2v`NR<=UgtkZZ8lUBsXBtdIG zMAFBg7ps~Wr07$*yV1S(k39`r`EXa^{r#tEO!`|7LkIj1TO+awPy7e&D##wB1b;Gm z7T)}5Eu$5GLpOYowvrA$0bj3Pyl8fQt=~kEo{6n0r{3Ad^@$6VL+H|LyT^LR1N$#a zN`u*GqU<)S1bq)eCbhQZ_f(C2P&NB?Lx4Wr-FQ{u@i6#HY^~0(w$C}s*3Nd~cn{Oo zuX#n(6m5xE<)5wRE|*rCl>&+u$NDQB50D^PdVjAwo^+`-D9=A7U7$Lu8`>668g30* zN*B2J@tGRGl(UW-D9;8Ot_6VY4#hc3{oeiZqdj5nqLM-LL+PLp0_8{9FG3so$}U5Y zLd2m-7EIWbW9&_vQ+N;Cf$(hqmZWEwHx@>H4il?I=aPPCC!glT+IAlpUu}?V-vo7s z*`>0383%BC7@$|bCyj?{&bx^gI%S`2VJ*IR4zrasROWCA_Hgm?V-d$I$Ruv*k98ag z?Y};!C<41}T+2GW0Gi4Mb1pAhsM}yyEq9oTus-xAB+d5-wOpw-My|h_Xur`~L95{~ zof1)dDWXHcXG@3Y^Jte_OJrwzNUl1cf_k)gg~b%-NQ|o7OZU?)!g)e__(A=8ZsYM# zx5=fYU~x{_r+CT!O^b5amvlaU*{31>c1XyZ?>Bw8x(uRM-o4T03w^fcmpYy0-xZfi zzm2H&#^k2kianI)g0wVVv2^f{jlV{}eJcv6JMe+*h?;i6spde+9%!d8!WXZ(5)vI)vbzHKo?xC9I6~Xo%GpdyI=Cs!-DQ+;#OE&l}WK zLFQCi-*e44=T11trmP{>B6je#uZFoQa7SlgAHeMVZVCFL*>FdwzM#_>i5M#G9m_}) zzBe~D;fTV!Y{jV`@%nzg99A#2;-pc(4@ZCU*P!`U&^r5F63@rq2D-t=MuoskftA@q z-B-?)aR!OqPnZ~|_?5#(uI6yc=bY)cLM9!R%##kBHJuZeVM!4($Www$A}_>1yOmb& zudSpyPNiBTKBPtT(E-F{onyACqHnl*nwjRBG$*!iuKW7fn=5(7u`aEV3tu+g1az;( z`bK!O$CG$s3E>G6Qu(C}1P93v9tx}a>!0?`Ro|a{|FAL|q`-IT+}qi>YndnXtAD>8 zKY{l+-W=DZ@bja|6RUl=pGm6Yb@T~0A>G;uJTA&c-|B$xw1PN@;79$tk5VG39vz#y z?{xIOASB9MmyTwbm4QH1`E~rVnBkev;*Kb?i;eZE`$x(X0R*2no_nG%5TA9;DZ0y$ zWBF=}g8BSl)eB1MqDkBVewKjeDb+2JiH;#<0l@?s<%rK9P?iOGmVC|fq0l00BwUws%mL0SwhC3c^Q*!%*k$$6@UtzBK0 zKlQ5xbjJ_;oY+pJ84y9!3`MY~WFCuj-dD+*;g%qV$@JegOa2ri$BFehul5Pe+{b61j zTkrG^h4#eKWoMetIbFc$Qyoz@HcL88bOE#u+~ zH*{RM51ddtDvg=|g|%+|u=xO?0+`CillJUyJR8EOlGV~ap(UXQr_SCZUAl!uIA<7~ zX1=OZC;#SZ*^SF(=s1CW88%4N$+5YU{oS9bKA^!$AW07JGYp!xZbkQqElGPsU#AUq zYaTW^(`&j9w~5I`t%8EiYCXGPa&$Fp)H%hVBn^5O9KATVzOGC|BiKQs`yKF-AoC$= z1{4K3@69J<%DMUEaqJ6#4R>)#3!dpLcT{C}Q8GGp|1sbwBf@4LQ&=)_pPxz7`D}Ua!wM^Y|3Y&l zJ1?}q4s))Pnl?1Y?yt;AFn{}=@z_;JxC8ZYiYUdL8Vu4!-sWu9Kl;N+^8Aa2y7jny`uCoqsiuP`DdS%+D(F-9W3P5!oU^fyW++K( zkg%`|bxB2>rREMDk0wFx@b=ceKU&g_&MA}MhuKru0e4hAb+N%#gg!k-za_FXJ}mnR zV{u;<-PqEV3j6+BNKxT?B%4P!oDw9g_5!>l&*2xm#@2-7kvx-cVWQ!QG;8 zkRdFmQ4o)L`@REomHn^un>><=n)#+ZKUnwNNYv#46l^~jB^_heJTX|*WAt@z{%vDq z%n^PL;uDdds<)2a^#2iAp8G^x*)|(E+u*UU zbwAIw_8F;;%?y_1t|?UwI3+0$gzaIO^kFOhz4VNlfj>!}%Wgw^+jzosG-cN0SS683+m>Qwd>BTf`Fvqz}zn@i09rTBt>lyygy`=}_M% zW{UqK5Emej<5!?3^m`JV`O!}5`atz}^QaULaJsgG6IPNAd!)aY@P~t`g3BCqQFY7KI#!58&OrSF z)+>b~3Ft#$4fXB-Y!|Z{J|2?%Un9L&E$%pB}Icksx21b7+pW z?53&F*l`7s^;&cBX54N-LE8xjfE+*M?lE?nqrc(hlyf4pj1TseFJ`o zZ^sRu6UB4Al;TBj%PqC%-A?ys%HE6`eyca{CelwbO-@(1ybnh`&Q-S$(rRw)MOM@uvzR zy=yp#mMmX?@EoaDn+U~Zm)8Hb7rK<*!!2PSwKnV&zN0p&T&mj6tDj;`XJZMN*JZ&e z{Zg`ya($gVgvjAs;|3TJTBjq$Z9I&U@`xvm-a2o-yQ;TZU{#tmK0a)b$AT#e4$3A1 zDrLzn9dTAjkWhacfQLv3=SEbl)j8Ggl1ihJdQzzOi0eb|X`;9l#;{n#uf-w` zjS8?CN|A{aL|)>@>~mCA6kxN^&Eo}Gn5P|Zkj%?TP|2u+7N9P^UH3@ukhIYCXfkk2 zR_l}ROKOI%4|pUlY_~aG+fS6EuBI+K@i;UEWSzy&-JjVMb{7Yml(LmPO91=YAfIv7^H%L>Jgd4f)64;)WGzTH)uUBfHy=`$Co+(GR)Ec< z{e+pHcuy|aaGrIb2Wi}aq_1&L4}+y$3IaVn=G29e%!`R5Z*pHHx7?32NBV%h2&xRV zc>^D36N6NP={RP0%QnU8qa?k#vU6Y7k zJzSpqG*2#c{Uq3d77#)m1eD4@Yu~AU;?Wveqh3M`sk+8Ma4*Vs9lOI+2@W}1mdV!` z?G#_Sj)?axfg0432W;I4BOsjx%nZ*ChY*dNf%^d*PY=?O%w|+tSvP-Bj#T92 zAF$|&d%6tdc+QoJ$juyhFjcTABUBLOH9GfhMnW%IWqs7&8UO_BUdRs2fu`|J0-76r z4Gxxf?9cMblBbiyqa*#cbZAb{@^KNn)AAyV@)AL_n(w|=Ij=hS5uoPqpUe;qqJ^=xgGottOT&e}k+&TzILZk8P7 z4x0Qq?`*c$ma%+_Hfm4E)p=Fey}CG%C?tDCN2Y5XHCAl2ugEFLt2peh0MvGbOuJ`} zn|WC&`{;>-k>YsiBR`m-k~p15bZ`_2U%A*EV+mSPC4<}}B0HT8v2D%W{{xdW9skd7 z)eslJ?zT=$7O0~{TpozNJ%WYx?4`esi2&{|kE|{dQ%GGGH3aM2Rgyq^`YAGzf_KP} zI;6X+0GW{4;-Zqlqa)i%5U#y|N@mUTj|4NfWYoGC2rkrZTb^I0NS}g77he3bLb4A6 zGHwVenE^``8D`+8T3bPyB=9OfneN$#dycA30xC~AG?Fw$dce+%;hdq;U65Ujge8&L8A?t&^@NCdu>ry}dJh|mg+!Z7W92FYK4ml7$Uw&te;26GrOrMzycw`*6q1l4@2Z zXRW!dkpt>By27#Sm%;l1=!>Pq-}VDwM`>=Y6|c9``H32Wy;g_Yon}ryLiN3fW&;xd zH-zoRWgg^bAi-KM`TKyD6V(aj93nmuo&BN5EyE22_}4|eu>Bat`_mN6(F`t88@T2@Ty=Xla{z5=+B9dqs{Nn=k9NmCN{KDgH z>zeukpdtEG9k}1{?YbAzNi|z-Y9~_47+Itbw*FW|b)ydFw?-D;t9xmVBCcje_uIx^yK&p!9h0{5bysHlU{O&Qvl?Gr)EyQAw9L ziJ@m_ZK0A8ciR~V;C~Ao#^RXD!O2|fGO?NwA<9Sa-#-tJ3Q#FUTApGJVF#-EyzuNT zKsKc0FG>_!VMYmO=JMQ^d3MhpT}S~9u%k9&cu<$PffQV3^W{*t&E+-`m){peL7D+N z+=0S;Emj_-E)6w9-aBm+g6{gI8*}mI&ke@)pbS)}E1xo*>=dn{3%YSS%q+No&!uW8 zLqMm6!t17Nak0TtBt{6Bt>kZ`E~{?s%ajrTz0Z_5QpfG{Ap4kkZi?)d`f`pMM>OuR zRohv3)>8eE@m=8-a^B(D8BtxD{x9B+6g!q>6HvNc0BTVN_tf;LI`U>9Kmr|UoN_pb z-$Z|CSkcNHr6R8nAmL(znh3p4%FmX_Y4ziU9L)iy;G6&~;>Mhc(p}vYAlmD_H=e-f z%3dr`Ga6Z-u2}pqSaBX?nQ+FX$Mj6kQ_)pW%HMiZPi4sZE|npL9fqi<6~^i~*nfZ% zthII~n$*B~rp3V#ffoI#2HM-#f8BAUsIZiOR*e8hKYa3&_eAklLb)C-dUNFlc+ZaN z)u}#@oB$hw7QM>S%R-ojE`%cN;k%sI*I#gA-Rqwkvf6hnJE~e=%!UE~3~FdIke0Ze zYUzv#>?!$k79hYeO4AaABk>mA0LS!1Jq7~UPw@aCzOgZR$NG;depqjFr3Xx~|IkUK_IZT`pEr#Sb}^DlVU>84I$WlfAf|w5UgD z%|onQMnM#f;`uyN{8SkDncKjxeFHx9d6#yU2edYI(eM}sx)hMeL0sQI3(D!5m;j)N z#*_F;v;OWsY5)gWJa3)X6lT=nMAeUFI;sX=RKC0ea~L?6K=tO}NKWBe#>s8}AfR=Z zbn)eVCU6SJ_>^!wiTM2a`Wcr8EMQv}0s{Ae`;@0{%9smXU$QBOiR(BtOXXrqOuA3wX2~Gu z_NhM>w9$%^A}(!P%1^eXydQc{Py^UfE?7{y2)v-aVcM{_fl>8))=B&l(cCF}a2V)?U)4~z*5xIOcADOgl#lkx;TIb&ip01Vn<7=Wem8@qI($sx z_i4zvYqt%$`x80e2pZbNEp(gmMNlZH1InkNk~Ejvvbwqzll;ipvX-%Ao1s-n z%90jaWZ#;E$e0#Vwo%qFWE~76##n}#xzB5=&-ebkf4{%(Kkh%ikNU@>Omkh=xz2U2 zbDrmU&Lugr&;Md(&3 zdzTj8w@p4z#aM!0_!($+77{!cS7GKJMJ8sd*4$;PJ1V@;GHfq`g&ewiYTP9+9M#O1 z@*L5if(?r>q>8SnR_8qB8hw1DZ{8W6<}=w^#5=(9uMX4@!gCDsW3M~Ro4NPMK*Tay z5BWb>8%5^V+R{wVgk}`o@X@20K{SpF@a73DPs#v$k62_!?WMAFTx&RrgGUs{5K)}r z8V6L6b_iS&m!zyy1Vj$J16$=#|5*NiwhuX*R!&RtK!rQpRHI@#-g`{jT36!M1z4Y# zrSXc^^X&m~gY+lY7rv|?84bL4hUWnfq$@;acCIiN*)U=hX9<|(-N01%cg5S`OjuYn zvaoWkWH^a=U)D~Txa7!GZ*@~Oz_eWCj(g||kF4JJG8DN? zp$gCu|LE6&SlNFCN4Od{g@%(TS(FMx-Lu@S(QZgwQ z6IX5*q9W_+UjY~02FOIz0+hE!oB14B*;`oIP32qz{z>C!%YR*SE?^00%rrv!M-J|q)FioJK3_cl&h{E^}*M2ggO(4NTN z!*zkPN3FnJn3J?liQ2024N($6m`ZK*j>{p}_&>Nu9kvy<%I{SofQkEb(C1?<5lr|U zU=JBms<@u?mAdsdEZprN?%oOKjrsz`S-|qBjSeT?>``0JG{LhMFDBrVn zz+$C*Hg#ZEN{tDFTYslJNsIjk23ghXYXBkn@q}|QNc!W8iY^g|@Z^3ghOU9yhAU}i zY6z?&S2c9@%+=(!4I_23Z}aFDI|C6@pdO&PI@u-KJ4i(*J zW2ea6m9n$w+sX=3P0hYaa5b(e5JV2oLO7x`4u8IQ2j`1(kOPOh^%V|W!m~ zWa+_Wf?Ejt%jrP?uSmH{HSO3_Ii4Uri@qg-oGsRIMB{_1z#?8PfTOi1lkf$0V9c{q{IbelkD2`x*tAi<0>)_bk~MOT5!!xVV_+e{Ovxi=3f%c3;Z#K!rFxTPJz_E<54Zj2 zfWap#3?=J0-vr^YLfbY0r{szq#mn>Ix5HY+&)-l9kw%R^h^xl`x`4hPFLE6mt-4w-k!nN_w#R!Y;&*`!1gh=Jev?Ki2b4*; zDIzukSc%zvva>YPmdQVExK23YAlf4iF&s5;i0Oz!Ji46pZ2d2^^0&m+u^~P$dd0}+ zC*KE=?HLwcoI7D`>pu%I;3?qFTnlTIsKz7-I{wG;M%ZHDcboq9yMY-1>ZXhgw6q4( zR%F(qdS4V`TEisu?}%TRotfnN$G6C;I_iLx&+Rt=Of9R*8LYbGn7Gf*lYp$e{2_=- z-cxlR^lgGGQRLlYxw}mFjlRdy`JW;#g;NkNc};=I0R?0Xv{_3`+`-3>Ac~?9QC_47 z;o%Rf<^D%#!fCqTa&I-Slz8?;P zyHv1gaqhUWk}1Gk;J|t|3NNZ0y(a~+SwSk`tLUr_;yL4ZBM{o)Sqv=liz;|7I|_0D zU8{wlV)$sc;jfW;f!>XblyUmWH%=PuccA2SZvEnFi;YotU@)nrM?PNR%%IdP+E~?t zeQ+;HKsKJg_zPe_iV>WAv7l4VNvotl2xLAE}dgar3UOere_!M&^apUns1oj|}_t~=4qE(xXg zx2Q{R96>605EoA@Xxtv30Y)9lgl;wiW^%5YG@!Fwu>J~QCX!>KU}*SPsJ2vJ20Z^P zU1@SDnO5_S>F6gjx~UJ-BkbEKCgwl;8|464B2?ZVBG`>n0dOmvHW*q5wxU7`L5S8P z2+@>Mr3KL{e3$R9ApQvNwhKrQKl|O~PY{3H9*rYpnNB5tSTA4~C3h>YceOU@)GuL_ zD;tlKYdW!Ng_p(lk}|8!a@Jk^IAvLDS!ibb{ey-FvHi}ndtZjGro4T<{NjNPBj9VX zc<=^rpTzVckcRTfUpfMfQf84r8o}xR1kxM4)!vp#C`BgmXSjolYN)Dc~hK3Q}e7BEP6-89j4Vr`azI52&?KxAfRul zn%Q6@zifkZz==-;AQbAgyS@fo9XlZD8BmI={W!kv*Pn)W&22k!u7&_G&o>aUi!!HW z3N^{c<7}H#kz>9aj-Mb_ESGMzF)shfM^F}#1TbPk z{aWCQAK42T$Bo4W&je*Qjvz%4NWzanDfIe-#BJZKc9(fGKc`OHa7Bs;q??w6>7}sO z03Noyx2?wbIZ*tw%4*9ZfBFQtGww1Ox4?o`Mr7|^c5dEreV$7Wq(=I80Eb)fx_CG< z_{N)8pztDP6HWf<2ugb?*E71;UWH*r zFham3f}Hy6FPCyjI~n~;-dm6j_}6x%H5wER9lFf98ilp z3NqFon})=!VwuLWL%Wz!gD%Rqt0Q>>-?_Nsdv<_yCo2)?wiL2(8|g8B0AjE?;yZu= zwk+BaSV_f#6vlnaGA#m>(!&L3_sGio)PJC8V{^-KIZspk0XWkNMX7Hg7 zk_tq+*|bq2CgKn*I+YgQVf!OT+4L&kwlT*eqtswKjX7;*oB_W%r1_r0)uv$5y84Df zBgI)C7*3qwY#@bn&8Z5Lba#x+a;2}gS_5B1F zpJexco!S+)++A|kO&mN22ICoH7)i>wNZ^t)Q(4D;CuQDPBFQEv1n7GVCU4(yHYuB+ zyN-juce>`9&-JBu1XA+z|73uUyU3Dv;|QNkiwo3FJ|``~f5&IBZWq3nmKt0JtP}4N z;8~a(LMrku4&PCT1x*Tgla1t^RIJTp;r8XEPB8mt71s67s>*Ba34+d}qfN$)+PQI0ux1le%q( zUwd>iZ#yFk-!(; zVN^UI@{mF~v#)G0y3Or(-u3MK>kivN2Y`UzbDcis>CY%-M(lzG;(&&wC(8VOg9+rP z{v|jO+mAzLyFwpHxN+f>ay_fOBYda zzDKukTy$TYOo48F;q0_ejH70)E-y%y=dO#f1d_Y&tBO32KCX$7Xw)K@6|qVhPE9o2wk`9F!uO%N-s^3?<_+stNu#0ub&3I&xo`Oys{EcL0aq9{zT z%Sh96B)`nbXSa0djm!DStMBwXz!lxA7~q>kiG%wzrFskD?x#oN`!CTWG z@V4CU?db@-byv0eyV6)5$BVyw_LQvEI0v; zci%SFDSZkliMuEd-?XRd7#spEKY##M z#sTL;+ziR4??6o2Bph+b>FN)G#XZIxr0)QPoDeFJ-Lr#5Bq6|Is-6eUV z9!f*o+@dBPAQs~60w~eA!Gb`Th+iZV<2Zntx@FYeYQbelBeo!Q6VgybNf{5cq9&2RMISzds_|b3%*elDk#}+Vnv1VFWfY z`I%s^&n##shlW8#Xc%87$3H1j8~Pwx&Tew+$df)*|M=pH^0_cxv6skVp=iRFXU*md z8oV9uoRJfcM3YU;yZVprIi;$o!tf8B`*aCYTSp0|PE0GmPz>O8Qb>D1}(Fwkp zC96Yzk}0)Bgh#U!VZfgc)zA(D$s;96aYm^0=zm%wQHq}e9~~`zb4z=FQ$}6G7c!2=VG}qXoEmC=Fw3Ni#>OqtS{EG zh1M+ji<6R2s`e>x)5|iBz2ARmLTYA9|0nMK@7P=(8l}$-eAhOw++_kr@OoVp=G@&t zz(fQ;S$$&$mn7YW5SUCx-JH0BMkpciMua?ICByc1)XTtzj72ntZMN6)nBO+Zf@pg~ zPPMT=u!br_7X5(8V@Ja3^r`hO+^4JCDR5TY32H1CEU&TMQ@zRV(dEh=vkF_rd||cV zc3nhR3cALI$Z9`NjlT3x*CIAYI`VQ$TXBY}0Ol7job4aWIP=Jd)mA@(lkB9~%ya3+ z*VV?WeJ0DuNp7SN9sAl?sYGbA*BdDNGJvyak9oQHfhtzNaGg|RaoXO0^s7+CaVAF; z^DU(ZA3_m9%6k)#p*G1XZf6OA zBjq~6PVzcMcTgsU@Q6S!TZLaclmRs@bsdF#14xD8gFw^LIb^_fF@A&B+j*3s4CSqAd3ho^T zD5CG3IjPTL^n(stF2^7X;FU^sOV1XWx9!<5{dpTg1Vb&h1{xe)f*h*gJ4Xi;ujvad zhyalC3+{)?V61kXwar-Dwld8Lxw%{*VSHoEU(fvXE`Fnr?2yD}Ppe7B%M&6ijHQ0l z%GFenGVLz5Y-GhtQ_iMO9Ga5yne>!Dc5QqC{D}+ZUdEr(Rzb4EebWYD86mXblGbHKiW50Tr zYQ`-mE&jMJVBz2M^@;Ko2bYQB_L3ywdP@X6JYX+}Bdq+?);m? zUAaD#4S{=8Q{fSC*{vu8z$YbVQQL5j2Q*(mNvI!A*v#TTM=6sWNCLL!gKj<_ zP1Ll#>?n1!orZ#wmG%ySaH97&=9-2;o7y)9OUis{kehf_C)*ZM$_IpcsynWFQ=7u| z1rCE_Z+(BT(YV|V9KFjhA8KYl3lKhD>-|<2xT%hD7i~9l_9*;?2Sz@AxcVvACMs#v75b%J<9u4)VDF*y0Rgs9dgg7hVjSX;coMs5|^{EeZ zj9Z|i2RDz8P4JX9rP4BTGMKE^R&+gpGZ$yL*M*O+VG5Yb9IszB{XB{Wj`e%#BoU3c zVT6P@)y_wOU4cw`3Ky&Bdmz*C^ZJnmweKqXoKG4(Re+j^+WBxOHQH0UJmgZMh}NA+ zhzeurtN8-7)PbBVDn*goIKA>nzSf)=X1=dYqf{Ev%Ovr74qNQN&>0*-#C6`buclgY z8f+O}n&H9N6p^HhM$B2BB;3jUo#0B!&3|}5dD#)qk{?_(<9sy$YTY&@3ofciQiS*d zUdKU7p%r|EJ;RTv`X+5?lTS6(vr_htQT&nIDJC~dJEzT_ zS=foYehD&Wg3tC;gU#tSk>Gi}3KH7oHL(&+&(hdJ0r#&OAK~JKXfmGec~iafp4^N4 z_Cr;2pf`8j!KX1fhke}>KL1c_#ilrF`FY|AbAiaDlgeW&Oi#{P{NVyHfbHQ(@@oGy zRU_Ujs(d+xZe`zz9M@2l>xo22ha2nJrWWn@Lq8!cyzC&Fn;J%LJIxKAcgLWj399wZ zGUs6}WujWKo znwW`5;){J>_z~}bT5oD487SSCB|Optq4nqTu7XHjv8yRXEob!1iJLMwIQs7Q?SQ3Z zWuw-Ii~@gOdLCuN+6NG0tI~~3V#EW>??3?1tlVCl6eEZQ24Q_@3{(O->V@^F6E^PO zKe7hf@Kb5nqAGIFy#YqOE>(3Aj3@{8%BCX9(v4 zH!6o{uP=pAUO~x(tfV-rlu3)?ak3qtGqf5bRFvJ_(?V zx6;#~WC${>oRXmf=k>y<^sZ;6kx1_zg$thrDdrzva~KjAeh!^&#$KFZR}!oE`0IZf za)BUYu2SUN~SF4XCb%TRwqRl?=G5D)V*iQbqrKsJ3UsNRh4(!R!*Gh(VD{6%m(|3I{ zt<}J^Te$$8dt_#`xh9Z$U=1gUB?Y`!VI7ZNZ>ortTvl>GqQG6|*6Qi1H&cf;Bh+-r zwCRR#fz?hPXhIsJxa^077Uk5#gVzO$$~y|ZOqpxWXU7WY?<}3n=pn+L-sz4I>042% zE`O-JX8vxRnvuSVgut_ zq!hAv*;Q32@VyB(lon@Ok^T6S2Xv|(o9-=SG|>Ly-8fq1)w2^(R=z#r%GDeui9kx5*gKI zT_WX@=zGrP{R-jNNylDuxW3UJu__*Q0{01k?nx*bIXjWD;H5eQy0_dwn6DQvLo!1* z+v`LksY?GM;(5lI));V3y_^krrK}y#xYX-GRxuexuz1T>nyDK`gtF z(4ln)irNegZQZQ!ViOqq%?XG&yZ*ObYD${=>(O2ciuwjQC0CQAI)e)D0~{3 zS-%WhgR=t`m)C`BjC8ocn{ICVl$kd7Xe!lpBjN;Sg8yH8^>$I=Q8ADA$-CHkINO?X zu(QdrBh^z~RV>T#_I(+)_e2F}J_|t&i_?sfw7}WV+%qabUX;j)S9XbfYP2Y_owPN- z{UAJ+q%SGv47*bfWo6X7m_YzAC-ZQhHn%B6sB$!9dj9RTe7VP)lE_vEj$(`wRMOub z#-S*+fz0BHgBBJOkZL{j^KJflqOQ4s(dV=^{EyU2yCV@*q4(xXN;o$@{Z5yOkfl0s`;~5>>Gljz;@Y^%uoDM2QyZb`$JRdSJdnnGV~gu=2cAB zwxRD`;n?4OKgrh?(a-Yb63%H3TNQfWZCW3p>?OE(xZtDpPN%kWV1HK=2EyqTv2+_3 z!u-QS(H@D!JItr%x?w$@w*J&}QeU0>N-H$ZJuRbs4G}_Ld!le0xf4thZu7m}ICzWj zo(&*La)*HrRjS@mXvu3eizkF~xcPBd)bV;L#bMaR7cmB}s98ln?#dLToFv*Je(q-; zLUG?S2kg}A7B7NEh3kW#imavY%=AUxXnJ{DpKX6tn=8yL!nzt`lQZIVbn`lJ^Gy2auq?`H5XmMz5;wYk}}THZ{26G>b$bh<%3|&5^$J=y@s(8OxD7Tm57-g9y(z`Zc zLpKREqK~Zg{@QazcWFnU`o4UoYoDCxo-#k+aa!bUhj^+LyShDH;9%3ju2Bq+Wuc+; znUsOIngdq3!LPsfC%eulT~=H`_cekaV7#Q?)+S-OrOYc4G zgzCFA<3BpkZCzf&Z&4We2$Pu=hFfj|?O8|^;xTb0&jeqk#5E?F1`}4nQ`MZNb-@z{ zPIBaUKLSW4Pp@I#*5oFpNn?dI|FJ8R_U%R- zkP9_-WF#xE|3xjqa;3k{+zoc$P}et&h_8W(`+~KUla)7t{rEBjVS)UNlH`K;bk{tz zIbBv7OI3W`MtJ~a(T*5a5FVxHYgG0)#zl&}(IFYV%|=v8VdX#sT(g@Q#3si?+l_u@ zr=HLtARhZEcBwZqm36BLbth!jcf>39bKhkA3qGGo=mP6XI_2^uR?0*b=y$T(feel@ zc{EQuZY#LB`jRVyHu>qKf5dbNO^9sYh;tywo@4BE8_GHI0f-XU_bx)%P}R~~QI!9B zfJY3=T3iN6Ud_3dbgh~vqorPA6A7iq+oby0+fe@CUwm)t+dY~Txtgv(033RoL9FK! za<3V$8iCUNrL35y*BF^V*_}$blCdAGK*y=ki$TZfs#-6_ zU|FYM*`A!sSVqY+`RXO#-jZ*W4>R_7TA|%9(`u~7JL=ER`cv3g&mZb%Is^QDgxE5qSHk_^Pj`;*n?<`=z&KMv0fW9+9~1Xcv9i~A?_bvLT)yCAgTNzKCk z(MzyEzZ=9H-RYjQtk@WJYR>b5p7TS`vi@XQR7=2fpX7PRny@F7x@^PP&szeK4BeEP>v^I+ zNmG`z?Tn^Gi4tp`WUe{UJ(Spvj-lB z`d&;t#@BOR$~gR{+^0^=E*;$xoYve{X=rJdfzn&bgi{dl$e?8F>Qkkk=P1*Q@7hT` zQPSf1qNgCr0yb_b4Tv4*=ZKMgkxD%yOZ~!xl#`_#XLa*+_Ox4wy~k7bBdWLs>s~uE z(Nfv$N~4YYDd+Is;pttCLf`NvA^K$J#_Ne>UH`=^6l_p|=lYjx1p$<5c@HcSwMUHn z9U?~`NY1USPeSae|G`wjElf&e>NSHEXeC3qc1525-Wuq*u z5o{LoqZ%VkgT-pZX6|p_iXG0ny<9^>vT1^@BUiC#O}8KId7(1l@(JIVx&CTmS$U$C z?!3a|e3#{KOUkt7lv?kBfBo4SyRYlmH0v;B6Pfvqd0$V(MjzX=_$^w(e8WhO%JAbf zbeBh|`CMb<03fZCTm8GQBtmU(Z*Zg~r?TP@AHkOt;D8O&&a)(hINL7MN7CU-i}L`< zi`Uoar`Od$ze49zgGXyPSk{DD;6>LEI??Wnb@! zqbku=LOMRfPh7-kbkD~KcHDH)V8dd>{?tb%3CTiPfDL8E?61PyI^YrYaP+yBAr$$z zr@E|salbdQD$t-)c12+Pp7ghlH|c@fD@U!&rX}i@Gp*;kyap>03-6MTEY4+spVJ!<8 z5Hfb9YhjZQBQCROcQ6l6VHfHMj zKYETqxLbgnKRU3#Fg^*S6$M3ws6CvHK*6iZ1f#w|$$9 z;ef8))`|3n%wHU8``I5M=eJgMB&G4k`|Azun!qH5PBNzRv{W=he*F~-W>mcG`j?Ot z*!P&UV2ewZI1PRw?GzDAOpP**SdrDGOg#0&*X}8G|AQ?diF0?DVliA3^dubRxzD22nZefN|fJ}xR?YkvD8ysf!{rosNGyhD=Ts_w)^@SrIS^#^+ zQ!5Tfp4d1tSq>F=J~n})dnle|rs@lWF^N!9hC^!cXmmp>jzIUI)u7?lYIz4CAgRv% z^Ac$L`^vP&UHCkhW%XO(Fnzw%p61aT(D3nK5GjkI|E+#`s{T)h`*`B%=O{L1zDon*>=C3V=P)JtB}dCG7;41zixKku^)rTr`THI$ z-!NkL#p(l9%41e+RCAlUf@D+kuFRbb(sI&(zv|;?q+#=<@7hhD6P*$YBbe&N`g}7_ z2J~j0Rbvug1~AeJH$hv4j^(7TQm@{|=eNZ=1K=T{yr7}BD+@#M{?I;Co+f(Ih_gry zUe(OIOl{Gr>-XcRQ8o{N4rABwt#JwxvgZql%EGZdh*+&w`w&^HD)znxZ?a;!zCCb9 zvP({d^{BCU5>XLM3w!iZDf-GK#YowvUOFuADj7z7MUAzrK?;atk^o*`lJtHW~r zDq%f2AmbNn^Ud_nOMESR&z*Y}|C;>yA&|%?%j*x;`A|7Uuw`)x-nCMR-<_W|1oPZO z5`6Idnr0(X#md?lInjceIGm|<7woXEmGrE_t#ay4dt`Og73H_r;Wae(H1sk`Cho0c zHRkK_%^YB;8bX_;k4x%ZDiN=5w{0BhK^lESwN_N)jaBRO)P~yLH;-^csL_YL!OHBg z&BAF^j&&PscY-X|p5W}E3qxur=b=0{{Ba^D`9WNWfF}X;RDveINubQI(*7-3pG>kKx!aJ{J^es@W z??}UUsaPSrt{sPivwe#?h?_srFH7;R2n;-wt~JLms5_Gpn2DB!1ahH58@k;`s;Q2~ z9w%XcI{8}HObP5mT{w12aEH;tM(ya+TE|b`ys`HAbt%OiimDUJ_rEQ_zG>}^zSN~p z$5%$LN=*sZz6b2w)e)8@G5Y7FZe8M$70RP5pLTTg|4!;64}|e|kb)|ECl`B&WpkQh z(8qQgv}5<5BrP(M&^<|G$tYjKT;`WUU%45p;dEcY-xkNbnaxnY@@Sv=w=VF=a+BFR z;N_A~&7Q;)l+{L&=bE?aioly|)@L!_DdK0C?av$-r=Mza2hQF5My|nd2ihMOC)6gM zwt4vYI5u&N!1|=Z&8Q!bU-ZktinZd;4c#}4sS9NV7r79riCESGzBSjZy(dnhc6z8T z2$0+JbQ-D(o~>y5kc-B~jS;T61qUkoUv!8SX&TM(h!F~Du}ogP9-q`9buh)B_-4(B zw&+){iFepS=bq|IuadUZ)h!gMzu_NIFuV1En8_fYAzive4L$Uoe?;yQVk}8e-dpTH zbmx^Psk{er3%1N|0V#MJ=Jb2}FGXJu(>&`v;zbVg_>||}8=1>s6sqatTg&|Mkbiu# z3?KCAik4jM2;<4xB_+4OxZJrO=BuLG-JSd9zMo|uR)Loy2ZALJW4&56GjqCxqiZ5J z@XSQc-1o0Yn*+bLrRR}EEwqp_Vpa$s&(WMVE}I@6_=4&`L{DmLICW1Nq)OIJ{7RYX zmh&9BU~nB7&=<e&&>IuoB`f5Oj=G~ zsba&Dto}e%XyqN~A(7kDU53^W4+-~t1J+(_u9J)jg=Y}#p3Z(|dgQR33#g^C{=S~% zDoKSD1?gJUblVF?;SrB=?=EJ$$KY0j+sN=CT5=f0!)fjlYz*DY*mH4BlJ?0_r5NJc zhp^VTzL)BOsY_kuvkR@48LrfKRcse6udmRhlcYBKA|?=+mnSDe0(K%*u6X1b8b*M) z5j-*R`1yc$R0hLqY|J?V?RD}VH@9lq;EzFkc5a*;hG;NF6jOw~-OUes>qO`XOFput zp!;y{()X@lM+w0svw~?nsukOC{}|8Adlkt!`dRi?x65%vLx~r|^6M)eFAL#|+rj)O z&bsXN{gqR((lD;n?)|l%JoBQQWZJR|pvzoC--`&zLHk;EJku!PhMk_qh2t6ZBV-RiL;h? z-n3qL=H4hF(V7DN27-OZ0dB^jPUmMHfk-swwJ<$Y>_3e2gDRvs#ol1r7+Fmjw*DXK zV~m*+q$%;%R-cAYxkd~6HA?K$`-5p?qJ{;T$~;M zjyEyXC@yuVzT0U@>nMk=*+I`dO3dl#ZI9u#a~`|5-8E>#7Ywi2%?2_i=poYu-ZcYX zU@D7uy2&QY_MzS9c*1Q*v3H&8Lwhu0lv9LdJfaY8bh{MqdG5Ii8gU}`gM5(L7UXTNnDLsE?y}r9iB4CWvHyJ z4_(W}eKu)TfHU>WS+>6I_3ZARmsX~4&<@?AO`^PnM^@WZ^OEWkpFLgXAi_UFQpg5V zRhxp#<3@G8n8CN)6Qo`{{@OdfxX9ZulTk=B{t~JgJY>9c@)R}UfOK4heyzv6E$(^D zmlH4xcus91&?Vu@iAjj4vhXTVaexiZA>Qs7SqXV#kN>?4T7p~&;$M&{*y3Gic$$w} zbs#m^*w-M3y?GnC;M7MfR(bmfA>=`bj~yyRNuLpF6KlH{pTUjlRW4OMOw7YL)uER+ z7duj;TPlWK3Dmbq%kW9lBX?uG>p+^&@3pARF#Ww|+r)1n!ixmWx%KjF#rZr*b8(1Wz5DcW#q+dh>X;*t{Lj3{^gb(6P1*l|Ms^ zv{V{i`?Ry~c9^6;O3Ex9i)v+~zox@h8hcJ)PE#jAD%HDZC%U`46`2Mb@*9zk%bsL0>tnIWW#kd)_CKBH&mU?obYLv~WHgw^#kPOUc`yaK0 zO9FZgiRY<$baq8cV`z{5kZ@EjiK`uq|HCVjliri3Nec<;viu{*+<{F$ZVmO>_&6D} zww~uYZSXi-kflelDIKkH9l5l24EdE0H7w|TL5;bZCVeqw88a!_3p-cZ*lD>$k0&i& z@Vu16wxFIF!Y`n+pP6AcH@&S1t-R#4QZ~74=|f}2VqtehOE;eHg!V%Bl3I2dr7HSR zfPa~MiofN;;>?^kJ9><8?o;;kl&@LgPq+AQn^^5V@3%t4$0)higb4jSANxh8Ro4J3u01fdfO@<3YcNKK9 ztTTQV9oaIp`dKNo8)UlWu;=Et%akUn@AtR+{BXAkT{bNL9Cnz)hQQtadaeZ+HM_CfTSMa|{dJ`X-KGVvHq$NbFiLY0 z*$W?7HOr%fVj4m=}`?Si|42UCTM!kun@06sHhC#0-M#XUzTaYoV)up z{|hPRsqKaDE_-?7L={r}v)U_$5990P2ovem1uJ8DHu#bhr4nTr}cb(Xq1JAg$c{v#&A#X}BnsSu8{tree(5UC}{zx8$@ z_at@6{+JhnCzRfU)i3RVs`sy!Mb7#YiRH~b0BW0|%$ng1LoUax?&%PAi+Uk_eHk&` zxk{weB|BdJMYqkwShtP|+O1E+Ie3OEyu{MApkQgDrF%1NtUKOYuepWQBRA<7l|HNz z$4**wb@eP|Rkv8}I4jm*T(Db?Xu`TcJqB{66?3y!^$nf4q=oGby?^BO!3^y7xti<+ z{_~4(inG;UzehU|r#g%)P{@$mkka_ovSJrf%1 zYymuM==p5fHg2ApP|f8N03PCz1`?%y6P=yUd^7WG39Q>S8M6yd9Kiqjp ziB*$5%zme;;D3awkX_cF87v>5?}>4}H9Ei($5LmY-qSxh6Dg-2ibll>Z4=YQ@=tyX>2TRK0=PP|c$g z@R}JOX}Mnt{(WbcuS=}Ur*=&_6U57a(kJ&=o*Cmx;7Z%A6KT?U2{&G*5|$NwEN~VY zL!Cv5|M?v9ajgy9jFqMEeBms|f$pgdj>bc;#5{~_268upaYEduP)VK{`7HiXc#WpH zYV9a(xFHv__W$^RD`*OZ1eFcNbK}n~;dMs3BkCqx81Uyk^5eLCN|ZhC*2`Lx@OVif z-c3NiY#IZeTWeHM-M>C|g?F2e(d2zKY8Kv0pc-B?GGSgV{k^Ru zAeMy~2F4Z7C;vU^RXO~l&``{9B0p&RBUx(;=6uP&zr_!*#+k~62CufBijVj))508# zyCt{;Fzc1@j3d|fyURk3K0EAoZt})@gOU96xYSwdw&A8@0{IxLe}C?Sj2YlSjmMf8jOLpcWz6zu?gI4NlRnU~6MQl*F0BK|4`coj24SuG&v!k? z1T@YD@g|pS@v6WvuZsyh~C*#_V2ACupywLw9VK`0p8YUWDF&-V++ZwfEuu zlChe6LpWeuT%mTzk5rMS;;$v}Fl5hl0nPJ4>yx9`8>-_NS12+6J!6ko&;zst2H8V~ zk}l@55^Jvz2)T$sJxAD?WwkWJ6$W2D_*Dx zMhSj@4fzr7pk>yJJdM-&_Np6=Of1AyF#dZRt@9ZG4jBb)@fw0&Fu9n#|Ks--@UZ8E zM@5`=#3obJxsZb?-0!N;ItNK)~-qnd@8H?X5QvQX$9L z^>>5vzZcvFja+?;=cd4WhR<`w)ci6SKmC#ZUrGFx#Q#61Xl5( z*B6cH11-NyHP`^3*k#t6Jvr7N{x%}XU4E$hmvw+79lXAL>CG>b4CQxe{3G8kBeoOy zqopI-l-`Hjr^uhs`v+6>J~nX75d3wUlUDpK@as6g<5H~+m0hF6F-7nX|0@S{JxBG3 zzSSlMy$b*S`743H68I~DzY_Q>fxi;?D}lcf_}?diP_~D_T8^*x4--4Vx8;A|3jeO? zuLS-|;I9P!O5m>q{z~BglLYRc-lzx32C-AekDUGg^aX$a(_abvmB9aM3B21g+!?bw Tua`dyS>`EigX0B9?XLYlq7p8p literal 43774 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", "{=>}", "[=>]"] -icon: todo -filenames: [] -index: false -import_style: none -lsp_command: -lsp_language_id: -lsp_args: [] -import_keywords: [] -import_from_keywords: [] -import_member_keywords: [] -import_roots: [] -import_env: [] -import_extensions: [] -import_member_files: [] -import_strip_extensions: true -import_dot_modules: false -import_static_modules: [] -completions: [TODO, DONE, WAITING, BLOCKED, NOTE, TODAY] -line_close_pairs: [] -statement_required_enders: [] -statement_exempt_prefixes: ["#", "-", "*", "TODO", "DONE", "WAITING", "BLOCKED", "NOTE"] -statement_exempt_suffixes: [] -formatting: - enabled: false - scope: selection_or_block - profile: none - block_style: none - statement_style: newline - brace_style: attach - one_statement_per_line: false - space_before_block_opener: false - space_after_comma: true - space_after_control_keyword: true - space_around_binary_operators: true - space_around_logical_operators: true - logical_operators: [] - binary_operators: [] - unary_prefix_operators: [] - protected_scopes: [comment] - pointer_alignment: name - continuation_indent: 2 - max_column: 100 - wrap_after_comma: false - case_indent: 0 - preserve_blank_lines: true - max_blank_lines: 1 - collapse_empty_blocks: false -rules: - - name: comment - scope: comment - pattern: "#.*$" - color: "#6A9955" - - name: done - pattern: "^[[:space:]]*DONE\\b.*$" - color: "#57CC99" - bold: true - - name: todo - pattern: "^[[:space:]]*TODO\\b.*$" - color: "#F9C74F" - bold: true - - name: waiting - pattern: "^[[:space:]]*(WAITING|BLOCKED)\\b.*$" - color: "#FF6B6B" - bold: true - - name: note - pattern: "^[[:space:]]*NOTE\\b.*$" - color: "#89B4FA" - - name: tag - pattern: "@[A-Za-z0-9_-]+" - color: "#CBA6F7" - - name: date - pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}" - color: "#B5CEA8" diff --git a/data/plugins/example-plugin/templates/todo-project/files/README.md b/data/plugins/example-plugin/templates/todo-project/files/README.md deleted file mode 100644 index 2a9729c..0000000 --- a/data/plugins/example-plugin/templates/todo-project/files/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# ${project_name} - -Owner: ${owner_name} - -This project was created from the example Graptos plugin. - -Open `tasks.todo` to see the plugin syntax highlighting. diff --git a/data/plugins/example-plugin/templates/todo-project/files/tasks.todo b/data/plugins/example-plugin/templates/todo-project/files/tasks.todo deleted file mode 100644 index 253abd0..0000000 --- a/data/plugins/example-plugin/templates/todo-project/files/tasks.todo +++ /dev/null @@ -1,6 +0,0 @@ -# Example todo file - -TODO Review the plugin manifest @plugin -WAITING Try syntax reload after editing this file @test -DONE Confirm project template appears in Init Project -NOTE Dates such as 2026-07-31 are highlighted diff --git a/data/plugins/example-plugin/templates/todo-project/template.yaml b/data/plugins/example-plugin/templates/todo-project/template.yaml deleted file mode 100644 index 9e73bb3..0000000 --- a/data/plugins/example-plugin/templates/todo-project/template.yaml +++ /dev/null @@ -1,31 +0,0 @@ -version: 1 -id: plugin-todo-project -name: Plugin Todo Project -description: Example plugin template that creates a small todo project. -language: Todo List - -variables: - owner_name: - label: Owner name - type: text - default: ${project_name} - - initialize_git: - label: Initialize Git repository - type: boolean - default: false - -files: - - path: README.md - template: files/README.md - - - path: tasks.todo - template: files/tasks.todo - -actions: - - type: git-init - when: initialize_git - -open: - project: . - file: tasks.todo diff --git a/data/plugins/full-demo/plugin.yaml b/data/plugins/full-demo/plugin.yaml index a4cdeba..da4aeec 100644 --- a/data/plugins/full-demo/plugin.yaml +++ b/data/plugins/full-demo/plugin.yaml @@ -3,7 +3,7 @@ name: Full Demo Plugin version: 1.0 description: Shows every current permission in one manifest. graptos_api_version: 1 -enabled: true +enabled: false native: lib/libgraptos_full_demo.so permissions: diff --git a/data/plugins/permission-command/plugin.yaml b/data/plugins/permission-command/plugin.yaml index efb616b..507a5b1 100644 --- a/data/plugins/permission-command/plugin.yaml +++ b/data/plugins/permission-command/plugin.yaml @@ -3,7 +3,7 @@ name: External Command Demo version: 1.0 description: Demonstrates the command permission shape for future external process commands. graptos_api_version: 1 -enabled: true +enabled: false permissions: - command diff --git a/data/plugins/permission-editor-read/plugin.yaml b/data/plugins/permission-editor-read/plugin.yaml index ab4af74..4e60b3f 100644 --- a/data/plugins/permission-editor-read/plugin.yaml +++ b/data/plugins/permission-editor-read/plugin.yaml @@ -3,7 +3,7 @@ name: Editor Read Demo version: 1.0 description: Adds a line word-count action that only reads editor text. graptos_api_version: 1 -enabled: true +enabled: false permissions: - editor.read diff --git a/data/plugins/permission-editor-write/plugin.yaml b/data/plugins/permission-editor-write/plugin.yaml index 46e8151..3a55a33 100644 --- a/data/plugins/permission-editor-write/plugin.yaml +++ b/data/plugins/permission-editor-write/plugin.yaml @@ -3,7 +3,7 @@ name: Editor Write Demo version: 1.0 description: Adds an action that inserts a section banner above the clicked line. graptos_api_version: 1 -enabled: true +enabled: false permissions: - editor.write diff --git a/data/plugins/permission-git/plugin.yaml b/data/plugins/permission-git/plugin.yaml index 896bdbd..970aca1 100644 --- a/data/plugins/permission-git/plugin.yaml +++ b/data/plugins/permission-git/plugin.yaml @@ -3,7 +3,7 @@ name: Git Blame Demo version: 1.0 description: Adds Git blame to the right-click editor line menu. graptos_api_version: 1 -enabled: true +enabled: false permissions: - editor.read diff --git a/data/plugins/permission-native/native_demo.c b/data/plugins/permission-native/native_demo.c new file mode 100644 index 0000000..e6ebe0d --- /dev/null +++ b/data/plugins/permission-native/native_demo.c @@ -0,0 +1,44 @@ +/** + * @file native_demo.c + * @brief Example native Graptoς plugin. + * @details The manifest owns identity and permissions. This shared library + * owns the command label and behavior. + */ + +#include "plugin_api.h" + +/** + * @brief Show text for the active editor line. + * @param context Command context supplied by Graptoς. + * @param user_data Plugin data supplied during registration. + */ +static void native_demo_line_info(GraptosPluginCommandContext *context, + gpointer user_data) { + (void)user_data; + guint line = graptos_plugin_context_line(context); + char *text = graptos_plugin_context_line_text(context, line); + char *body = g_strdup_printf("Line: %u\n\n%s", line, text ? text : ""); + graptos_plugin_context_show_output(context, + "Native Demo", + "Native Line Info", + body); + g_free(body); + g_free(text); +} + +/** + * @brief Register this plugin with Graptoς. + * @param host Host capability object supplied by Graptoς. + * @return TRUE when registration succeeds. + */ +gboolean graptos_plugin_register(GraptosPluginHost *host) { + if (graptos_plugin_host_api_version(host) != GRAPTOS_PLUGIN_API_VERSION) { + return FALSE; + } + return graptos_plugin_host_register_editor_line_command(host, + "native-demo-line-info", + "Native Demo Line Info", + native_demo_line_info, + NULL, + NULL); +} diff --git a/data/plugins/permission-native/plugin.yaml b/data/plugins/permission-native/plugin.yaml index f7ff8ec..4249bb6 100644 --- a/data/plugins/permission-native/plugin.yaml +++ b/data/plugins/permission-native/plugin.yaml @@ -8,7 +8,5 @@ native: lib/libgraptos_native_demo.so permissions: - native - -contributes: - commands: - - native-demo-command + - editor.read + - ui diff --git a/data/plugins/permission-project-read/plugin.yaml b/data/plugins/permission-project-read/plugin.yaml index 28e3225..9274ad8 100644 --- a/data/plugins/permission-project-read/plugin.yaml +++ b/data/plugins/permission-project-read/plugin.yaml @@ -3,7 +3,7 @@ name: Project Read Demo version: 1.0 description: Shows a small project summary without writing files. graptos_api_version: 1 -enabled: true +enabled: false permissions: - project.read diff --git a/data/plugins/permission-project-write/plugin.yaml b/data/plugins/permission-project-write/plugin.yaml index dda48c4..371eb54 100644 --- a/data/plugins/permission-project-write/plugin.yaml +++ b/data/plugins/permission-project-write/plugin.yaml @@ -3,7 +3,7 @@ name: Project Write Demo version: 1.0 description: Demonstrates the project.write permission shape for future scaffolding commands. graptos_api_version: 1 -enabled: true +enabled: false permissions: - project.write diff --git a/data/plugins/permission-syntax/plugin.yaml b/data/plugins/permission-syntax/plugin.yaml index 2fe44aa..8136fd7 100644 --- a/data/plugins/permission-syntax/plugin.yaml +++ b/data/plugins/permission-syntax/plugin.yaml @@ -3,7 +3,7 @@ name: QML Syntax Demo version: 1.0 description: Contributes a small QML syntax definition. graptos_api_version: 1 -enabled: true +enabled: false permissions: - syntax diff --git a/data/plugins/permission-template/plugin.yaml b/data/plugins/permission-template/plugin.yaml index 40d90a5..490ea41 100644 --- a/data/plugins/permission-template/plugin.yaml +++ b/data/plugins/permission-template/plugin.yaml @@ -3,7 +3,7 @@ name: Template Demo version: 1.0 description: Contributes a tiny project template. graptos_api_version: 1 -enabled: true +enabled: false permissions: - template diff --git a/data/plugins/permission-terminal/plugin.yaml b/data/plugins/permission-terminal/plugin.yaml index f001861..4e0a35d 100644 --- a/data/plugins/permission-terminal/plugin.yaml +++ b/data/plugins/permission-terminal/plugin.yaml @@ -3,7 +3,7 @@ name: Terminal Demo version: 1.0 description: Demonstrates the terminal permission shape for future terminal-backed commands. graptos_api_version: 1 -enabled: true +enabled: false permissions: - terminal diff --git a/data/plugins/permission-ui/plugin.yaml b/data/plugins/permission-ui/plugin.yaml index d64b884..d316833 100644 --- a/data/plugins/permission-ui/plugin.yaml +++ b/data/plugins/permission-ui/plugin.yaml @@ -3,7 +3,7 @@ name: UI Demo version: 1.0 description: Adds a plugin information dialog through the editor context menu. graptos_api_version: 1 -enabled: true +enabled: false permissions: - ui diff --git a/data/plugins/run-active-file/plugin.yaml b/data/plugins/run-active-file/plugin.yaml new file mode 100644 index 0000000..8ddcb93 --- /dev/null +++ b/data/plugins/run-active-file/plugin.yaml @@ -0,0 +1,13 @@ +id: run-active-file +name: Run Active File +version: 1.0 +description: Runs the active file with a simple extension-based native command. +graptos_api_version: 1 +enabled: true +native: lib/libgraptos_run_active_file.so + +permissions: + - native + - editor.read + - command + - ui diff --git a/data/plugins/run-active-file/run_active_file.c b/data/plugins/run-active-file/run_active_file.c new file mode 100644 index 0000000..14fec08 --- /dev/null +++ b/data/plugins/run-active-file/run_active_file.c @@ -0,0 +1,167 @@ +/** + * @file run_active_file.c + * @brief Native plugin that runs the active file. + * @details The manifest owns trust and permissions. This shared library owns + * the command label, runner selection, process execution, and output. + */ + +#include "plugin_api.h" + +#include +#include + +/** + * @brief Runner command for one file type. + */ +typedef struct { + const char *extension; /**< File extension including the dot. */ + const char *program; /**< Program used to run the file. */ +} RunFileRunner; + +/** + * @brief Known extension runners. + */ +static const RunFileRunner RUNNERS[] = { + { ".py", "python3" }, + { ".js", "node" }, + { ".mjs", "node" }, + { ".sh", "bash" }, + { ".bash", "bash" }, + { ".rb", "ruby" }, + { ".pl", "perl" }, + { ".lua", "lua" }, + { ".php", "php" }, +}; + +/** + * @brief Return a runner for a path extension. + * @param path Active file path. + * @return Program name, or NULL when no extension runner is known. + */ +static const char *runner_for_path(const char *path) { + if (!path) return NULL; + for (guint i = 0u; i < G_N_ELEMENTS(RUNNERS); i++) { + if (g_str_has_suffix(path, RUNNERS[i].extension)) { + return RUNNERS[i].program; + } + } + return NULL; +} + +/** + * @brief Format process output for the result dialog. + * @param path File path that was run. + * @param program Program used to run it. + * @param status Process wait status. + * @param stdout_text Captured stdout. + * @param stderr_text Captured stderr. + * @return Newly allocated dialog body. + */ +static char *format_output(const char *path, + const char *program, + gint status, + const char *stdout_text, + const char *stderr_text) { + return g_strdup_printf("Command: %s %s\nExit status: %d\n\nstdout:\n%s\n\nstderr:\n%s", + program ? program : "", + path ? path : "", + status, + stdout_text && stdout_text[0] ? stdout_text : "(empty)", + stderr_text && stderr_text[0] ? stderr_text : "(empty)"); +} + +/** + * @brief Run the active file and show captured output. + * @param context Command context supplied by Graptoς. + * @param user_data Plugin data supplied during registration. + */ +static void run_active_file(GraptosPluginCommandContext *context, + gpointer user_data) { + (void)user_data; + char *path = graptos_plugin_context_file_path(context); + if (!path || !path[0]) { + graptos_plugin_context_show_output(context, + "Run Active File", + "No Saved File", + "Save the active tab before running it."); + g_free(path); + return; + } + + const char *program = runner_for_path(path); + gboolean executable = g_file_test(path, G_FILE_TEST_IS_EXECUTABLE); + if (!program && !executable) { + char *body = g_strdup_printf("No runner is configured for this file:\n%s\n\nSupported extensions: .py, .js, .mjs, .sh, .bash, .rb, .pl, .lua, .php\nExecutable files are run directly.", + path); + graptos_plugin_context_show_output(context, + "Run Active File", + "Unsupported File Type", + body); + g_free(body); + g_free(path); + return; + } + + char *argv_with_runner[] = { (char *)program, path, NULL }; + char *argv_executable[] = { path, NULL }; + char **argv = program ? argv_with_runner : argv_executable; + char *cwd = g_path_get_dirname(path); + char *stdout_text = NULL; + char *stderr_text = NULL; + GError *error = NULL; + gint status = 0; + gboolean ok = g_spawn_sync(cwd, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, + NULL, + &stdout_text, + &stderr_text, + &status, + &error); + if (!ok) { + char *body = g_strdup_printf("Failed to run:\n%s\n\n%s", + path, + error ? error->message : "Unknown error"); + graptos_plugin_context_show_output(context, + "Run Active File", + "Run Failed", + body); + g_free(body); + g_clear_error(&error); + } else { + char *body = format_output(path, + program ? program : path, + status, + stdout_text, + stderr_text); + graptos_plugin_context_show_output(context, + "Run Active File", + "Run Output", + body); + g_free(body); + } + + g_free(stdout_text); + g_free(stderr_text); + g_free(cwd); + g_free(path); +} + +/** + * @brief Register this plugin with Graptoς. + * @param host Host capability object supplied by Graptoς. + * @return TRUE when registration succeeds. + */ +gboolean graptos_plugin_register(GraptosPluginHost *host) { + if (graptos_plugin_host_api_version(host) != GRAPTOS_PLUGIN_API_VERSION) { + return FALSE; + } + return graptos_plugin_host_register_editor_line_command(host, + "run-active-file", + "Run Active File", + run_active_file, + NULL, + NULL); +} diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md new file mode 100644 index 0000000..b6cd7ac --- /dev/null +++ b/docs/PLUGINS.md @@ -0,0 +1,426 @@ +# Plugins + +Graptoς plugins are local folders with a `plugin.yaml` manifest. The plugin +layer is the boundary between Graptoς internals and extensions: plugins declare +what they contribute, and Graptoς decides how those contributions are loaded and +executed. + +Version 1 is intentionally small. It supports declarative plugin discovery, +syntax and template directories, command declarations, editor context-menu +items, and a bottom-bar Plugins tool panel. Native plugin loading has an ABI +stub, but Graptoς does not auto-load native code at startup. + +## Plugin locations + +Graptoς scans these plugin roots: + +```text +~/.local/share/graptos/plugins// +${prefix}/share/graptos/plugins// +data/plugins// +``` + +Use `data/plugins/` while developing inside the source tree. Installed builds +use `${prefix}/share/graptos/plugins/`. + +To disable plugin discovery for tests or debugging: + +```sh +GRAPTOS_DISABLE_PLUGINS=1 graptos +``` + +## Basic layout + +```text +data/plugins/my-plugin/ + plugin.yaml + syntax/ + templates/ +``` + +Only `plugin.yaml` is required. Other directories are needed only when the +manifest points at them. + +## Manifest + +Minimal manifest: + +```yaml +id: my-plugin +name: My Plugin +version: 1.0 +description: Short description. +graptos_api_version: 1 +enabled: true + +permissions: + - editor.read + +contributes: + commands: [] + menus: [] +``` + +Required fields: + +- `id`: stable lowercase id. Use lowercase letters, numbers, `.`, `_`, or `-`. +- `name`: visible plugin name. +- `version`: plugin version string. +- `graptos_api_version`: must match the current plugin API version, currently `1`. + +Optional fields: + +- `description`: short visible description. +- `enabled`: `true` or `false`; defaults to `true`. +- `native`: relative or absolute path to a shared library. Parsed, but not + auto-loaded during normal startup. +- `permissions`: declared capabilities. +- `contributes`: plugin contribution lists. + +## Permissions + +Current known permission names: + +```text +editor.read +editor.write +project.read +project.write +ui +syntax +theme +template +git +terminal +command +native +``` + +Graptoς currently enforces permission checks for built-in plugin command +execution where applicable. For example, the Git blame example requires `git`. +Full permission prompt UI is not implemented yet. + +## Contributions + +### Syntax directories + +```yaml +permissions: + - syntax + +contributes: + syntaxes: + - syntax +``` + +Each listed path is resolved relative to the plugin folder unless it is +absolute. The directory should contain Graptoς syntax YAML files. + +### Project template directories + +```yaml +permissions: + - template + +contributes: + templates: + - templates +``` + +Each listed directory should contain project-template subdirectories. Each +template subdirectory has the same `template.yaml` format used by built-in +project templates. + +### Editor commands + +Plugins can add editor-line actions. The same action appears in two places: + +- The editor right-click menu, where it uses the line under the pointer. +- The bottom-bar Plugins tool panel, where it uses the active cursor line. + +```yaml +permissions: + - editor.read + - git + +contributes: + commands: + - git-blame-line + menus: + - editor-line:Git Blame This Line:git-blame-line +``` + +Menu format: + +```text +editor-line:

-![graptos](data/logos/graptos-logo-dark.png) +drawing ### γραπτός: Grap-Tos; adj, written or inscribed