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..20e7ebae6 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);
@@ -354,60 +356,100 @@ 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();
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;
+ }
+
+ /* 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)
+ )
+ );
+ }
+
+ if (printMultiline) {
+ line = i->s;
+ } else {
+ line += " ";
+ line += i->s;
}
- ++i;
- }
- if (i != state.activities.rend()) {
- line += i->s;
if (!i->phase.empty()) {
line += " (";
line += i->phase;
line += ")";
}
if (!i->lastLine.empty()) {
- if (!i->s.empty()) line += ": ";
+ if (!i->s.empty()) {
+ line += ": ";
+ }
line += i->lastLine;
}
+ if (printMultiline) {
+ if (state.lastLines < (height - 1)) {
+ writeToStderr(filterANSIEscapes(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) {
+ writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
+ }
return nextWakeup;
}
@@ -506,9 +548,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 +562,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 +570,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)
+ { }
};
/**