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>
This commit is contained in:
111
src/tui/completion.cpp
Normal file
111
src/tui/completion.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user