From 00923f81fe9fe76ed177f0cff236a0f450e121cb Mon Sep 17 00:00:00 2001 From: "A. Madani" Date: Thu, 16 Jul 2026 01:45:02 +0300 Subject: [PATCH] alert dialog and #9 --- src/app.h | 19 ++- src/app/app_file_search_actions.inc.c | 33 ++--- src/app/app_preference_actions.inc.c | 7 +- src/app/app_window_lifecycle.inc.c | 6 +- src/app/app_window_state.inc.c | 44 ++++++- src/app_layout.c | 20 +++ src/app_state.c | 1 + src/codex_client.c | 19 +-- src/codex_panel.c | 7 +- src/codex_review.c | 15 +-- src/config.c | 35 ++--- src/dialogs.c | 60 ++++++--- src/dialogs.h | 7 + .../editor_tab_highlight_scheduling.inc.c | 1 - src/editor_tab_io.c | 123 ++++++------------ src/editor_tab_latex.c | 119 +++++++---------- src/editor_tab_sourceview.c | 40 ++---- src/git/git_actions.inc.c | 81 ++++-------- src/git/git_core.inc.c | 18 +-- src/git/git_credentials.inc.c | 10 +- src/index/index_collect.inc.c | 9 +- src/project/project_context.inc.c | 50 +++---- src/project_search.c | 16 +-- src/syntax_loader.c | 18 +-- src/ui.c | 6 +- src/ui.h | 1 + src/ui/ui_base_css.inc.c | 1 + src/ui/ui_editor_css.inc.c | 5 + 28 files changed, 353 insertions(+), 418 deletions(-) diff --git a/src/app.h b/src/app.h index bcbdcb6..472bb44 100644 --- a/src/app.h +++ b/src/app.h @@ -11,7 +11,7 @@ #include "syntax.h" #ifndef APP_VERSION -#define APP_VERSION "0.16.3" +#define APP_VERSION "0.16.8" #endif /** @@ -33,6 +33,8 @@ typedef struct _EditorWindow { GHashTable *locked_paths; /**< Locked paths. */ GHashTable *git_file_status; /**< short Git marker. */ GtkWidget *status_label; /**< Status label. */ + char *status_error_title; /**< Latest status error title. */ + char *status_error_detail; /**< Latest status error detail. */ GtkWidget *syntax_combo; /**< Syntax combo. */ GtkWidget *indent_status_label; /**< Indent status label. */ GtkWidget *indent_dropdown; /**< Indent dropdown. */ @@ -81,6 +83,7 @@ typedef struct _EditorWindow { char *topbar_fg_color; /**< Topbar fg color. */ char *bottombar_bg_color; /**< Bottombar bg color. */ char *bottombar_fg_color; /**< Bottombar fg color. */ + char *status_error_color; /**< Status error color. */ char *button_bg_color; /**< Button bg color. */ char *button_fg_color; /**< Button fg color. */ char *button_hover_bg_color; /**< Button hover bg color. */ @@ -163,6 +166,20 @@ gboolean app_window_close_all_tabs(EditorWindow *win); * @brief App window set status. */ void app_window_set_status(EditorWindow *win, const char *text); +/** + * @brief App window set error status. + */ +void app_window_set_error_status(EditorWindow *win, + const char *short_text, + const char *detail); +/** + * @brief App window clear error status. + */ +void app_window_clear_error_status(EditorWindow *win); +/** + * @brief App window show status error. + */ +void app_window_show_status_error(EditorWindow *win); /** * @brief App window is file locked. */ diff --git a/src/app/app_file_search_actions.inc.c b/src/app/app_file_search_actions.inc.c index af51ec2..88a0c59 100644 --- a/src/app/app_file_search_actions.inc.c +++ b/src/app/app_file_search_actions.inc.c @@ -79,10 +79,9 @@ void action_open_folder(GtkWidget *widget, gpointer user_data) { * @brief Cleaf executable path. */ static char *cleaf_executable_path(void) { - GError *error = NULL; + g_autoptr(GError) error = NULL; char *path = g_file_read_link("/proc/self/exe", &error); if (path) return path; - g_clear_error(&error); const char *prgname = g_get_prgname(); if (prgname && prgname[0] != '\0') return g_strdup(prgname); @@ -97,37 +96,30 @@ void action_open_folder_new_instance(GtkWidget *widget, gpointer user_data) { EditorWindow *win = user_data; if (!win) return; - char *folder = cleaf_select_folder_dialog(app_window_gtk(win), - "Open Folder in New Instance"); + g_autofree char *folder = cleaf_select_folder_dialog(app_window_gtk(win), + "Open Folder in New Instance"); if (!folder) return; - char *exe = cleaf_executable_path(); + g_autofree char *exe = cleaf_executable_path(); if (!exe) { - dialog_error(app_window_gtk(win), "Could not launch instance", - "Cleaf could not determine its executable path."); - g_free(folder); + app_window_set_error_status(win, "Could not launch instance", + "Cleaf could not determine its executable path."); return; } char *argv[] = { exe, folder, NULL }; - GError *error = NULL; + g_autoptr(GError) error = NULL; gboolean ok = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error); if (!ok) { - dialog_error(app_window_gtk(win), "Could not launch instance", - error ? error->message : "Unknown launch error."); - g_clear_error(&error); + app_window_set_error_status(win, "Could not launch instance", + error ? error->message : "Unknown launch error."); } else { - char *display = g_filename_display_basename(folder); - char *msg = g_strdup_printf("Opened new instance for: %s", - display ? display : folder); + g_autofree char *display = g_filename_display_basename(folder); + g_autofree char *msg = g_strdup_printf("Opened new instance for: %s", + display ? display : folder); app_window_set_status(win, msg); - g_free(msg); - g_free(display); } - - g_free(exe); - g_free(folder); } @@ -260,4 +252,3 @@ void action_syntax_diagnostics(GtkWidget *widget, gpointer user_data) { g_free(diag); } - diff --git a/src/app/app_preference_actions.inc.c b/src/app/app_preference_actions.inc.c index a6fd1e3..d538eab 100644 --- a/src/app/app_preference_actions.inc.c +++ b/src/app/app_preference_actions.inc.c @@ -134,8 +134,8 @@ void choose_color_for_slot(EditorWindow *win, GdkRGBA parsed; if (!gdk_rgba_parse(&parsed, value)) { - dialog_error(app_window_gtk(win), "Invalid colour", - "Use a valid GTK colour, such as #1E1E1E."); + app_window_set_error_status(win, "Invalid colour", + "Use a valid GTK colour, such as #1E1E1E."); g_free(value); return; } @@ -234,6 +234,7 @@ void action_reset_all_backgrounds(GtkWidget *widget, gpointer user_data) { g_clear_pointer(&win->topbar_fg_color, g_free); g_clear_pointer(&win->bottombar_bg_color, g_free); g_clear_pointer(&win->bottombar_fg_color, g_free); + g_clear_pointer(&win->status_error_color, g_free); g_clear_pointer(&win->button_bg_color, g_free); g_clear_pointer(&win->button_fg_color, g_free); g_clear_pointer(&win->button_hover_bg_color, g_free); @@ -269,5 +270,3 @@ void action_reset_all_backgrounds(GtkWidget *widget, gpointer user_data) { apply_preferences_to_all_tabs(win); cleaf_config_save(win); } - - diff --git a/src/app/app_window_lifecycle.inc.c b/src/app/app_window_lifecycle.inc.c index c8cedae..10ef0ce 100644 --- a/src/app/app_window_lifecycle.inc.c +++ b/src/app/app_window_lifecycle.inc.c @@ -29,7 +29,7 @@ static void codex_status_changed(CodexClient *client, } else if (state == CODEX_CLIENT_FAILED) { char *message = g_strdup_printf("Codex unavailable: %s", detail ? detail : "unknown error"); - app_window_set_status(win, message); + app_window_set_error_status(win, "Codex unavailable", message); g_free(message); } } @@ -70,6 +70,7 @@ EditorWindow *app_window_new(GtkApplication *application) { win->topbar_fg_color = g_strdup("#d4d4d4"); win->bottombar_bg_color = g_strdup("#181a1f"); win->bottombar_fg_color = g_strdup("#d4d4d4"); + win->status_error_color = g_strdup("#ff6b6b"); win->button_bg_color = g_strdup("#181a1f"); win->button_fg_color = g_strdup("#d4d4d4"); win->button_hover_bg_color = g_strdup("#2a2e3d"); @@ -229,6 +230,8 @@ void app_window_free(EditorWindow *win) { if (win->project_roots) g_ptr_array_free(win->project_roots, 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); + g_free(win->status_error_title); + g_free(win->status_error_detail); g_free(win->editor_bg_color); g_free(win->editor_fg_color); g_free(win->editor_gutter_bg_color); @@ -246,6 +249,7 @@ void app_window_free(EditorWindow *win) { g_free(win->topbar_fg_color); g_free(win->bottombar_bg_color); g_free(win->bottombar_fg_color); + g_free(win->status_error_color); g_free(win->button_bg_color); g_free(win->button_fg_color); g_free(win->button_hover_bg_color); diff --git a/src/app/app_window_state.inc.c b/src/app/app_window_state.inc.c index 2e6ad69..eee6f0d 100644 --- a/src/app/app_window_state.inc.c +++ b/src/app/app_window_state.inc.c @@ -15,10 +15,52 @@ GtkWindow *app_window_gtk(EditorWindow *win) { void app_window_set_status(EditorWindow *win, const char *text) { if (!win || !win->status_label) return; + app_window_clear_error_status(win); // Empty string keeps the status label valid without showing stale text. gtk_label_set_text(GTK_LABEL(win->status_label), text ? text : ""); } +/** + * @brief App window clear error status. + */ +void app_window_clear_error_status(EditorWindow *win) { + if (!win) return; + g_clear_pointer(&win->status_error_title, g_free); + g_clear_pointer(&win->status_error_detail, g_free); + if (win->status_label) { + gtk_widget_remove_css_class(win->status_label, "cleaf-status-error"); + gtk_widget_set_tooltip_text(win->status_label, NULL); + } +} + +/** + * @brief App window set error status. + */ +void app_window_set_error_status(EditorWindow *win, + const char *short_text, + const char *detail) { + if (!win || !win->status_label) return; + + g_clear_pointer(&win->status_error_title, g_free); + g_clear_pointer(&win->status_error_detail, g_free); + win->status_error_title = g_strdup(short_text && short_text[0] ? short_text : "Error"); + win->status_error_detail = g_strdup(detail && detail[0] ? detail : win->status_error_title); + + gtk_label_set_text(GTK_LABEL(win->status_label), win->status_error_title); + gtk_widget_add_css_class(win->status_label, "cleaf-status-error"); + gtk_widget_set_tooltip_text(win->status_label, "Click to show error details"); +} + +/** + * @brief App window show status error. + */ +void app_window_show_status_error(EditorWindow *win) { + if (!win || !win->status_error_detail) return; + dialog_error(app_window_gtk(win), + win->status_error_title ? win->status_error_title : "Error", + win->status_error_detail); +} + /** * @brief Canonical or dup. @@ -213,4 +255,4 @@ void app_window_note_path_renamed(EditorWindow *win, g_free(old_canonical); g_free(new_canonical); -} \ No newline at end of file +} diff --git a/src/app_layout.c b/src/app_layout.c index afdcea0..d3bbff2 100644 --- a/src/app_layout.c +++ b/src/app_layout.c @@ -483,6 +483,21 @@ GtkWidget *build_tool_panel(EditorWindow *win) { return win->tool_revealer; } +/** + * @brief On status label clicked. + */ +static void on_status_label_clicked(GtkGestureClick *gesture, + int n_press, + double x, + double y, + gpointer user_data) { + (void)gesture; + (void)n_press; + (void)x; + (void)y; + app_window_show_status_error(user_data); +} + /** * @brief Build bottom bar. */ @@ -495,6 +510,11 @@ GtkWidget *build_bottom_bar(EditorWindow *win) { gtk_label_set_ellipsize(GTK_LABEL(win->status_label), PANGO_ELLIPSIZE_END); gtk_widget_add_css_class(win->status_label, "cleaf-status"); gtk_widget_set_size_request(win->status_label, 180, -1); + GtkGesture *status_click = gtk_gesture_click_new(); + g_signal_connect(status_click, "pressed", + G_CALLBACK(on_status_label_clicked), win); + gtk_widget_add_controller(win->status_label, + GTK_EVENT_CONTROLLER(status_click)); gtk_box_append(GTK_BOX(bottom), win->status_label); gtk_box_append(GTK_BOX(bottom), tool_button_new("Find", "Open find panel (Ctrl+F)", G_CALLBACK(action_show_find), win)); diff --git a/src/app_state.c b/src/app_state.c index 1216122..1b752cd 100644 --- a/src/app_state.c +++ b/src/app_state.c @@ -26,6 +26,7 @@ void app_window_apply_css(EditorWindow *win) { win->tab_active_fg_color, win->topbar_bg_color, win->topbar_fg_color, win->bottombar_bg_color, win->bottombar_fg_color, + win->status_error_color, win->button_bg_color, win->button_fg_color, win->button_hover_bg_color, win->button_active_bg_color, diff --git a/src/codex_client.c b/src/codex_client.c index 7876737..4f37b5e 100644 --- a/src/codex_client.c +++ b/src/codex_client.c @@ -99,7 +99,7 @@ static gboolean codex_client_write(CodexClient *client, char *message) { g_free(message); return FALSE; /**< False. */ } - GError *error = NULL; + g_autoptr(GError) error = NULL; gboolean written = g_output_stream_write_all(client->input, message, strlen(message), @@ -110,7 +110,6 @@ static gboolean codex_client_write(CodexClient *client, char *message) { if (!written) { codex_client_set_state(client, CODEX_CLIENT_FAILED, error ? error->message : "write failed"); - g_clear_error(&error); } return written; /**< Written. */ } @@ -511,16 +510,15 @@ static void codex_client_line_ready(GObject *source, GAsyncResult *result, gpointer user_data) { CodexClient *client = user_data; - GError *error = NULL; + g_autoptr(GError) error = NULL; gsize length = 0u; - char *line = g_data_input_stream_read_line_finish(G_DATA_INPUT_STREAM(source), - result, &length, &error); + g_autofree char *line = g_data_input_stream_read_line_finish(G_DATA_INPUT_STREAM(source), + result, &length, &error); if (!line) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { codex_client_set_state(client, CODEX_CLIENT_FAILED, error ? error->message : "Codex exited"); } - g_clear_error(&error); codex_client_unref(client); return; } @@ -529,10 +527,7 @@ static void codex_client_line_ready(GObject *source, if (codex_protocol_parse(line, &root, &error)) { codex_client_handle_message(client, root); json_node_free(root); - } else { - g_clear_error(&error); } - g_free(line); if (!client->disposing) codex_client_read_next(client); codex_client_unref(client); } @@ -683,19 +678,17 @@ void codex_client_start(CodexClient *client, const char *cwd) { client->cwd = g_strdup(cwd ? cwd : "."); client->cancellable = g_cancellable_new(); - GError *error = NULL; - GSubprocessLauncher *launcher = g_subprocess_launcher_new( + g_autoptr(GError) error = NULL; + g_autoptr(GSubprocessLauncher) launcher = g_subprocess_launcher_new( G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_SILENCE); client->process = g_subprocess_launcher_spawn(launcher, &error, "codex", "app-server", "--stdio", NULL); - g_object_unref(launcher); if (!client->process) { codex_client_set_state(client, CODEX_CLIENT_FAILED, error ? error->message : "could not start Codex"); - g_clear_error(&error); return; } client->input = g_object_ref(g_subprocess_get_stdin_pipe(client->process)); diff --git a/src/codex_panel.c b/src/codex_panel.c index ff4be48..24577be 100644 --- a/src/codex_panel.c +++ b/src/codex_panel.c @@ -605,16 +605,15 @@ static void panel_revert_diff(GtkWidget *widget, gpointer user_data) { "Reverse only the changes recorded for this Codex turn?")) { return; } - GError *error = NULL; + g_autoptr(GError) error = NULL; if (codex_review_revert(panel->win, panel->turn_diff, &error)) { g_clear_pointer(&panel->turn_diff, g_free); gtk_widget_set_visible(panel->review_box, FALSE); app_window_set_status(panel->win, "Codex turn reverted"); } else { - dialog_error(app_window_gtk(panel->win), "Could not revert Codex turn", - error ? error->message : "The files changed after this turn."); + app_window_set_error_status(panel->win, "Could not revert Codex turn", + error ? error->message : "The files changed after this turn."); } - g_clear_error(&error); } /** diff --git a/src/codex_review.c b/src/codex_review.c index c03498c..165fe0b 100644 --- a/src/codex_review.c +++ b/src/codex_review.c @@ -52,20 +52,19 @@ static gboolean run_reverse_apply(const char *cwd, const char *diff, gboolean check, GError **error) { - GSubprocessLauncher *launcher = g_subprocess_launcher_new( + g_autoptr(GSubprocessLauncher) launcher = g_subprocess_launcher_new( G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE); g_subprocess_launcher_set_cwd(launcher, cwd); - GSubprocess *process = check + g_autoptr(GSubprocess) process = check ? g_subprocess_launcher_spawn(launcher, error, "git", "apply", "--reverse", "--check", NULL) : g_subprocess_launcher_spawn(launcher, error, "git", "apply", "--reverse", NULL); - g_object_unref(launcher); if (!process) return FALSE; - char *stdout_text = NULL; - char *stderr_text = NULL; + g_autofree char *stdout_text = NULL; + g_autofree char *stderr_text = NULL; gboolean communicated = g_subprocess_communicate_utf8(process, diff, NULL, &stdout_text, &stderr_text, error); @@ -75,9 +74,6 @@ static gboolean run_reverse_apply(const char *cwd, stderr_text && stderr_text[0] ? stderr_text : "reverse patch failed"); } - g_free(stdout_text); - g_free(stderr_text); - g_object_unref(process); return success; } @@ -88,11 +84,10 @@ gboolean codex_review_revert(EditorWindow *win, const char *diff, GError **error) { if (!win || !diff || diff[0] == '\0') return FALSE; - char *fallback = win->project_root ? NULL : g_get_current_dir(); + g_autofree char *fallback = win->project_root ? NULL : g_get_current_dir(); const char *cwd = win->project_root ? win->project_root : fallback; gboolean valid = run_reverse_apply(cwd, diff, TRUE, error); gboolean reverted = valid && run_reverse_apply(cwd, diff, FALSE, error); - g_free(fallback); if (reverted) cleaf_git_refresh_and_rebuild(win); return reverted; } diff --git a/src/config.c b/src/config.c index e7909a4..6cb20ab 100644 --- a/src/config.c +++ b/src/config.c @@ -12,10 +12,9 @@ * @brief Parse bool. */ static gboolean parse_bool(GKeyFile *key_file, const char *key, gboolean fallback) { - GError *error = NULL; + g_autoptr(GError) error = NULL; gboolean value = g_key_file_get_boolean(key_file, "Editor", key, &error); if (error) { - g_clear_error(&error); return fallback; } return value; @@ -25,10 +24,9 @@ static gboolean parse_bool(GKeyFile *key_file, const char *key, gboolean fallbac * @brief Parse uint. */ static guint parse_uint(GKeyFile *key_file, const char *key, guint fallback, guint min_value, guint max_value) { - GError *error = NULL; + g_autoptr(GError) error = NULL; gint value = g_key_file_get_integer(key_file, "Editor", key, &error); if (error) { - g_clear_error(&error); return fallback; } if (value < (gint)min_value) return min_value; @@ -72,19 +70,15 @@ char *cleaf_config_path(void) { */ void cleaf_config_load(EditorWindow *win) { if (!win) return; - char *path = cleaf_config_path(); + g_autofree char *path = cleaf_config_path(); if (!path) return; if (!g_file_test(path, G_FILE_TEST_EXISTS)) { - g_free(path); return; } - GKeyFile *key_file = g_key_file_new(); - GError *error = NULL; + g_autoptr(GKeyFile) key_file = g_key_file_new(); + g_autoptr(GError) error = NULL; if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, &error)) { - g_clear_error(&error); - g_key_file_unref(key_file); - g_free(path); return; } @@ -105,6 +99,7 @@ void cleaf_config_load(EditorWindow *win) { load_color(key_file, "topbar_foreground_color", &win->topbar_fg_color); load_color(key_file, "bottombar_background_color", &win->bottombar_bg_color); load_color(key_file, "bottombar_foreground_color", &win->bottombar_fg_color); + load_color(key_file, "status_error_color", &win->status_error_color); load_color(key_file, "button_background_color", &win->button_bg_color); load_color(key_file, "button_foreground_color", &win->button_fg_color); load_color(key_file, "button_hover_background_color", &win->button_hover_bg_color); @@ -152,7 +147,6 @@ void cleaf_config_load(EditorWindow *win) { win->use_gtksourceview_highlighting = TRUE; win->use_yaml_style_overrides = parse_bool(key_file, "use_yaml_style_overrides", win->use_yaml_style_overrides); - g_key_file_unref(key_file); if (!win->codex_preview_bg_color) { win->codex_preview_bg_color = g_strdup(win->editor_bg_color); } @@ -162,7 +156,6 @@ void cleaf_config_load(EditorWindow *win) { if (!win->codex_prompt_bg_color) { win->codex_prompt_bg_color = g_strdup(win->editor_bg_color); } - g_free(path); } /** @@ -170,20 +163,17 @@ void cleaf_config_load(EditorWindow *win) { */ void cleaf_config_save(EditorWindow *win) { if (!win) return; - char *path = cleaf_config_path(); + g_autofree char *path = cleaf_config_path(); if (!path) return; - char *dir = g_path_get_dirname(path); + g_autofree char *dir = g_path_get_dirname(path); if (!dir) { - g_free(path); return; } if (g_mkdir_with_parents(dir, 0700) != 0) { - g_free(dir); - g_free(path); return; } - GKeyFile *key_file = g_key_file_new(); + g_autoptr(GKeyFile) key_file = g_key_file_new(); save_color(key_file, "background_color", win->editor_bg_color); save_color(key_file, "foreground_color", win->editor_fg_color); save_color(key_file, "editor_gutter_background_color", win->editor_gutter_bg_color); @@ -201,6 +191,7 @@ void cleaf_config_save(EditorWindow *win) { save_color(key_file, "topbar_foreground_color", win->topbar_fg_color); save_color(key_file, "bottombar_background_color", win->bottombar_bg_color); save_color(key_file, "bottombar_foreground_color", win->bottombar_fg_color); + save_color(key_file, "status_error_color", win->status_error_color); save_color(key_file, "button_background_color", win->button_bg_color); save_color(key_file, "button_foreground_color", win->button_fg_color); save_color(key_file, "button_hover_background_color", win->button_hover_bg_color); @@ -248,12 +239,8 @@ void cleaf_config_save(EditorWindow *win) { g_key_file_set_boolean(key_file, "Editor", "use_yaml_style_overrides", win->use_yaml_style_overrides); gsize length = 0u; - char *data = g_key_file_to_data(key_file, &length, NULL); + g_autofree char *data = g_key_file_to_data(key_file, &length, NULL); if (data) { (void)g_file_set_contents(path, data, (gssize)length, NULL); - g_free(data); } - g_key_file_unref(key_file); - g_free(dir); - g_free(path); } diff --git a/src/dialogs.c b/src/dialogs.c index 4ff12fc..b93f282 100644 --- a/src/dialogs.c +++ b/src/dialogs.c @@ -17,8 +17,12 @@ static GtkWidget *dialog_window_new(GtkWindow *parent, gtk_widget_add_css_class(window, "cleaf-window"); gtk_window_set_title(GTK_WINDOW(window), title ? title : "Cleaf"); gtk_window_set_default_size(GTK_WINDOW(window), width, height); + gtk_window_set_resizable(GTK_WINDOW(window), TRUE); gtk_window_set_modal(GTK_WINDOW(window), TRUE); - if (parent) gtk_window_set_transient_for(GTK_WINDOW(window), parent); + if (parent) { + gtk_window_set_transient_for(GTK_WINDOW(window), parent); + gtk_window_set_destroy_with_parent(GTK_WINDOW(window), TRUE); + } return window; } @@ -54,25 +58,43 @@ static GtkWidget *button_row_new(void) { } /** - * @brief Show message. + * @brief Dialog output. */ -static void show_message(GtkWindow *parent, - const char *title, - const char *primary, - const char *detail) { - GtkWidget *window = dialog_window_new(parent, title, 420, 160); +void dialog_output(GtkWindow *parent, + const char *title, + const char *heading, + const char *body) { + GtkWidget *window = dialog_window_new(parent, title ? title : "Cleaf", + 760, 520); GtkWidget *box = dialog_content_new(window); - gtk_box_append(GTK_BOX(box), dialog_label_new(primary, TRUE)); - if (detail && detail[0] != '\0') { - gtk_box_append(GTK_BOX(box), dialog_label_new(detail, FALSE)); - } + + GtkWidget *label = dialog_label_new(heading ? heading : title, TRUE); + gtk_box_append(GTK_BOX(box), label); + + GtkTextBuffer *buffer = gtk_text_buffer_new(NULL); + gtk_text_buffer_set_text(buffer, body ? body : "", -1); + GtkWidget *view = gtk_text_view_new_with_buffer(buffer); + g_object_unref(buffer); + gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); + gtk_text_view_set_monospace(GTK_TEXT_VIEW(view), TRUE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_NONE); + + GtkWidget *scrolled = gtk_scrolled_window_new(); + gtk_widget_set_vexpand(scrolled, TRUE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), view); + gtk_box_append(GTK_BOX(box), scrolled); GtkWidget *row = button_row_new(); - GtkWidget *ok = cleaf_flat_button_new("Close", NULL, - G_CALLBACK(cleaf_modal_window_respond), - GINT_TO_POINTER(GTK_RESPONSE_CLOSE)); - gtk_box_append(GTK_BOX(row), ok); + GtkWidget *close = cleaf_flat_button_new("Close", NULL, + G_CALLBACK(cleaf_modal_window_respond), + GINT_TO_POINTER(GTK_RESPONSE_CLOSE)); + gtk_box_append(GTK_BOX(row), close); gtk_box_append(GTK_BOX(box), row); + (void)cleaf_modal_window_run(GTK_WINDOW(window), GTK_RESPONSE_CLOSE); cleaf_widget_destroy(window); } @@ -81,15 +103,17 @@ static void show_message(GtkWindow *parent, * @brief Dialog error. */ void dialog_error(GtkWindow *parent, const char *primary, const char *detail) { - show_message(parent, "Error", primary ? primary : "Error", detail); + dialog_output(parent, "Error", primary ? primary : "Error", detail); } /** * @brief Dialog info. */ void dialog_info(GtkWindow *parent, const char *primary, const char *detail) { - show_message(parent, "Information", - primary ? primary : "Information", detail); + dialog_output(parent, + "Information", + primary ? primary : "Information", + detail && detail[0] ? detail : primary); } /** diff --git a/src/dialogs.h b/src/dialogs.h index 811f6c1..c208951 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -8,6 +8,13 @@ #include +/** + * @brief Dialog output. + */ +void dialog_output(GtkWindow *parent, + const char *title, + const char *heading, + const char *body); /** * @brief Dialog error. */ diff --git a/src/editor/editor_tab_highlight_scheduling.inc.c b/src/editor/editor_tab_highlight_scheduling.inc.c index ac99f5d..151b713 100644 --- a/src/editor/editor_tab_highlight_scheduling.inc.c +++ b/src/editor/editor_tab_highlight_scheduling.inc.c @@ -6,7 +6,6 @@ void editor_tab_apply_highlight(EditorTab *tab) { if (!tab || !tab->buffer) return; /* Cleaf no longer applies regex YAML highlighting to the editor buffer. - * This change came when moving to GtkSourceView. * Highlighting is owned by GtkSourceView; YAML rules only generate optional * GtkSourceView style-scheme overrides when the user enables them. */ diff --git a/src/editor_tab_io.c b/src/editor_tab_io.c index 61ba480..785994b 100644 --- a/src/editor_tab_io.c +++ b/src/editor_tab_io.c @@ -36,18 +36,15 @@ gboolean write_all_fd(int fd, const char *data, gsize len) { gboolean write_text_atomic(const char *path, const char *text, GError **error) { if (!path || path[0] == '\0' || !text) return FALSE; - char *dir = g_path_get_dirname(path); - if (!dir) { - g_free(dir); - return FALSE; - } + g_autofree char *dir = g_path_get_dirname(path); + if (!dir) return FALSE; struct stat st; mode_t mode = 0644u; if (stat(path, &st) == 0) mode = (mode_t)(st.st_mode & 0777u); gboolean ok = FALSE; - char *tmp_path = NULL; + g_autofree char *tmp_path = NULL; int fd = -1; for (guint attempt = 0u; attempt < 100u; attempt++) { g_free(tmp_path); @@ -59,8 +56,6 @@ gboolean write_text_atomic(const char *path, const char *text, GError **error) { if (fd < 0) { g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errno), "Could not create temporary save file: %s", g_strerror(errno)); - g_free(tmp_path); - g_free(dir); return FALSE; } @@ -87,8 +82,6 @@ gboolean write_text_atomic(const char *path, const char *text, GError **error) { ok = TRUE; } - g_free(tmp_path); - g_free(dir); return ok; } @@ -100,22 +93,18 @@ char *autosave_path_for_tab(EditorTab *tab) { if (!tab) return NULL; const char *cache = g_get_user_cache_dir(); if (!cache) return NULL; - char *dir = g_build_filename(cache, "cleaf", "autosave", NULL); + g_autofree char *dir = g_build_filename(cache, "cleaf", "autosave", NULL); if (g_mkdir_with_parents(dir, 0700) != 0) { - g_free(dir); return NULL; } - char *name = NULL; + g_autofree char *name = NULL; if (tab->file_path) { guint hash = g_str_hash(tab->file_path); name = g_strdup_printf("%08x.autosave", hash); } else { name = g_strdup_printf("untitled-%p.autosave", (void *)tab); } - char *path = g_build_filename(dir, name, NULL); - g_free(name); - g_free(dir); - return path; + return g_build_filename(dir, name, NULL); } @@ -125,11 +114,10 @@ char *autosave_path_for_tab(EditorTab *tab) { */ static void cleanup_legacy_tilde_backup(const char *path) { if (!path || path[0] == '\0') return; - char *legacy = g_strdup_printf("%s~", path); + g_autofree char *legacy = g_strdup_printf("%s~", path); if (legacy && g_file_test(legacy, G_FILE_TEST_IS_REGULAR)) { (void)g_unlink(legacy); } - g_free(legacy); } /** @@ -137,29 +125,28 @@ static void cleanup_legacy_tilde_backup(const char *path) { */ gboolean editor_tab_load_file(EditorTab *tab, const char *path) { if (!tab || !path) return FALSE; - char *contents = NULL; + g_autofree char *contents = NULL; gsize length = 0; - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!g_file_get_contents(path, &contents, &length, &error)) { - dialog_error(app_window_gtk(tab->win), "Could not open file", error ? error->message : path); - g_clear_error(&error); + app_window_set_error_status(tab->win, "Could not open file", + error ? error->message : path); return FALSE; } if (length > (gsize)G_MAXINT) { - g_free(contents); - dialog_error(app_window_gtk(tab->win), "File too large", "This build refuses to load files larger than GTK's text-buffer insertion limit."); + app_window_set_error_status(tab->win, "File too large", + "This build refuses to load files larger than GTK's text-buffer insertion limit."); return FALSE; } if (!g_utf8_validate(contents, (gssize)length, NULL)) { - g_free(contents); - dialog_error(app_window_gtk(tab->win), "Unsupported encoding", "Cleaf currently opens UTF-8 text files only. Convert the file to UTF-8 first."); + app_window_set_error_status(tab->win, "Unsupported encoding", + "Cleaf currently opens UTF-8 text files only. Convert the file to UTF-8 first."); return FALSE; } tab->applying_change = TRUE; gtk_text_buffer_set_text(tab->buffer, contents, (gint)length); tab->applying_change = FALSE; - g_free(contents); g_free(tab->file_path); tab->file_path = g_canonicalize_filename(path, NULL); cleanup_legacy_tilde_backup(tab->file_path); @@ -185,25 +172,19 @@ gboolean editor_tab_load_file(EditorTab *tab, const char *path) { gboolean write_backup_if_needed(EditorTab *tab, const char *path) { if (!tab || !tab->backup_enabled || !path || !g_file_test(path, G_FILE_TEST_EXISTS)) return TRUE; - char *old = NULL; + g_autofree char *old = NULL; gsize len = 0; - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!g_file_get_contents(path, &old, &len, &error)) { - g_clear_error(&error); return TRUE; } - char *dir = g_path_get_dirname(path); - char *base = g_path_get_basename(path); - char *backup_dir = g_build_filename(dir, ".cleaf-backups", NULL); + g_autofree char *dir = g_path_get_dirname(path); + g_autofree char *base = g_path_get_basename(path); + g_autofree char *backup_dir = g_build_filename(dir, ".cleaf-backups", NULL); if (g_mkdir_with_parents(backup_dir, 0700) != 0) { - char *msg = g_strdup_printf("Could not create backup folder %s: %s", backup_dir, g_strerror(errno)); - dialog_error(app_window_gtk(tab->win), "Backup failed", msg); - g_free(msg); - g_free(backup_dir); - g_free(base); - g_free(dir); - g_free(old); + g_autofree char *msg = g_strdup_printf("Could not create backup folder %s: %s", backup_dir, g_strerror(errno)); + app_window_set_error_status(tab->win, "Backup failed", msg); return FALSE; } @@ -214,23 +195,15 @@ gboolean write_backup_if_needed(EditorTab *tab, const char *path) { if (tmv) strftime(stamp, sizeof(stamp), "%Y%m%d-%H%M%S", tmv); else g_snprintf(stamp, sizeof(stamp), "backup"); - char *backup_name = g_strdup_printf("%s.%s~", base ? base : "file", stamp); - char *backup_path = g_build_filename(backup_dir, backup_name, NULL); + g_autofree char *backup_name = g_strdup_printf("%s.%s~", base ? base : "file", stamp); + g_autofree char *backup_path = g_build_filename(backup_dir, backup_name, NULL); (void)len; gboolean ok = write_text_atomic(backup_path, old, &error); if (!ok) { - char *msg = g_strdup_printf("Could not write backup %s: %s", backup_path, error ? error->message : "unknown error"); - dialog_error(app_window_gtk(tab->win), "Backup failed", msg); - g_free(msg); - g_clear_error(&error); + g_autofree char *msg = g_strdup_printf("Could not write backup %s: %s", backup_path, error ? error->message : "unknown error"); + app_window_set_error_status(tab->win, "Backup failed", msg); } - g_free(backup_path); - g_free(backup_name); - g_free(backup_dir); - g_free(base); - g_free(dir); - g_free(old); return ok; } @@ -246,26 +219,22 @@ gboolean save_to_path(EditorTab *tab, const char *path) { * operation that can free or replace tab->file_path; otherwise the tab * title can be rebuilt from freed memory and appear as mojibake. */ - char *stable_path = g_strdup(path); + g_autofree char *stable_path = g_strdup(path); if (!stable_path) return FALSE; if (!write_backup_if_needed(tab, stable_path)) { - g_free(stable_path); return FALSE; } - char *text = buffer_text(tab); - GError *error = NULL; + g_autofree char *text = buffer_text(tab); + g_autoptr(GError) error = NULL; gboolean ok = write_text_atomic(stable_path, text, &error); - g_free(text); if (!ok) { - dialog_error(app_window_gtk(tab->win), "Could not save file", error ? error->message : stable_path); - g_clear_error(&error); - g_free(stable_path); + app_window_set_error_status(tab->win, "Could not save file", + error ? error->message : stable_path); return FALSE; } g_free(tab->file_path); tab->file_path = g_canonicalize_filename(stable_path, NULL); - g_free(stable_path); cleanup_legacy_tilde_backup(tab->file_path); tab->modified = FALSE; tab->manual_syntax_override = FALSE; @@ -294,13 +263,11 @@ gboolean editor_tab_save(EditorTab *tab, gboolean force_dialog) { return save_to_path(tab, tab->file_path); } - char *filename = cleaf_save_file_dialog(app_window_gtk(tab->win), - "Save File"); + g_autofree char *filename = cleaf_save_file_dialog(app_window_gtk(tab->win), + "Save File"); if (!filename) return FALSE; - gboolean ok = save_to_path(tab, filename); - g_free(filename); - return ok; + return save_to_path(tab, filename); } @@ -319,8 +286,8 @@ static GtkWidget *close_dialog_button(const char *label, int response) { gboolean editor_tab_confirm_close(EditorTab *tab) { if (!tab || !tab->modified) return TRUE; - char *base = editor_tab_basename(tab); - char *title = g_strdup_printf("Save changes to %s?", base); + g_autofree char *base = editor_tab_basename(tab); + g_autofree char *title = g_strdup_printf("Save changes to %s?", base); GtkWidget *dialog = gtk_window_new(); gtk_widget_add_css_class(dialog, "cleaf-window"); @@ -358,8 +325,6 @@ gboolean editor_tab_confirm_close(EditorTab *tab) { int response = cleaf_modal_window_run(GTK_WINDOW(dialog), GTK_RESPONSE_CANCEL); cleaf_widget_destroy(dialog); - g_free(title); - g_free(base); if (response == GTK_RESPONSE_ACCEPT) return editor_tab_save(tab, FALSE); if (response == GTK_RESPONSE_REJECT) return TRUE; @@ -374,21 +339,17 @@ gboolean editor_tab_auto_save(EditorTab *tab) { if (!tab || !tab->modified) return TRUE; if (tab->file_path) return save_to_path(tab, tab->file_path); - char *path = autosave_path_for_tab(tab); + g_autofree char *path = autosave_path_for_tab(tab); if (!path) return FALSE; - char *text = buffer_text(tab); - GError *error = NULL; + g_autofree char *text = buffer_text(tab); + g_autoptr(GError) error = NULL; gboolean ok = write_text_atomic(path, text, &error); if (!ok) { - dialog_error(app_window_gtk(tab->win), "Auto-save failed", error ? error->message : path); - g_clear_error(&error); + app_window_set_error_status(tab->win, "Auto-save failed", + error ? error->message : path); } else if (tab->win) { - char *status = g_strdup_printf("Auto-saved unsaved buffer to %s", path); + g_autofree char *status = g_strdup_printf("Auto-saved unsaved buffer to %s", path); app_window_set_status(tab->win, status); - g_free(status); } - g_free(text); - g_free(path); return ok; } - diff --git a/src/editor_tab_latex.c b/src/editor_tab_latex.c index f2b03a9..2d7e683 100644 --- a/src/editor_tab_latex.c +++ b/src/editor_tab_latex.c @@ -53,14 +53,13 @@ static char *find_latex_command(void) { * @brief Basename without suffix. */ static char *basename_without_suffix(const char *path) { - char *base = g_path_get_basename(path ? path : "document.tex"); + g_autofree char *base = g_path_get_basename(path ? path : "document.tex"); char *dot = base ? strrchr(base, '.') : NULL; if (dot && dot != base) *dot = '\0'; if (!base || base[0] == '\0') { - g_free(base); return g_strdup("document"); } - return base; + return g_steal_pointer(&base); } /** @@ -75,31 +74,25 @@ static gboolean ensure_saved_source(EditorTab *tab, char **source_path_out) { return *source_path_out != NULL; } - char *text = buffer_text(tab); + g_autofree char *text = buffer_text(tab); if (!text) return FALSE; - GError *error = NULL; - char *tmp_dir = g_dir_make_tmp("cleaf-latex-XXXXXX", &error); + g_autoptr(GError) error = NULL; + g_autofree char *tmp_dir = g_dir_make_tmp("cleaf-latex-XXXXXX", &error); if (!tmp_dir) { - dialog_error(app_window_gtk(tab->win), "Could not create temp dir", - error ? error->message : "Unknown error"); - g_clear_error(&error); - g_free(text); + app_window_set_error_status(tab->win, "Could not create temp dir", + error ? error->message : "Unknown error"); return FALSE; } char *source = g_build_filename(tmp_dir, "document.tex", NULL); - g_free(tmp_dir); if (!write_text_atomic(source, text, &error)) { - dialog_error(app_window_gtk(tab->win), "Could not write temp TeX file", - error ? error->message : "Unknown error"); - g_clear_error(&error); + app_window_set_error_status(tab->win, "Could not write temp TeX file", + error ? error->message : "Unknown error"); g_free(source); - g_free(text); return FALSE; } - g_free(text); *source_path_out = source; return TRUE; } @@ -108,11 +101,10 @@ static gboolean ensure_saved_source(EditorTab *tab, char **source_path_out) { * @brief Build output dir for source. */ static char *build_output_dir_for_source(const char *source_path) { - char *dir = g_path_get_dirname(source_path); + g_autofree char *dir = g_path_get_dirname(source_path); if (!dir) return NULL; char *output = g_build_filename(dir, LATEX_BUILD_DIR, NULL); - g_free(dir); if (g_mkdir_with_parents(output, 0700) != 0) { g_free(output); return NULL; @@ -150,9 +142,9 @@ static gboolean run_latex(EditorTab *tab, const char *source_path, const char *working_dir, const char *output_dir) { - char *stdout_text = NULL; - char *stderr_text = NULL; - GError *error = NULL; + g_autofree char *stdout_text = NULL; + g_autofree char *stderr_text = NULL; + g_autoptr(GError) error = NULL; int status = 0; char *argv[] = { @@ -171,40 +163,33 @@ static gboolean run_latex(EditorTab *tab, NULL, NULL, &stdout_text, &stderr_text, &status, &error); if (!ok || status != 0) { - char *log = latex_log_message(command, stdout_text, stderr_text); - dialog_error(app_window_gtk(tab->win), "LaTeX render failed", - error ? error->message : log); - g_free(log); - g_clear_error(&error); - g_free(stdout_text); - g_free(stderr_text); + g_autofree char *log = latex_log_message(command, stdout_text, stderr_text); + app_window_set_error_status(tab->win, "LaTeX render failed", + error ? error->message : log); return FALSE; } - g_free(stdout_text); - g_free(stderr_text); return TRUE; } /** * @brief Open pdf. */ -static void open_pdf(EditorTab *tab, const char *pdf_path) { - GError *error = NULL; - char *uri = g_filename_to_uri(pdf_path, NULL, &error); +static gboolean open_pdf(EditorTab *tab, const char *pdf_path) { + g_autoptr(GError) error = NULL; + g_autofree char *uri = g_filename_to_uri(pdf_path, NULL, &error); if (!uri) { - dialog_error(app_window_gtk(tab->win), "Could not open rendered PDF", - error ? error->message : "Invalid PDF path"); - g_clear_error(&error); - return; + app_window_set_error_status(tab->win, "Could not open rendered PDF", + error ? error->message : "Invalid PDF path"); + return FALSE; } if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) { - dialog_error(app_window_gtk(tab->win), "Could not open rendered PDF", - error ? error->message : "No default PDF application"); - g_clear_error(&error); + app_window_set_error_status(tab->win, "Could not open rendered PDF", + error ? error->message : "No default PDF application"); + return FALSE; } - g_free(uri); + return TRUE; } /** @@ -213,59 +198,45 @@ static void open_pdf(EditorTab *tab, const char *pdf_path) { void editor_tab_render_latex(EditorTab *tab) { if (!tab || !tab->win) return; if (!editor_tab_is_latex(tab)) { - dialog_error(app_window_gtk(tab->win), "Not a LaTeX document", - "Render is available for .tex/.latex files or LaTeX syntax."); + app_window_set_error_status(tab->win, "Not a LaTeX document", + "Render is available for .tex/.latex files or LaTeX syntax."); return; } - char *command = find_latex_command(); + g_autofree char *command = find_latex_command(); if (!command) { - dialog_error(app_window_gtk(tab->win), "LaTeX command not found", - "Install pdflatex, xelatex, or lualatex, or set " - "CLEAF_LATEX_COMMAND to the command path."); + app_window_set_error_status(tab->win, "LaTeX command not found", + "Install pdflatex, xelatex, or lualatex, or set " + "CLEAF_LATEX_COMMAND to the command path."); return; } - char *source_path = NULL; + g_autofree char *source_path = NULL; if (!ensure_saved_source(tab, &source_path)) { - g_free(command); return; } - char *output_dir = build_output_dir_for_source(source_path); + g_autofree char *output_dir = build_output_dir_for_source(source_path); if (!output_dir) { - dialog_error(app_window_gtk(tab->win), "Could not create build dir", - "Cleaf could not create .cleaf-latex-build."); - g_free(command); - g_free(source_path); + app_window_set_error_status(tab->win, "Could not create build dir", + "Cleaf could not create .cleaf-latex-build."); return; } - char *working_dir = g_path_get_dirname(source_path); + g_autofree char *working_dir = g_path_get_dirname(source_path); if (!run_latex(tab, command, source_path, working_dir, output_dir)) { - g_free(command); - g_free(source_path); - g_free(working_dir); - g_free(output_dir); return; } - char *stem = basename_without_suffix(source_path); - char *pdf_name = g_strdup_printf("%s.pdf", stem); - char *pdf_path = g_build_filename(output_dir, pdf_name, NULL); + g_autofree char *stem = basename_without_suffix(source_path); + g_autofree char *pdf_name = g_strdup_printf("%s.pdf", stem); + g_autofree char *pdf_path = g_build_filename(output_dir, pdf_name, NULL); if (g_file_test(pdf_path, G_FILE_TEST_IS_REGULAR)) { - open_pdf(tab, pdf_path); - app_window_set_status(tab->win, "LaTeX rendered successfully."); + if (open_pdf(tab, pdf_path)) { + app_window_set_status(tab->win, "LaTeX rendered successfully."); + } } else { - dialog_error(app_window_gtk(tab->win), "Rendered PDF not found", - "LaTeX finished, but Cleaf could not find the PDF output."); + app_window_set_error_status(tab->win, "Rendered PDF not found", + "LaTeX finished, but Cleaf could not find the PDF output."); } - - g_free(pdf_path); - g_free(pdf_name); - g_free(stem); - g_free(command); - g_free(source_path); - g_free(working_dir); - g_free(output_dir); } diff --git a/src/editor_tab_sourceview.c b/src/editor_tab_sourceview.c index 7177641..bb9476d 100644 --- a/src/editor_tab_sourceview.c +++ b/src/editor_tab_sourceview.c @@ -304,23 +304,22 @@ static const char *effective_colour(const char *value, const char *fallback) { static GtkSourceStyleScheme *source_yaml_override_scheme_for_tab(EditorTab *tab) { if (!tab || !tab->win) return NULL; - char *dir = yaml_override_style_dir(); + g_autofree char *dir = yaml_override_style_dir(); if (!dir) return source_style_scheme_for_window(tab->win); if (g_mkdir_with_parents(dir, 0700) != 0) { - g_free(dir); return source_style_scheme_for_window(tab->win); } gboolean dark = colour_is_dark(tab->win->editor_bg_color); const char *syntax_name = (tab->active_syntax && tab->active_syntax->name) ? tab->active_syntax->name : "plain"; - char *slug = style_slug_from_syntax_name(syntax_name); - char *id = g_strdup_printf("cleaf-config-%s-%s-%s", - dark ? "dark" : "light", - tab->win->use_yaml_style_overrides ? "yaml" : "plain", - slug ? slug : "plain"); - char *filename = g_strdup_printf("%s.xml", id); - char *path = g_build_filename(dir, filename, NULL); + g_autofree char *slug = style_slug_from_syntax_name(syntax_name); + g_autofree char *id = g_strdup_printf("cleaf-config-%s-%s-%s", + dark ? "dark" : "light", + tab->win->use_yaml_style_overrides ? "yaml" : "plain", + slug ? slug : "plain"); + g_autofree char *filename = g_strdup_printf("%s.xml", id); + g_autofree char *path = g_build_filename(dir, filename, NULL); const char *fg = effective_colour(tab->win->editor_fg_color, dark ? "#d4d4d4" : "#202124"); @@ -337,7 +336,7 @@ static GtkSourceStyleScheme *source_yaml_override_scheme_for_tab(EditorTab *tab) dark ? "#ffffff" : "#111827"); const char *cursor = effective_colour(tab->win->editor_cursor_color, fg); - GString *xml = g_string_new(NULL); + g_autoptr(GString) xml = g_string_new(NULL); g_string_append(xml, "\n"); const char *parent_id = source_parent_scheme_id_for_window(tab->win); g_string_append_printf(xml, @@ -367,35 +366,25 @@ static GtkSourceStyleScheme *source_yaml_override_scheme_for_tab(EditorTab *tab) if (tab->win->use_yaml_style_overrides && tab->active_syntax && tab->active_syntax->rules) { - GHashTable *seen = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + g_autoptr(GHashTable) seen = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); for (guint i = 0u; i < tab->active_syntax->rules->len; i++) { SyntaxRule *rule = g_ptr_array_index(tab->active_syntax->rules, i); if (!rule || !rule->color || rule->color[0] == '\0') continue; - GPtrArray *style_names = gtksource_styles_for_yaml_rule(rule->name); + g_autoptr(GPtrArray) style_names = gtksource_styles_for_yaml_rule(rule->name); for (guint j = 0u; style_names && j < style_names->len; j++) { const char *style_name = g_ptr_array_index(style_names, j); if (!style_name || g_hash_table_contains(seen, style_name)) continue; g_hash_table_add(seen, g_strdup(style_name)); append_style_from_rule(xml, style_name, rule); } - if (style_names) g_ptr_array_free(style_names, TRUE); } - g_hash_table_unref(seen); } g_string_append(xml, "\n"); - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!g_file_set_contents(path, xml->str, (gssize)xml->len, &error)) { - g_clear_error(&error); - g_string_free(xml, TRUE); - g_free(path); - g_free(filename); - g_free(id); - g_free(slug); - g_free(dir); return source_style_scheme_for_window(tab->win); } - g_string_free(xml, TRUE); GtkSourceStyleSchemeManager *manager = gtk_source_style_scheme_manager_get_default(); GtkSourceStyleScheme *scheme = NULL; @@ -413,11 +402,6 @@ static GtkSourceStyleScheme *source_yaml_override_scheme_for_tab(EditorTab *tab) gtk_source_style_scheme_manager_force_rescan(manager); scheme = gtk_source_style_scheme_manager_get_scheme(manager, id); } - g_free(path); - g_free(filename); - g_free(id); - g_free(slug); - g_free(dir); return scheme ? scheme : source_style_scheme_for_window(tab->win); } diff --git a/src/git/git_actions.inc.c b/src/git/git_actions.inc.c index e09239a..3b7f8c2 100644 --- a/src/git/git_actions.inc.c +++ b/src/git/git_actions.inc.c @@ -6,48 +6,10 @@ static void show_git_output(EditorWindow *win, const char *title, const char *body) { - GtkWidget *window = gtk_window_new(); - gtk_widget_add_css_class(window, "cleaf-window"); - gtk_window_set_title(GTK_WINDOW(window), title ? title : "Git"); - gtk_window_set_default_size(GTK_WINDOW(window), 760, 520); - gtk_window_set_modal(GTK_WINDOW(window), TRUE); - if (win) gtk_window_set_transient_for(GTK_WINDOW(window), app_window_gtk(win)); - - GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); - gtk_widget_add_css_class(box, "cleaf-root"); - cleaf_set_all_margins(box, 10); - gtk_window_set_child(GTK_WINDOW(window), box); - - GtkWidget *label = gtk_label_new(title ? title : "Git"); - gtk_label_set_xalign(GTK_LABEL(label), 0.0f); - gtk_widget_add_css_class(label, "cleaf-menu-title"); - gtk_box_append(GTK_BOX(box), label); - - GtkTextBuffer *buffer = gtk_text_buffer_new(NULL); - gtk_text_buffer_set_text(buffer, body ? body : "", -1); - GtkWidget *view = gtk_text_view_new_with_buffer(buffer); - g_object_unref(buffer); - gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); - gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); - gtk_text_view_set_monospace(GTK_TEXT_VIEW(view), TRUE); - gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_NONE); - - GtkWidget *scrolled = gtk_scrolled_window_new(); - gtk_widget_set_vexpand(scrolled, TRUE); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), view); - gtk_box_append(GTK_BOX(box), scrolled); - - GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); - gtk_widget_set_halign(row, GTK_ALIGN_END); - GtkWidget *close = cleaf_flat_button_new("Close", NULL, - G_CALLBACK(cleaf_modal_window_respond), - GINT_TO_POINTER(GTK_RESPONSE_CLOSE)); - gtk_box_append(GTK_BOX(row), close); - gtk_box_append(GTK_BOX(box), row); - - (void)cleaf_modal_window_run(GTK_WINDOW(window), GTK_RESPONSE_CLOSE); - cleaf_widget_destroy(window); + dialog_output(win ? app_window_gtk(win) : NULL, + title ? title : "Git", + title ? title : "Git", + body); } /** @@ -70,7 +32,9 @@ static void show_git_error(EditorWindow *win, g_string_append(text, "stdout:\n"); g_string_append(text, result->out); } - show_git_output(win, operation ? operation : git_error_title(result->kind), text->str); + app_window_set_error_status(win, + operation ? operation : git_error_title(result->kind), + text->str); g_string_free(text, TRUE); } @@ -131,7 +95,8 @@ void action_git_status(GtkWidget *widget, gpointer user_data) { EditorWindow *win = user_data; char *repo = current_repo(win, NULL); if (!repo) { - dialog_error(app_window_gtk(win), "Git status failed", "The current file or project folder is not inside a Git repository."); + app_window_set_error_status(win, "Git status failed", + "The current file or project folder is not inside a Git repository."); return; } @@ -197,7 +162,8 @@ void action_git_diff(GtkWidget *widget, gpointer user_data) { char *rel = NULL; char *repo = current_repo(win, &rel); if (!repo) { - dialog_error(app_window_gtk(win), "Git diff failed", "The current file or project folder is not inside a Git repository."); + app_window_set_error_status(win, "Git diff failed", + "The current file or project folder is not inside a Git repository."); g_free(rel); return; } @@ -234,7 +200,8 @@ void action_git_stage(GtkWidget *widget, gpointer user_data) { char *rel = NULL; char *repo = current_repo(win, &rel); if (!repo || !rel || g_strcmp0(rel, ".") == 0) { - dialog_error(app_window_gtk(win), "Git stage failed", "Open a file inside a Git repository first, or use Stage All."); + app_window_set_error_status(win, "Git stage failed", + "Open a file inside a Git repository first, or use Stage All."); g_free(rel); g_free(repo); return; @@ -258,7 +225,8 @@ void action_git_unstage(GtkWidget *widget, gpointer user_data) { char *rel = NULL; char *repo = current_repo(win, &rel); if (!repo || !rel || g_strcmp0(rel, ".") == 0) { - dialog_error(app_window_gtk(win), "Git unstage failed", "Open a file inside a Git repository first."); + app_window_set_error_status(win, "Git unstage failed", + "Open a file inside a Git repository first."); g_free(rel); g_free(repo); return; @@ -285,7 +253,8 @@ void action_git_stage_all(GtkWidget *widget, gpointer user_data) { EditorWindow *win = user_data; char *repo = current_repo(win, NULL); if (!repo) { - dialog_error(app_window_gtk(win), "Git stage all failed", "No Git repository is active."); + app_window_set_error_status(win, "Git stage all failed", + "No Git repository is active."); return; } @@ -305,7 +274,8 @@ void action_git_commit(GtkWidget *widget, gpointer user_data) { EditorWindow *win = user_data; char *repo = current_repo(win, NULL); if (!repo) { - dialog_error(app_window_gtk(win), "Git commit failed", "No Git repository is active."); + app_window_set_error_status(win, "Git commit failed", + "No Git repository is active."); return; } @@ -316,7 +286,8 @@ void action_git_commit(GtkWidget *widget, gpointer user_data) { } g_strstrip(message); if (message[0] == '\0') { - dialog_error(app_window_gtk(win), "Git commit failed", "Commit message cannot be empty."); + app_window_set_error_status(win, "Git commit failed", + "Commit message cannot be empty."); g_free(message); g_free(repo); return; @@ -327,7 +298,8 @@ void action_git_commit(GtkWidget *widget, gpointer user_data) { && check.exit_code == 1; git_result_clear(&check); if (!staged) { - dialog_error(app_window_gtk(win), "Git commit failed", "No staged changes to commit. Use Git > Stage or Git > Stage All first."); + app_window_set_error_status(win, "Git commit failed", + "No staged changes to commit. Use Git > Stage or Git > Stage All first."); g_free(message); g_free(repo); return; @@ -353,7 +325,8 @@ void action_git_pull(GtkWidget *widget, gpointer user_data) { EditorWindow *win = user_data; char *repo = current_repo(win, NULL); if (!repo) { - dialog_error(app_window_gtk(win), "Git pull failed", "No Git repository is active."); + app_window_set_error_status(win, "Git pull failed", + "No Git repository is active."); return; } CleafGitResult result; @@ -372,7 +345,8 @@ void action_git_push(GtkWidget *widget, gpointer user_data) { EditorWindow *win = user_data; char *repo = current_repo(win, NULL); if (!repo) { - dialog_error(app_window_gtk(win), "Git push failed", "No Git repository is active."); + app_window_set_error_status(win, "Git push failed", + "No Git repository is active."); return; } CleafGitResult result; @@ -402,4 +376,3 @@ void action_git_push(GtkWidget *widget, gpointer user_data) { git_result_clear(&result); g_free(repo); } - diff --git a/src/git/git_core.inc.c b/src/git/git_core.inc.c index 7965043..dcbe6c5 100644 --- a/src/git/git_core.inc.c +++ b/src/git/git_core.inc.c @@ -140,8 +140,8 @@ static gboolean run_argv(const char * const *argv, if (!argv || !argv[0] || !result) return FALSE; memset(result, 0, sizeof(*result)); - GError *error = NULL; - GSubprocess *proc = g_subprocess_newv(argv, + g_autoptr(GError) error = NULL; + g_autoptr(GSubprocess) proc = g_subprocess_newv(argv, G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE, @@ -151,20 +151,19 @@ static gboolean run_argv(const char * const *argv, result->kind = CLEAF_GIT_ERROR_GIT_MISSING; result->err = g_strdup(error ? error->message : "Could not start git."); result->message = g_strdup(git_error_title(result->kind)); - g_clear_error(&error); return FALSE; } - char *out = NULL; - char *err = NULL; + g_autofree char *out = NULL; + g_autofree char *err = NULL; gboolean ok = g_subprocess_communicate_utf8(proc, stdin_text, NULL, &out, &err, &error); - result->out = out ? out : g_strdup(""); - result->err = err ? err : g_strdup(""); + result->out = out ? g_steal_pointer(&out) : g_strdup(""); + result->err = err ? g_steal_pointer(&err) : g_strdup(""); /* * Git can emit very large diffs or hook output. Keep result buffers bounded * before they are copied into dialogs/tabs so a failed command cannot make @@ -177,8 +176,6 @@ static gboolean run_argv(const char * const *argv, result->exit_code = 1; result->kind = classify_git_error(result->out, error ? error->message : result->err); result->message = g_strdup(error ? error->message : git_error_title(result->kind)); - g_clear_error(&error); - g_object_unref(proc); return FALSE; } @@ -186,13 +183,11 @@ static gboolean run_argv(const char * const *argv, if (result->exit_code == 0) { result->kind = CLEAF_GIT_ERROR_NONE; result->message = g_strdup("Git command completed."); - g_object_unref(proc); return TRUE; } result->kind = classify_git_error(result->out, result->err); result->message = g_strdup(git_error_title(result->kind)); - g_object_unref(proc); return FALSE; } @@ -423,4 +418,3 @@ void cleaf_git_refresh_and_rebuild(EditorWindow *win) { cleaf_git_refresh_all(win); project_tree_refresh(win); } - diff --git a/src/git/git_credentials.inc.c b/src/git/git_credentials.inc.c index 3199559..3ee92a1 100644 --- a/src/git/git_credentials.inc.c +++ b/src/git/git_credentials.inc.c @@ -137,7 +137,9 @@ void action_git_credentials(GtkWidget *widget, gpointer user_data) { char *helper = NULL; char *err = credential_dialog(win, &protocol, &host, &username, &secret, &helper); if (err) { - if (g_strcmp0(err, "cancelled") != 0) dialog_error(app_window_gtk(win), "Git credentials", err); + if (g_strcmp0(err, "cancelled") != 0) { + app_window_set_error_status(win, "Git credentials", err); + } g_free(err); g_free(protocol); g_free(host); g_free(username); g_free(secret); g_free(helper); return; @@ -186,7 +188,8 @@ void action_git_run(GtkWidget *widget, gpointer user_data) { EditorWindow *win = user_data; char *repo = current_repo(win, NULL); if (!repo) { - dialog_error(app_window_gtk(win), "Git command failed", "No Git repository is active."); + app_window_set_error_status(win, "Git command failed", + "No Git repository is active."); return; } @@ -206,7 +209,8 @@ void action_git_run(GtkWidget *widget, gpointer user_data) { char **parsed = NULL; GError *parse_error = NULL; if (!g_shell_parse_argv(args, &argc, &parsed, &parse_error)) { - dialog_error(app_window_gtk(win), "Git command parse failed", parse_error ? parse_error->message : "Could not parse arguments."); + app_window_set_error_status(win, "Git command parse failed", + parse_error ? parse_error->message : "Could not parse arguments."); g_clear_error(&parse_error); g_free(args); g_free(repo); diff --git a/src/index/index_collect.inc.c b/src/index/index_collect.inc.c index e3e4505..c01efe9 100644 --- a/src/index/index_collect.inc.c +++ b/src/index/index_collect.inc.c @@ -54,18 +54,16 @@ static gboolean read_small_file(const char *path, char **out_text) { if (stat(path, &st) != 0) return FALSE; if (!S_ISREG(st.st_mode)) return FALSE; if (st.st_size < 0 || (guint64)st.st_size > (guint64)CLEAF_INDEX_MAX_FILE_BYTES) return FALSE; - char *text = NULL; + g_autofree char *text = NULL; gsize len = 0u; - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!g_file_get_contents(path, &text, &len, &error)) { - g_clear_error(&error); return FALSE; } if (!g_utf8_validate(text, (gssize)len, NULL)) { - g_free(text); return FALSE; } - *out_text = text; + *out_text = g_steal_pointer(&text); return TRUE; } @@ -242,4 +240,3 @@ static char *find_in_projects(EditorWindow *win, const char *basename) { g_hash_table_destroy(seen); return found; } - diff --git a/src/project/project_context.inc.c b/src/project/project_context.inc.c index 986bd7f..dfd642d 100644 --- a/src/project/project_context.inc.c +++ b/src/project/project_context.inc.c @@ -149,51 +149,36 @@ static void project_context_rename(GtkWidget *widget, gpointer user_data) { ProjectAction *action = user_data; if (!action || !action->win || !action->path) return; - char *old_base = g_path_get_basename(action->path); - char *new_name = dialog_prompt_text(app_window_gtk(action->win), - "Rename", - "New name:", - old_base); - if (!new_name) { - g_free(old_base); - return; - } + g_autofree char *old_base = g_path_get_basename(action->path); + g_autofree char *new_name = dialog_prompt_text(app_window_gtk(action->win), + "Rename", + "New name:", + old_base); + if (!new_name) return; g_strstrip(new_name); if (invalid_new_name(new_name)) { - dialog_error(app_window_gtk(action->win), "Invalid name", - "Use a plain file or folder name, not a path."); - g_free(new_name); - g_free(old_base); + app_window_set_error_status(action->win, "Invalid name", + "Use a plain file or folder name, not a path."); return; } if (g_strcmp0(new_name, old_base) == 0) { - g_free(new_name); - g_free(old_base); return; } - char *dir = g_path_get_dirname(action->path); - char *target = g_build_filename(dir, new_name, NULL); + g_autofree char *dir = g_path_get_dirname(action->path); + g_autofree char *target = g_build_filename(dir, new_name, NULL); if (g_file_test(target, G_FILE_TEST_EXISTS)) { - dialog_error(app_window_gtk(action->win), "Rename failed", - "A file or folder with that name already exists."); - g_free(target); - g_free(dir); - g_free(new_name); - g_free(old_base); + app_window_set_error_status(action->win, "Rename failed", + "A file or folder with that name already exists."); return; } if (g_rename(action->path, target) != 0) { - dialog_error(app_window_gtk(action->win), "Rename failed", - g_strerror(errno)); - g_free(target); - g_free(dir); - g_free(new_name); - g_free(old_base); + app_window_set_error_status(action->win, "Rename failed", + g_strerror(errno)); return; } @@ -207,13 +192,8 @@ static void project_context_rename(GtkWidget *widget, gpointer user_data) { app_window_note_path_renamed(action->win, action->path, target); project_tree_rebuild(action->win); - char *msg = g_strdup_printf("Renamed %s to %s", old_base, new_name); + g_autofree char *msg = g_strdup_printf("Renamed %s to %s", old_base, new_name); app_window_set_status(action->win, msg); - g_free(msg); - g_free(target); - g_free(dir); - g_free(new_name); - g_free(old_base); } /** diff --git a/src/project_search.c b/src/project_search.c index d855369..188712c 100644 --- a/src/project_search.c +++ b/src/project_search.c @@ -207,14 +207,12 @@ static void project_scan_dir(ProjectSearch *state, depth + 1u); } else if (syntax_path_is_indexable(state->win->syntaxes, path) && readable_small_file(path)) { - char *text = NULL; + g_autofree char *text = NULL; gsize len = 0u; - GError *error = NULL; + g_autoptr(GError) error = NULL; if (g_file_get_contents(path, &text, &len, &error) && text) { scan_text_for_matches(state, path, text, query); } - g_clear_error(&error); - g_free(text); } g_free(path); if (state->matches->len >= PROJECT_SEARCH_MAX_RESULTS) break; @@ -296,23 +294,19 @@ static char *replace_literal(const char *text, static guint replace_in_file(const char *path, const char *find, const char *replace) { - char *text = NULL; + g_autofree char *text = NULL; gsize len = 0u; - GError *error = NULL; + g_autoptr(GError) error = NULL; guint count = 0u; if (!g_file_get_contents(path, &text, &len, &error)) { - g_clear_error(&error); return 0u; } - char *updated = replace_literal(text, find, replace, &count); + g_autofree char *updated = replace_literal(text, find, replace, &count); if (count > 0u) { if (!g_file_set_contents(path, updated, -1, &error)) { - g_clear_error(&error); count = 0u; } } - g_free(updated); - g_free(text); return count; } diff --git a/src/syntax_loader.c b/src/syntax_loader.c index 9519031..f0123a9 100644 --- a/src/syntax_loader.c +++ b/src/syntax_loader.c @@ -207,12 +207,11 @@ static void parse_rule_field(SyntaxRule *rule, const char *key, const char *valu */ static gboolean compile_syntax_rule(SyntaxRule *rule, const char *filename) { if (!rule || !rule->pattern || rule->pattern[0] == '\0') return FALSE; - GError *error = NULL; + g_autoptr(GError) error = NULL; rule->regex = g_regex_new(rule->pattern, G_REGEX_MULTILINE | G_REGEX_OPTIMIZE, 0, &error); if (!rule->regex) { g_warning("Invalid regex in %s (%s): %s", filename ? filename : "syntax file", rule->name ? rule->name : "unnamed", error ? error->message : "unknown error"); - g_clear_error(&error); return FALSE; } if (!rule->name) rule->name = g_strdup("rule"); @@ -433,35 +432,30 @@ static gboolean compile_syntax_rules(SyntaxDef *syntax, const char *path) { * @brief Load syntax file. */ static SyntaxDef *load_syntax_file(const char *path) { - char *contents = NULL; + g_autofree char *contents = NULL; gsize length = 0u; - GError *error = NULL; + g_autoptr(GError) error = NULL; if (!g_file_get_contents(path, &contents, &length, &error)) { g_warning("Could not read syntax file %s: %s", path, error ? error->message : "unknown error"); - g_clear_error(&error); return NULL; } SyntaxDef *syntax = syntax_def_new_default(); gboolean in_rules = FALSE; SyntaxRule *current_rule = NULL; - char *block_list_key = NULL; - char **lines = g_strsplit(contents, "\n", -1); + g_autofree char *block_list_key = NULL; + g_auto(GStrv) lines = g_strsplit(contents, "\n", -1); for (guint i = 0u; syntax && lines && lines[i]; i++) { - char *raw = g_strdup(lines[i]); + g_autofree char *raw = g_strdup(lines[i]); if (!raw) continue; g_strchomp(raw); char *line = g_strstrip(raw); if (line[0] != '\0' && line[0] != '#') { parse_syntax_line(syntax, line, &in_rules, ¤t_rule, &block_list_key); } - g_free(raw); } - g_clear_pointer(&block_list_key, g_free); - g_strfreev(lines); - g_free(contents); if (!syntax) return NULL; if (!compile_syntax_rules(syntax, path)) { diff --git a/src/ui.c b/src/ui.c index a25af5f..edda7b7 100644 --- a/src/ui.c +++ b/src/ui.c @@ -107,13 +107,11 @@ static void file_dialog_finish(GtkFileDialog *dialog, FileDialogState *state = user_data; if (!state) return; - GError *error = NULL; - GFile *file = finish_func(dialog, result, &error); + g_autoptr(GError) error = NULL; + g_autoptr(GFile) file = finish_func(dialog, result, &error); if (file) { state->path = g_file_get_path(file); - g_object_unref(file); } - g_clear_error(&error); if (state->loop) g_main_loop_quit(state->loop); } diff --git a/src/ui.h b/src/ui.h index 2ca3231..b54dbf3 100644 --- a/src/ui.h +++ b/src/ui.h @@ -40,6 +40,7 @@ void cleaf_apply_editor_css(const char *editor_bg_color, const char *topbar_fg_color, const char *bottombar_bg_color, const char *bottombar_fg_color, + const char *status_error_color, const char *button_bg_color, const char *button_fg_color, const char *button_hover_bg_color, diff --git a/src/ui/ui_base_css.inc.c b/src/ui/ui_base_css.inc.c index f53253f..9da495a 100644 --- a/src/ui/ui_base_css.inc.c +++ b/src/ui/ui_base_css.inc.c @@ -110,6 +110,7 @@ void cleaf_apply_css(void) { ".cleaf-switch-row { padding: 0 1px; }" ".cleaf-switch-label { opacity: 0.82; font-size: 8.5pt; padding: 0 2px; }" ".cleaf-status { opacity: 0.85; padding: 3px 6px; }" + ".cleaf-status-error { font-weight: 700; }" ".cleaf-codex-panel {" " border-left: 1px solid alpha(currentColor, 0.18);" " padding: 8px;" diff --git a/src/ui/ui_editor_css.inc.c b/src/ui/ui_editor_css.inc.c index 8e44bcb..ad1f87e 100644 --- a/src/ui/ui_editor_css.inc.c +++ b/src/ui/ui_editor_css.inc.c @@ -43,6 +43,7 @@ void cleaf_apply_editor_css(const char *editor_bg_color, const char *topbar_fg_color, const char *bottombar_bg_color, const char *bottombar_fg_color, + const char *status_error_color, const char *button_bg_color, const char *button_fg_color, const char *button_hover_bg_color, @@ -125,6 +126,8 @@ void cleaf_apply_editor_css(const char *editor_bg_color, (bottombar_bg_color && bottombar_bg_color[0] == '#') ? bottombar_bg_color : effective_bg; const char *effective_bottombar_fg = (bottombar_fg_color && bottombar_fg_color[0] == '#') ? bottombar_fg_color : effective_fg; + const char *effective_status_error = + (status_error_color && status_error_color[0] == '#') ? status_error_color : "#ff6b6b"; const char *effective_button_bg = (button_bg_color && button_bg_color[0] == '#') ? button_bg_color : effective_bg; const char *effective_button_fg = @@ -290,6 +293,8 @@ void cleaf_apply_editor_css(const char *editor_bg_color, append_bg_rule(css, ".cleaf-bottom", effective_bottombar_bg); append_fg_rule(css, ".cleaf-bottom", effective_bottombar_fg); append_fg_rule(css, ".cleaf-bottom label", effective_bottombar_fg); + append_fg_rule(css, ".cleaf-bottom label.cleaf-status-error", effective_status_error); + append_fg_rule(css, "label.cleaf-status-error", effective_status_error); append_fg_rule(css, ".cleaf-root", effective_fg); append_fg_rule(css, ".cleaf-root label", effective_fg); g_string_append_printf(css,