Files
essim/src/tui/tui.hpp
François 7cec6e1b0c tui: add --batch mode to run a script and print output headless
BootDispatch already runs --restore/--source synchronously before the TUI
starts (Source takes its headless drain branch when no screen is attached),
so the console buffer is complete by then. New --batch flag dumps that buffer
(Tui::DumpOutput) to stdout and exits without launching the TUI — enabling
scripted/CI runs and verify output capture (e.g. essim --batch --source s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:36:41 +02:00

290 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef _TUI_HPP_
#define _TUI_HPP_
#include <atomic>
#include <deque>
#include <functional>
#include <iosfwd>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <ftxui/component/component.hpp>
#include <ftxui/component/screen_interactive.hpp>
class System;
class Tui {
enum class Completion { None, Path, Command };
struct Prompt {
std::string question;
std::function<void(const std::string &)> on_answer;
Completion completion = Completion::None;
};
struct CommandSpec {
struct Param {
std::string name;
Completion completion = Completion::None;
};
std::vector<Param> params;
std::function<void(const std::vector<std::string> &)> action;
bool prompt_for_missing = true;
std::string description;
bool scriptable = true;
bool interactive = false; ///< opens a full-screen mode when called bare
bool hidden = false; ///< don't list in `help` (used by aliases)
};
// ---- Shell state ----
std::vector<std::string> history;
std::vector<std::string> recorded; // commands since the last 'new', for script-save
std::vector<std::string> output;
std::string input;
int cursor_pos;
int history_idx;
int scroll_offset; ///< Lines scrolled up from the tail; 0 = follow newest output.
bool quit;
bool in_source;
std::unique_ptr<System> sys;
std::deque<Prompt> pending;
std::map<std::string, CommandSpec> commands;
std::map<std::string, std::string> vars; ///< $var-style substitution table.
// ---- Screen orchestration ----
int screen_idx;
// Where Esc should send the user *next* if they came via an inter-screen
// jump (e.g. Enter on a part in `explore` → set-connector-type). 1 means
// "no back-link, Esc goes to the dashboard like usual". Always reset
// after consumption to avoid stale links across unrelated navigation.
int screen_back_idx = -1;
// ---- Connect screen state ----
std::vector<std::string> connect_modules;
int connect_m1_idx;
int connect_m2_idx;
std::string connect_p1_filter;
std::string connect_p2_filter;
std::vector<std::string> connect_p1_list;
std::vector<std::string> connect_p2_list;
int connect_p1_idx;
int connect_p2_idx;
int connect_focus_idx;
// ---- Explore screen state ----
std::vector<std::string> explore_modules;
int explore_module_idx;
std::vector<std::string> explore_types;
int explore_type_idx;
std::vector<std::string> explore_children;
int explore_child_idx;
std::string explore_child_filter;
std::string explore_detail_filter;
std::vector<std::string> explore_detail;
std::vector<std::string> explore_detail_sig; ///< parallel: `module\tsignal` per row (empty = no action)
int explore_detail_idx;
// Second detail sub-panel — populated only when the children pane is on
// the signals tab (top = local pins, bottom = net members).
std::vector<std::string> explore_detail2;
std::vector<std::string> explore_detail2_sig;
int explore_detail2_idx = 0;
std::string explore_header;
int explore_focus_idx;
// ---- Source-file loading (event-driven, one line per tick) ----
std::atomic<bool> loading; ///< true while a script is being processed; read by tick thread.
std::atomic<bool> tick_in_flight; ///< main thread acks each tick by clearing this; ticker waits.
std::string loading_filename;
std::vector<std::string> loading_lines;
size_t loading_idx;
int loading_executed;
int loading_lineno;
bool loading_prev_in_source;
ftxui::ScreenInteractive *screen_ptr; ///< set in Run() so Source() can post events.
// ---- Dashboard scroll state (0 = top; grows as the user scrolls down) ----
int dashboard_scroll_offset = 0;
// ---- Generic error-notification dialog (modal) ----
bool error_open = false;
std::string error_message;
// ---- Generic Yes/No confirmation dialog (modal) ----
bool confirm_open = false;
std::string confirm_message;
std::function<void()> confirm_on_yes;
// ---- Generic file-picker dialog (modal) ----
// Reused for any "pick a path" interaction (export, save, restore, …).
// `persist_key` ties the dir + filename to a key persisted under the
// user-data directory so the next invocation under the same key
// re-opens on the same location. `on_confirm` runs the actual action
// when the user accepts a path.
//
// Optional `filters`: a horizontal selector (Toggle) at the top of
// the dialog with (label, extension) pairs. Picking a filter rewrites
// the filename's extension to match. The action then dispatches on
// extension (the dialog stays format-agnostic).
public:
struct FilenameFilter { std::string label; std::string extension; };
private:
struct FileDialogState {
bool open = false;
std::string title;
std::string persist_key;
std::string dir;
std::string filename;
std::vector<std::string> entries; ///< rebuilt every frame
std::vector<bool> entries_is_dir;
int entry_idx = 0;
// Focus map (variable: 0=filters [if any], else +1 each next slot):
// filters present → 0=filters 1=entries 2=filename 3=button (4 slots)
// no filters → 0=entries 1=filename 2=button (3 slots)
int focus_idx = 0;
std::string status;
std::vector<FilenameFilter> filters;
std::vector<std::string> filter_labels; ///< parallel to `filters`
int filter_idx = 0;
std::function<void(const std::string &)> on_confirm;
};
FileDialogState file_dialog;
// ---- Help screen state ----
int help_topic_idx = 0;
std::vector<std::string> help_topic_names; ///< populated by BuildHelpScreen
// ---- Analyze screen state (unified verify + analyze) ----
int analyze_focus_idx = 0; ///< 0=issues 1=groups 2=types
std::vector<std::string> analyze_issues;
std::vector<std::string> analyze_groups;
std::vector<std::string> analyze_types;
int analyze_issue_idx = 0;
int analyze_group_idx = 0;
int analyze_type_idx = 0;
// ---- Command palette (global, fuzzy-find over commands + objects) ----
bool palette_open = false;
std::string palette_query;
int palette_idx = 0;
// Rebuilt every frame from <query, sys>: label shown to the user.
std::vector<std::string> palette_labels;
// Parallel kind/payload for each label. kind: 'c'=command, 'm'=module,
// 's'=signal. payload: command name / module name / "module\tsignal".
std::vector<std::pair<char, std::string>> palette_items;
// ---- Signal-type popup (shared between net + explore screens) ----
bool sigtype_dialog_open = false;
std::string sigtype_dialog_mod;
std::string sigtype_dialog_sig;
std::vector<std::string> sigtype_dialog_entries; ///< ["power","gnd","other"]
int sigtype_dialog_choice = 0;
// ---- Set-type screen state ----
std::vector<std::string> settype_modules;
int settype_m_idx;
std::string settype_p_filter;
std::vector<std::string> settype_p_list;
int settype_p_idx;
std::string settype_type;
std::string settype_status;
int settype_focus_idx;
public:
Tui();
~Tui();
void Run();
void DumpCommandsMd(std::ostream &out) const;
// Write the accumulated console output to `out`. Used by batch mode to
// surface a script's output without starting the TUI.
void DumpOutput(std::ostream &out) const;
// Boot-time hook: dispatch a single command exactly as if the user
// typed it (e.g. `restore foo.essim` or `source bring-up.essim`).
// Call before `Run()` to seed the system before the event loop starts.
void BootDispatch(const std::string &raw);
private:
// Lifecycle (commands.cpp)
void RegisterCommands();
// Per-file command-group registrators. Each adds entries to the
// `commands` map. Called from RegisterCommands().
void RegisterExportCommands(); // commands_export.cpp
// Shell (shell.cpp)
void Print(const std::string &line);
void Submit();
void Dispatch(const std::string &raw);
void Finalize(const std::string &name,
const CommandSpec &spec,
const std::vector<std::string> &args);
void HistoryUp();
void HistoryDown();
void CancelPending();
void LoadHistory();
void AppendHistory(const std::string &cmd);
void Source(const std::string &filename);
void ProcessNextSourceLine();
std::string ExpandVars(const std::string &s) const;
// Completion (completion.cpp)
void CompleteCommand(size_t start = 0);
void CompletePath(size_t start = 0);
void CompleteInline();
// Open the signal-type popup for <mod>/<sig> (no-op if names don't resolve).
void OpenSignalTypeDialog(const std::string &mod_name,
const std::string &sig_name);
// Apply the selected type to the targeted signal, record a
// `set-signal-type` line (deduping consecutive edits of the same signal),
// and close the popup.
void ApplySignalTypeChoice();
// Filtered part list rebuild (used by connect & set-connector-type screens)
void RefreshFilteredPartList(const std::vector<std::string> &modules,
int m_idx,
const std::string &filter,
std::vector<std::string> &out,
int &sel_idx);
// Screen builders (screen_*.cpp)
ftxui::Component BuildMainScreen(ftxui::ScreenInteractive &screen);
ftxui::Component BuildConnectScreen();
ftxui::Component BuildSettypeScreen();
ftxui::Component BuildExploreScreen();
ftxui::Component BuildDashboardScreen();
ftxui::Component BuildAnalyzeScreen();
ftxui::Component BuildHelpScreen();
ftxui::Component BuildFileDialog();
ftxui::Component BuildErrorModal();
ftxui::Component BuildConfirmModal();
// Pop a centred modal with `msg` and an OK button. Esc / Enter close
// it. Use for actionable failures the user must see (write errors,
// bad inputs, etc.) — for normal feedback keep `Print()`.
void ShowError(const std::string &msg);
// Pop a centred Yes/No modal. `on_yes` runs only if the user picks
// Yes (Esc and No cancel). Use for destructive confirmations
// (overwrite a file, delete a snapshot, …).
void ShowConfirm(const std::string &msg, std::function<void()> on_yes);
// Open the picker modal. `persist_key` controls where the last-used
// dir + filename are stored (one tiny file per key under the user
// data directory). `on_confirm` runs when the user presses Enter on
// the action button — it receives the absolute path the user picked.
void OpenFileDialog(std::string title,
std::string persist_key,
std::string default_filename,
std::vector<FilenameFilter> filters,
std::function<void(const std::string &)> on_confirm);
void ConfirmFileDialog();
ftxui::Component BuildSignalTypeModal();
ftxui::Component BuildPaletteModal();
// Open palette (resets query/index, builds initial list).
void OpenPalette();
// Execute the currently-highlighted palette entry.
void ActivatePaletteEntry();
};
#endif // _TUI_HPP_