// config.c — Global program configuration (config.ini next to binary) #include #include #include #ifdef __APPLE__ #include #include #include #else #include #endif Config g_config = {0}; //////////////////////////////// // Path resolution static void config_get_path(char *out, S32 out_size) { char exe_path[CONFIG_PATH_MAX]; #ifdef __APPLE__ U32 sz = sizeof(exe_path); _NSGetExecutablePath(exe_path, &sz); #else GetModuleFileNameA(NULL, exe_path, CONFIG_PATH_MAX); #endif // Strip binary name to get directory char *sep = strrchr(exe_path, '/'); #ifndef __APPLE__ char *bsep = strrchr(exe_path, '\\'); if (bsep && (!sep || bsep > sep)) sep = bsep; #endif if (sep) sep[1] = '\0'; snprintf(out, out_size, "%sconfig.ini", exe_path); } //////////////////////////////// // Helpers static void config_trim(char *s) { char *start = s; while (*start == ' ' || *start == '\t') start++; if (start != s) memmove(s, start, strlen(start) + 1); S32 len = (S32)strlen(s); while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t' || s[len - 1] == '\r' || s[len - 1] == '\n')) s[--len] = '\0'; } //////////////////////////////// // Defaults static void config_set_defaults(void) { MemoryZeroStruct(&g_config); snprintf(g_config.theme, sizeof(g_config.theme), "%s", "Gruvbox Dark"); g_config.show_line_numbers = 1; g_config.syntax_enabled = 1; g_config.block_cursor = 1; g_config.cursor_smear = 1; g_config.smooth_scroll = 1; g_config.ui_scale = 1.0f; g_config.editor_font_size = 15.0f; snprintf(g_config.editor_font, sizeof(g_config.editor_font), "%s", "FiraCode"); snprintf(g_config.ui_font, sizeof(g_config.ui_font), "%s", "Inter"); g_config.window_width = 1280; g_config.window_height = 800; g_config.active_project[0] = '\0'; g_config.recent_dir_count = 0; } //////////////////////////////// // Load static void config_load(void) { config_set_defaults(); char path[CONFIG_PATH_MAX]; config_get_path(path, sizeof(path)); FILE *f = fopen(path, "rb"); if (!f) { // No config file — write defaults config_save(); return; } char line_buf[CONFIG_PATH_MAX + 64]; char current_section[64] = {0}; while (fgets(line_buf, sizeof(line_buf), f)) { config_trim(line_buf); if (line_buf[0] == '\0' || line_buf[0] == '#' || line_buf[0] == ';') continue; // Section header if (line_buf[0] == '[') { char *close = strchr(line_buf, ']'); if (close) { *close = '\0'; snprintf(current_section, sizeof(current_section), "%s", line_buf + 1); } continue; } // Key = value char *eq = strchr(line_buf, '='); if (!eq) continue; *eq = '\0'; char *key = line_buf; char *val = eq + 1; config_trim(key); config_trim(val); if (strcmp(current_section, "general") == 0) { if (strcmp(key, "theme") == 0) snprintf(g_config.theme, sizeof(g_config.theme), "%s", val); else if (strcmp(key, "show_line_numbers") == 0) g_config.show_line_numbers = (strcmp(val, "true") == 0); else if (strcmp(key, "syntax_enabled") == 0) g_config.syntax_enabled = (strcmp(val, "true") == 0); else if (strcmp(key, "block_cursor") == 0) g_config.block_cursor = (strcmp(val, "true") == 0); else if (strcmp(key, "cursor_smear") == 0) g_config.cursor_smear = (strcmp(val, "true") == 0); else if (strcmp(key, "smooth_scroll") == 0) g_config.smooth_scroll = (strcmp(val, "true") == 0); else if (strcmp(key, "ui_scale") == 0) g_config.ui_scale = (F32)atof(val); else if (strcmp(key, "editor_font_size") == 0) g_config.editor_font_size = (F32)atof(val); else if (strcmp(key, "editor_font") == 0) snprintf(g_config.editor_font, sizeof(g_config.editor_font), "%s", val); else if (strcmp(key, "ui_font") == 0) snprintf(g_config.ui_font, sizeof(g_config.ui_font), "%s", val); else if (strcmp(key, "active_project") == 0) snprintf(g_config.active_project, sizeof(g_config.active_project), "%s", val); else if (strcmp(key, "window_width") == 0) g_config.window_width = atoi(val); else if (strcmp(key, "window_height") == 0) g_config.window_height = atoi(val); } else if (strcmp(current_section, "recent_dirs") == 0) { // Keys are "0", "1", "2", etc. S32 idx = atoi(key); if (idx >= 0 && idx < CONFIG_MAX_RECENT_DIRS && val[0] != '\0') { snprintf(g_config.recent_dirs[idx], CONFIG_PATH_MAX, "%s", val); if (idx + 1 > g_config.recent_dir_count) g_config.recent_dir_count = idx + 1; } } } fclose(f); } //////////////////////////////// // Save static void config_save(void) { char path[CONFIG_PATH_MAX]; config_get_path(path, sizeof(path)); FILE *f = fopen(path, "wb"); if (!f) return; fprintf(f, "# codeMAX configuration — managed by the program\r\n\r\n"); fprintf(f, "[general]\r\n"); fprintf(f, "theme = %s\r\n", g_config.theme); fprintf(f, "show_line_numbers = %s\r\n", g_config.show_line_numbers ? "true" : "false"); fprintf(f, "syntax_enabled = %s\r\n", g_config.syntax_enabled ? "true" : "false"); fprintf(f, "block_cursor = %s\r\n", g_config.block_cursor ? "true" : "false"); fprintf(f, "cursor_smear = %s\r\n", g_config.cursor_smear ? "true" : "false"); fprintf(f, "smooth_scroll = %s\r\n", g_config.smooth_scroll ? "true" : "false"); fprintf(f, "ui_scale = %.2f\r\n", g_config.ui_scale); fprintf(f, "editor_font_size = %.1f\r\n", g_config.editor_font_size); fprintf(f, "editor_font = %s\r\n", g_config.editor_font); fprintf(f, "ui_font = %s\r\n", g_config.ui_font); fprintf(f, "window_width = %d\r\n", g_config.window_width); fprintf(f, "window_height = %d\r\n", g_config.window_height); if (g_config.active_project[0] != '\0') fprintf(f, "active_project = %s\r\n", g_config.active_project); fprintf(f, "\r\n[recent_dirs]\r\n"); for (S32 i = 0; i < g_config.recent_dir_count; i++) { if (g_config.recent_dirs[i][0] != '\0') fprintf(f, "%d = %s\r\n", i, g_config.recent_dirs[i]); } fclose(f); } //////////////////////////////// // Recent directories static void config_push_recent_dir(const char *dir) { if (!dir || dir[0] == '\0') return; // Normalize path for comparison char norm[CONFIG_PATH_MAX]; snprintf(norm, sizeof(norm), "%s", dir); for (char *p = norm; *p; p++) { if (*p == '/') *p = '\\'; } // Check if already in list — if so, move to front S32 existing = -1; for (S32 i = 0; i < g_config.recent_dir_count; i++) { #ifdef __APPLE__ if (strcmp(g_config.recent_dirs[i], norm) == 0) { existing = i; break; } #else if (_stricmp(g_config.recent_dirs[i], norm) == 0) { existing = i; break; } #endif } if (existing == 0) return; // already at front // Save the entry if it exists, or use the new dir char entry[CONFIG_PATH_MAX]; snprintf(entry, sizeof(entry), "%s", norm); // Shift entries down S32 start = (existing >= 0) ? existing : g_config.recent_dir_count; if (start >= CONFIG_MAX_RECENT_DIRS) start = CONFIG_MAX_RECENT_DIRS - 1; for (S32 i = start; i > 0; i--) memcpy(g_config.recent_dirs[i], g_config.recent_dirs[i - 1], CONFIG_PATH_MAX); // Insert at front snprintf(g_config.recent_dirs[0], CONFIG_PATH_MAX, "%s", entry); if (existing < 0 && g_config.recent_dir_count < CONFIG_MAX_RECENT_DIRS) g_config.recent_dir_count++; config_save(); }