diff --git a/appicon.ico b/appicon.ico new file mode 100644 index 0000000..3b4e44a Binary files /dev/null and b/appicon.ico differ diff --git a/data.h b/data.h index 1c91015..e87860d 100644 --- a/data.h +++ b/data.h @@ -46,6 +46,10 @@ struct FunctionRow { std::string description; std::string created_at; bool tested = false; + // JSON array string ("[\"a\",\"b\"]") con IDs de funciones consumidas. + // Se carga junto con la lista para soportar reverse lookup "Used by" en + // el tab Dependencies del Explorer sin endpoint extra. + std::string uses_functions; }; struct AppRow { diff --git a/data_http.cpp b/data_http.cpp index 4c3bb9d..f0d57c7 100644 --- a/data_http.cpp +++ b/data_http.cpp @@ -373,7 +373,7 @@ bool load_all_functions_http(const std::string& api_url, HttpClient cli(host, port); auto j = api_query(cli, "SELECT id, name, lang, domain, kind, purity, description, " - "created_at, tested FROM functions ORDER BY name"); + "created_at, tested, uses_functions FROM functions ORDER BY name"); if (j.is_null() || !j.contains("rows")) return false; out.clear(); @@ -389,6 +389,7 @@ bool load_all_functions_http(const std::string& api_url, r.description = extract_str(row, 6); r.created_at = extract_str(row, 7); r.tested = extract_row_int(row, 8) != 0; + r.uses_functions = extract_str(row, 9); out.push_back(std::move(r)); } return true; diff --git a/views.cpp b/views.cpp index ba0491a..9e0d943 100644 --- a/views.cpp +++ b/views.cpp @@ -325,6 +325,31 @@ static int g_explorer_test_idx = 0; static char g_explorer_filter[128] = {}; static int g_explorer_lang_idx = 0; static int g_explorer_domain_idx = 0; +// Caches del tab Dependencies — se recomputan en explorer_select() para no +// reparsar JSON cada frame. uses_funcs/uses_types: parse de d.uses_functions +// y d.uses_types. used_by: reverse lookup cliente-side sobre g_explorer_funcs. +static std::vector g_explorer_uses_funcs; +static std::vector g_explorer_uses_types; +static std::vector g_explorer_used_by; + +// Parser tolerante de arrays JSON de strings (`["a","b"]`). Las entradas de +// la BD son IDs `[a-z0-9_]+` sin escapes, asi que un walker simple basta y +// evita anadir dependencia a nlohmann/json (que vive solo en data_http.cpp). +static std::vector parse_string_array_json(const std::string& json_str) { + std::vector out; + if (json_str.empty() || json_str == "[]") return out; + size_t i = 0, n = json_str.size(); + while (i < n) { + while (i < n && json_str[i] != '"') i++; + if (i >= n) break; + i++; // past opening " + size_t start = i; + while (i < n && json_str[i] != '"') i++; + if (i > start) out.emplace_back(json_str.substr(start, i - start)); + if (i < n) i++; // past closing " + } + return out; +} static void trigger_reload() { ImGui::GetIO().UserData = reinterpret_cast(1); @@ -1378,9 +1403,22 @@ static void explorer_select(const std::string& id) { g_explorer_detail = FunctionDetail{}; g_explorer_tests.clear(); g_explorer_test_idx = 0; + g_explorer_uses_funcs.clear(); + g_explorer_uses_types.clear(); + g_explorer_used_by.clear(); if (!g_api_url.empty() && !id.empty()) { load_function_detail_http(g_api_url, id, g_explorer_detail); load_unit_tests_http(g_api_url, id, g_explorer_tests); + // Caches del tab Dependencies — forward + reverse. + g_explorer_uses_funcs = parse_string_array_json(g_explorer_detail.uses_functions); + g_explorer_uses_types = parse_string_array_json(g_explorer_detail.uses_types); + for (const auto& f : g_explorer_funcs) { + if (f.id == id) continue; + auto deps = parse_string_array_json(f.uses_functions); + for (const auto& dep : deps) { + if (dep == id) { g_explorer_used_by.push_back(f.id); break; } + } + } } } @@ -1574,6 +1612,97 @@ void draw_functions_explorer() { } ImGui::EndTabItem(); } + // Tab Dependencies: muestra uses_functions, uses_types, y reverse + // lookup "used by" (clientes-side sobre g_explorer_funcs). Caches + // (g_explorer_uses_*) se rellenan en explorer_select(). + int total_deps = (int)(g_explorer_uses_funcs.size() + + g_explorer_uses_types.size()); + char deps_label[32]; + std::snprintf(deps_label, sizeof(deps_label), + "Dependencies (%d)", total_deps); + if (ImGui::BeginTabItem(deps_label)) { + ImGui::BeginChild("##deps_scroll", ImVec2(0, 0)); + + // Section 1: Uses functions (clickables; color dim si la id + // no resuelve en el catalogo actual). + char hdr1[64]; + std::snprintf(hdr1, sizeof(hdr1), + "Uses functions (%zu)", + g_explorer_uses_funcs.size()); + if (ImGui::CollapsingHeader(hdr1, + ImGuiTreeNodeFlags_DefaultOpen)) { + if (g_explorer_uses_funcs.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, + fn_tokens::colors::text_dim); + ImGui::TextUnformatted("(none)"); + ImGui::PopStyleColor(); + } else { + for (const auto& fid : g_explorer_uses_funcs) { + bool resolves = false; + for (const auto& f : g_explorer_funcs) { + if (f.id == fid) { resolves = true; break; } + } + ImVec4 col = resolves + ? fn_tokens::colors::text + : fn_tokens::colors::text_dim; + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::PushID(fid.c_str()); + if (ImGui::Selectable(fid.c_str())) { + if (resolves) explorer_select(fid); + } + ImGui::PopID(); + ImGui::PopStyleColor(); + } + } + } + + // Section 2: Uses types — sin navegacion (no hay type explorer + // todavia); bullets para indicar listado pasivo. + char hdr2[64]; + std::snprintf(hdr2, sizeof(hdr2), + "Uses types (%zu)", + g_explorer_uses_types.size()); + if (ImGui::CollapsingHeader(hdr2, + ImGuiTreeNodeFlags_DefaultOpen)) { + if (g_explorer_uses_types.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, + fn_tokens::colors::text_dim); + ImGui::TextUnformatted("(none)"); + ImGui::PopStyleColor(); + } else { + for (const auto& tid : g_explorer_uses_types) { + ImGui::BulletText("%s", tid.c_str()); + } + } + } + + // Section 3: Used by (reverse) — clientes detectados al + // seleccionar la funcion. + char hdr3[64]; + std::snprintf(hdr3, sizeof(hdr3), + "Used by (%zu)", + g_explorer_used_by.size()); + if (ImGui::CollapsingHeader(hdr3, + ImGuiTreeNodeFlags_DefaultOpen)) { + if (g_explorer_used_by.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, + fn_tokens::colors::text_dim); + ImGui::TextUnformatted("(no consumers in catalog)"); + ImGui::PopStyleColor(); + } else { + for (const auto& fid : g_explorer_used_by) { + ImGui::PushID(fid.c_str()); + if (ImGui::Selectable(fid.c_str())) { + explorer_select(fid); + } + ImGui::PopID(); + } + } + } + + ImGui::EndChild(); + ImGui::EndTabItem(); + } if (ImGui::BeginTabItem("Metadata")) { ImGui::BeginChild("##meta_scroll", ImVec2(0, 0)); auto kv = [](const char* k, const std::string& v) { @@ -1590,8 +1719,7 @@ void draw_functions_explorer() { kv("Created:", d.created_at); kv("Returns:", d.returns); kv("Error type:", d.error_type); - kv("Uses functions:", d.uses_functions); - kv("Uses types:", d.uses_types); + // Uses functions/types se muestran en el tab Dependencies. kv("Params schema:", d.params_schema); kv("Example:", d.example); kv("Notes:", d.notes);