Files
essim/src/tui/screen_search.cpp
François f3920964f0 ODS import, persistence, scripting, connector types + VPX transforms.
- ODS importer (libzip + pugixml): each sheet → Part, rows → Pin/Signal.
- save / restore commands: tab-delimited snapshot of modules, parts,
  signals, connections + pin_map. `restore` replaces the System.
- source / script-save: replay a file of commands; record canonical
  commands since last `new` for replay later. Interactive screens
  refused during source. `explore` marked non-scriptable.
- TUI screen_explore: 4 columns (modules, type, children, detail) with
  filters on children and detail; detail is a Menu so arrows scroll
  long pin lists.
- Connector types & transforms: each Part carries a `connector_type`
  string. `set-type` validates the part's pin layout against the type
  (cols set check). `connect` strict pair: rejects when lookup falls
  back to identity unless types are both empty AND pin sets match.
- VPX 3U transforms: 3 registered pairs (vpx-3u-bkp-pN ↔ vpx-3u-payload-pN,
  N=0/1/2) with row-pattern correspondence tables ported from the user's
  Python reference.
- Code split for maintainability: src/tui/{shell,completion,commands,
  screen_main,screen_search,screen_connect,screen_settype,screen_explore,
  tui_helpers}.cpp.
- Bug fixes: Module::add(Part*) override sets part->prnt (was always
  null, breaking save's W lines). Defensive guards in explore against
  empty Menu lists. Renderer wrapped in try/catch so domain throws
  surface as on-screen errors instead of SIGABRT.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 19:58:51 +02:00

84 lines
2.9 KiB
C++

#include "tui/tui.hpp"
#include "tui/tui_helpers.hpp"
#include "system/modules.hpp"
#include "system/parts.hpp"
#include "system/signals.hpp"
#include "system/system.hpp"
#include <ftxui/component/component.hpp>
#include <ftxui/component/component_options.hpp>
#include <ftxui/dom/elements.hpp>
#include <algorithm>
#include <exception>
#include <utility>
using namespace ftxui;
Component Tui::BuildSearchScreen() {
InputOption query_opt;
query_opt.multiline = false;
query_opt.transform = [](InputState s) {
auto el = s.element;
if (s.is_placeholder) el |= dim;
return el;
};
auto query_input = Input(&search_query, "filter…", query_opt);
auto module_menu = Menu(&search_modules, &search_module_idx);
auto type_menu = Menu(&search_types, &search_type_idx);
auto components = Container::Vertical(
{query_input, module_menu, type_menu}, &search_focus_idx);
return Renderer(components,
[this, query_input, module_menu, type_menu] {
std::vector<std::pair<std::string, size_t>> hits;
if (!search_modules.empty() && sys) {
const std::string &mname = search_modules[search_module_idx];
try {
Module *mod = sys->modules()->get(mname);
std::string needle = ToLower(search_query);
if (search_type_idx == 0) { // parts
for (auto &pkv : *mod)
if (needle.empty()
|| ToLower(pkv.first).find(needle) != std::string::npos)
hits.emplace_back(pkv.first, pkv.second->size());
} else { // signals
for (auto &skv : *mod->signals)
if (needle.empty()
|| ToLower(skv.first).find(needle) != std::string::npos)
hits.emplace_back(skv.first, skv.second->size());
}
} catch (const std::exception &) {}
}
std::sort(hits.begin(), hits.end(),
[](const auto &a, const auto &b) { return NaturalLess(a.first, b.first); });
Elements result_lines;
for (const auto &h : hits)
result_lines.push_back(
text(" " + h.first + " (" + std::to_string(h.second) + " pins)"));
auto left = vbox({
text("module") | bold,
module_menu->Render() | yframe | flex,
separator(),
text("type") | bold,
type_menu->Render(),
}) | size(WIDTH, EQUAL, 28);
auto right = vbox({
hbox({text(" search: "), query_input->Render() | flex}) | border,
text(std::to_string(hits.size()) + " match(es)") | dim,
vbox(std::move(result_lines)) | yframe | flex,
}) | flex;
return vbox({
hbox({left, separator(), right}) | flex,
text(" Tab: cycle focus | Esc: leave search ") | dim,
}) | border;
});
}