// Copyright Epic Games, Inc. All Rights Reserved. #include "history_cmd.h" #include #include #include #include #include #include #include #include #include namespace zen { HistoryCommand::HistoryCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_options()("filter", "Filter by executable ('zen' or 'zenserver' / 'server')", cxxopts::value(m_Filter)->default_value("")); m_Options.add_options()("print", "Print the selected command line instead of running it", cxxopts::value(m_Print)); m_Options.add_options()("l,list", "List all invocations to stdout instead of showing the picker", cxxopts::value(m_List)); } HistoryCommand::~HistoryCommand() { } namespace { bool KeepRecord(const HistoryRecord& Rec, std::string_view Filter) { if (Filter.empty()) { return true; } if (Filter == "zen") { return Rec.Exe == "zen"; } if (Filter == "zenserver" || Filter == "server") { return Rec.Exe == "zenserver"; } return true; } std::string BuildLabel(const HistoryRecord& Rec) { std::string Exe = Rec.Exe.empty() ? std::string("?") : Rec.Exe; std::string Lbl = fmt::format("{} {:<9} pid {:<7}", Rec.Ts, Exe, Rec.Pid); if (!Rec.CmdLine.empty()) { Lbl += " "; Lbl += Rec.CmdLine; } return Lbl; } int SpawnZen(const HistoryRecord& Rec) { const std::filesystem::path TargetPath = GetRunningExecutablePath(); ZEN_CONSOLE("Running: {}", Rec.CmdLine); CreateProcOptions Opts{}; CreateProcResult Result = CreateProc(TargetPath, Rec.CmdLine, Opts); if (!Result) { throw zen::runtime_error("failed to launch '{}'", TargetPath); } ProcessHandle Proc; Proc.Initialize(Result); return Proc.WaitExitCode(); } void SpawnZenServerDetached(const HistoryRecord& Rec) { const std::filesystem::path TargetPath = GetRunningExecutablePath().parent_path() / ("zenserver" ZEN_EXE_SUFFIX_LITERAL); ZEN_CONSOLE("Launching detached: {}", Rec.CmdLine); CreateProcOptions Opts{}; Opts.Flags = CreateProcOptions::Flag_NoConsole | CreateProcOptions::Flag_NewProcessGroup; CreateProcResult Result = CreateProc(TargetPath, Rec.CmdLine, Opts); if (!Result) { throw zen::runtime_error("failed to launch '{}'", TargetPath); } #if ZEN_PLATFORM_WINDOWS // Take ownership of the handle just to release it; we do not wait. ProcessHandle Proc; Proc.Initialize(Result); ZEN_CONSOLE("Launched {} (pid {})", TargetPath, Proc.Pid()); #else ZEN_CONSOLE("Launched {} (pid {})", TargetPath, static_cast(Result)); #endif } void PrintPlainTable(const std::vector& Records) { if (Records.empty()) { ZEN_CONSOLE("No invocation history available."); return; } for (const HistoryRecord& Rec : Records) { std::string Line = fmt::format("{} {:<9} pid {:<7}", Rec.Ts, Rec.Exe, Rec.Pid); if (!Rec.CmdLine.empty()) { Line += " "; Line += Rec.CmdLine; } ZEN_CONSOLE("{}", Line); } } } // namespace void HistoryCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return; } if (m_List && m_Print) { throw OptionParseException("'--list' conflicts with '--print'", m_Options.help()); } std::vector All = ReadInvocationHistory(); std::vector Filtered; Filtered.reserve(All.size()); for (HistoryRecord& Rec : All) { if (KeepRecord(Rec, m_Filter)) { Filtered.push_back(std::move(Rec)); } } std::reverse(Filtered.begin(), Filtered.end()); if (Filtered.empty()) { ZEN_CONSOLE("No invocation history available{}.", m_Filter.empty() ? "" : " for this filter"); return; } if (m_List || !IsTuiAvailable()) { PrintPlainTable(Filtered); return; } std::vector Labels; Labels.reserve(Filtered.size()); for (const HistoryRecord& Rec : Filtered) { Labels.push_back(BuildLabel(Rec)); } int Selected = TuiPickOne("Recent invocations. Select one to re-run:", Labels); if (Selected < 0) { return; } const HistoryRecord& Pick = Filtered[Selected]; if (m_Print) { ZEN_CONSOLE("{}", Pick.CmdLine); return; } if (Pick.CmdLine.empty()) { throw zen::runtime_error("selected record has no command line"); } if (Pick.Exe == "zenserver") { SpawnZenServerDetached(Pick); return; } if (Pick.Exe == "zen") { int ExitCode = SpawnZen(Pick); if (ExitCode != 0) { throw ErrorWithReturnCode(fmt::format("zen exited with code {}", ExitCode), ExitCode); } return; } throw zen::runtime_error("unknown executable '{}' in history record", Pick.Exe); } } // namespace zen