diff --git a/doc/manual/change-authors.yml b/doc/manual/change-authors.yml index c56f588ca..5a7b8117e 100644 --- a/doc/manual/change-authors.yml +++ b/doc/manual/change-authors.yml @@ -60,6 +60,10 @@ jade: forgejo: jade github: lf- +kloenk: + forgejo: kloenk + github: kloenk + lovesegfault: github: lovesegfault diff --git a/doc/manual/rl-next/multiline-log-format.md b/doc/manual/rl-next/multiline-log-format.md new file mode 100644 index 000000000..62bb9f1e7 --- /dev/null +++ b/doc/manual/rl-next/multiline-log-format.md @@ -0,0 +1,14 @@ +--- +synopsis: Add log formats `multiline` and `multiline-with-logs` +cls: [1369] +credits: [kloenk] +category: Improvements +--- + +Added two new log formats (`multiline` and `multiline-with-logs`) that display +current activities below each other for better visibility. + +These formats attempt to use the maximum available lines +(defaulting to 25 if unable to determine) and print up to that many lines. +The status bar is displayed as the first line, with each subsequent +activity on its own line. diff --git a/doc/manual/src/command-ref/opt-common.md b/doc/manual/src/command-ref/opt-common.md index cd289b7ea..8bb05ddbb 100644 --- a/doc/manual/src/command-ref/opt-common.md +++ b/doc/manual/src/command-ref/opt-common.md @@ -78,6 +78,15 @@ Most commands in Lix accept the following command-line options: Display the raw logs, with the progress bar at the bottom. + - `multiline` + + Display a progress bar during the builds and in the lines below that one line per activity. + + + - `multiline-with-logs` + + Displayes the raw logs, with a progress bar and activities each in a new line at the bottom. + - [`--no-build-output`](#opt-no-build-output) / `-Q` By default, output written by builders to standard output and standard error is echoed to the Lix command's standard error. diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc index 20b5befe4..41a800bc5 100644 --- a/src/libmain/common-args.cc +++ b/src/libmain/common-args.cc @@ -57,7 +57,7 @@ MixCommonArgs::MixCommonArgs(const std::string & programName) addFlag({ .longName = "log-format", - .description = "Set the format of log output; one of `raw`, `internal-json`, `bar` or `bar-with-logs`.", + .description = "Set the format of log output; one of `raw`, `internal-json`, `bar`, `bar-with-logs`, `multiline` or `multiline-with-logs`.", .category = loggingCategory, .labels = {"format"}, .handler = {[](std::string format) { setLogFormat(format); }}, diff --git a/src/libmain/loggers.cc b/src/libmain/loggers.cc index 80080d616..8c3c4e355 100644 --- a/src/libmain/loggers.cc +++ b/src/libmain/loggers.cc @@ -17,6 +17,10 @@ LogFormat parseLogFormat(const std::string & logFormatStr) { return LogFormat::bar; else if (logFormatStr == "bar-with-logs") return LogFormat::barWithLogs; + else if (logFormatStr == "multiline") + return LogFormat::multiline; + else if (logFormatStr == "multiline-with-logs") + return LogFormat::multilineWithLogs; throw Error("option 'log-format' has an invalid value '%s'", logFormatStr); } @@ -35,6 +39,17 @@ Logger * makeDefaultLogger() { logger->setPrintBuildLogs(true); return logger; } + case LogFormat::multiline: { + auto logger = makeProgressBar(); + logger->setPrintMultiline(true); + return logger; + } + case LogFormat::multilineWithLogs: { + auto logger = makeProgressBar(); + logger->setPrintMultiline(true); + logger->setPrintBuildLogs(true); + return logger; + } default: abort(); } diff --git a/src/libmain/loggers.hh b/src/libmain/loggers.hh index e5721420c..6064b6160 100644 --- a/src/libmain/loggers.hh +++ b/src/libmain/loggers.hh @@ -11,6 +11,8 @@ enum class LogFormat { internalJSON, bar, barWithLogs, + multiline, + multilineWithLogs, }; void setLogFormat(const std::string & logFormatStr); diff --git a/src/libmain/progress-bar.cc b/src/libmain/progress-bar.cc index d83b09cd4..4ac9f72bd 100644 --- a/src/libmain/progress-bar.cc +++ b/src/libmain/progress-bar.cc @@ -73,6 +73,8 @@ private: std::map activitiesByType; + int lastLines = 0; + uint64_t filesLinked = 0, bytesLinked = 0; uint64_t corruptedPaths = 0, untrustedPaths = 0; @@ -89,6 +91,7 @@ private: std::condition_variable quitCV, updateCV; bool printBuildLogs = false; + bool printMultiline = false; bool isTTY; public: @@ -103,7 +106,7 @@ public: while (state->active) { if (!state->haveUpdate) state.wait_for(updateCV, nextWakeup); - nextWakeup = draw(*state); + nextWakeup = draw(*state, {}); state.wait_for(quitCV, std::chrono::milliseconds(50)); } }); @@ -165,8 +168,7 @@ public: void log(State & state, Verbosity lvl, std::string_view s) { if (state.active) { - writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n"); - draw(state); + draw(state, s); } else { auto s2 = s + ANSI_NORMAL "\n"; if (!isTTY) s2 = filterANSIEscapes(s2, true); @@ -298,10 +300,14 @@ public: } log(*state, lvlInfo, ANSI_FAINT + info.name.value_or("unnamed") + suffix + ANSI_NORMAL + lastLine); } else { - state->activities.erase(i->second); - info.lastLine = lastLine; - state->activities.emplace_back(info); - i->second = std::prev(state->activities.end()); + if (!printMultiline) { + state->activities.erase(i->second); + info.lastLine = lastLine; + state->activities.emplace_back(info); + i->second = std::prev(state->activities.end()); + } else { + i->second->lastLine = lastLine; + } update(*state); } } @@ -354,60 +360,97 @@ public: updateCV.notify_one(); } - std::chrono::milliseconds draw(State & state) + std::chrono::milliseconds draw(State & state, const std::optional & s) { auto nextWakeup = A_LONG_TIME; state.haveUpdate = false; if (state.paused || !state.active) return nextWakeup; - std::string line; + auto windowSize = getWindowSize(); + auto width = windowSize.second; + if (width <= 0) { + width = std::numeric_limits::max(); + } + if (printMultiline && (state.lastLines >= 1)) { + // FIXME: make sure this works on windows + writeToStderr(fmt("\e[G\e[%dF\e[J", state.lastLines)); + } + + state.lastLines = 0; + + if (s != std::nullopt) + writeToStderr("\r\e[K" + filterANSIEscapes(s.value(), !isTTY) + ANSI_NORMAL "\n"); + + std::string line; std::string status = getStatus(state); if (!status.empty()) { line += '['; line += status; line += "]"; } + if (printMultiline && !line.empty()) { + writeToStderr(filterANSIEscapes(line, false, width) + "\n"); + state.lastLines++; + } + auto height = windowSize.first > 0 ? windowSize.first : 25; + auto moreBuilds = 0; auto now = std::chrono::steady_clock::now(); + std::string activity_line; if (!state.activities.empty()) { - if (!status.empty()) line += " "; - auto i = state.activities.rbegin(); - - while (i != state.activities.rend()) { - if (i->visible && (!i->s.empty() || !i->lastLine.empty())) { - /* Don't show activities until some time has - passed, to avoid displaying very short - activities. */ - auto delay = std::chrono::milliseconds(10); - if (i->startTime + delay < now) - break; - else - nextWakeup = std::min(nextWakeup, std::chrono::duration_cast(delay - (now - i->startTime))); + for (auto i = state.activities.begin(); i != state.activities.end(); ++i) { + if (!(i->visible && (!i->s.empty() || !i->lastLine.empty()))) { + continue; } - ++i; - } - if (i != state.activities.rend()) { - line += i->s; + /* Don't show activities until some time has + passed, to avoid displaying very short + activities. */ + auto delay = std::chrono::milliseconds(10); + if (i->startTime + delay >= now) { + nextWakeup = std::min( + nextWakeup, + std::chrono::duration_cast( + delay - (now - i->startTime) + ) + ); + } + + activity_line = i->s; + if (!i->phase.empty()) { - line += " ("; - line += i->phase; - line += ")"; + activity_line += " ("; + activity_line += i->phase; + activity_line += ")"; } if (!i->lastLine.empty()) { - if (!i->s.empty()) line += ": "; - line += i->lastLine; + if (!i->s.empty()) { + activity_line += ": "; + } + activity_line += i->lastLine; + } + if (printMultiline) { + if (state.lastLines < (height - 1)) { + writeToStderr(filterANSIEscapes(activity_line, false, width) + "\n"); + state.lastLines++; + } else { + moreBuilds++; + } } } } - auto width = getWindowSize().second; - if (width <= 0) width = std::numeric_limits::max(); + if (printMultiline && moreBuilds) { + writeToStderr(fmt("And %d more...", moreBuilds)); + } - writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K"); + if (!printMultiline) { + line += " " + activity_line; + writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K"); + } return nextWakeup; } @@ -506,9 +549,8 @@ public: { auto state(state_.lock()); if (state->active) { - std::cerr << "\r\e[K"; Logger::writeToStdout(s); - draw(*state); + draw(*state, {}); } else { Logger::writeToStdout(s); } @@ -521,7 +563,7 @@ public: std::cerr << fmt("\r\e[K%s ", msg); auto s = trim(readLine(STDIN_FILENO)); if (s.size() != 1) return {}; - draw(*state); + draw(*state, {}); return s[0]; } @@ -529,6 +571,11 @@ public: { this->printBuildLogs = printBuildLogs; } + + void setPrintMultiline(bool printMultiline) override + { + this->printMultiline = printMultiline; + } }; Logger * makeProgressBar() diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index 64be8bc62..115b979f8 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -114,6 +114,9 @@ public: virtual void setPrintBuildLogs(bool printBuildLogs) { } + + virtual void setPrintMultiline(bool printMultiline) + { } }; /**