Files
essim/src/tui/completion.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

112 lines
3.4 KiB
C++

#include "tui/tui.hpp"
#include "tui/tui_helpers.hpp"
#include <cctype>
#include <cstdlib>
#include <filesystem>
void Tui::CompleteCommand(size_t start) {
std::string current = input.substr(start);
std::vector<std::string> matches;
for (const auto &kv : commands)
if (kv.first.rfind(current, 0) == 0) matches.push_back(kv.first);
if (matches.empty()) return;
auto replace_with = [&](const std::string &replacement) {
input.replace(start, std::string::npos, replacement);
cursor_pos = (int)input.size();
};
if (matches.size() == 1) { replace_with(matches[0]); return; }
std::string lcp = LongestCommonPrefix(matches);
if (lcp.size() > current.size()) { replace_with(lcp); return; }
std::string line = " ";
for (const auto &m : matches) line += " " + m;
Print(line);
}
void Tui::CompletePath(size_t start) {
namespace fs = std::filesystem;
std::string current = input.substr(start);
auto pos = current.rfind('/');
std::string disp, prefix;
if (pos == std::string::npos) { disp = ""; prefix = current; }
else { disp = current.substr(0, pos + 1); prefix = current.substr(pos + 1); }
std::string resolved = disp.empty() ? "." : disp;
if (!resolved.empty() && resolved[0] == '~') {
if (const char *home = std::getenv("HOME"))
resolved = std::string(home) + resolved.substr(1);
}
std::vector<std::string> names;
std::vector<bool> is_dir;
try {
for (const auto &e : fs::directory_iterator(resolved)) {
std::string n = e.path().filename().string();
if (n.rfind(prefix, 0) == 0) {
names.push_back(n);
is_dir.push_back(e.is_directory());
}
}
} catch (const std::exception &) {
return;
}
if (names.empty()) return;
auto replace_with = [&](const std::string &replacement) {
input.replace(start, std::string::npos, replacement);
cursor_pos = (int)input.size();
};
if (names.size() == 1) {
replace_with(disp + names[0] + (is_dir[0] ? "/" : ""));
return;
}
std::string lcp = LongestCommonPrefix(names);
if (lcp.size() > prefix.size()) { replace_with(disp + lcp); return; }
std::string line = " ";
for (size_t i = 0; i < names.size(); ++i)
line += " " + names[i] + (is_dir[i] ? "/" : "");
Print(line);
}
void Tui::CompleteInline() {
bool ends_with_ws = !input.empty()
&& std::isspace((unsigned char)input.back());
size_t arg_start;
if (input.empty() || ends_with_ws) {
arg_start = input.size();
} else {
size_t i = input.size();
while (i > 0 && !std::isspace((unsigned char)input[i - 1])) --i;
arg_start = i;
}
auto preceding = Tokenize(input.substr(0, arg_start));
int arg_index = (int)preceding.size();
if (arg_index == 0) { CompleteCommand(); return; }
if (preceding.empty()) return;
auto cmd_it = commands.find(preceding[0]);
if (cmd_it == commands.end()) return;
const auto &spec = cmd_it->second;
int param_idx = arg_index - 1;
if (param_idx >= (int)spec.params.size()) return;
switch (spec.params[param_idx].completion) {
case Completion::Path: CompletePath(arg_start); break;
case Completion::Command: CompleteCommand(arg_start); break;
case Completion::None: break;
}
}