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..7740da953 100644
--- a/doc/manual/src/command-ref/opt-common.md
+++ b/doc/manual/src/command-ref/opt-common.md
@@ -78,6 +78,16 @@ 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 8fcb10325..0b756301c 100644
--- a/src/libmain/common-args.cc
+++ b/src/libmain/common-args.cc
@@ -58,7 +58,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 b3466a860..b3b46fc21 100644
--- a/src/libmain/progress-bar.cc
+++ b/src/libmain/progress-bar.cc
@@ -95,8 +95,7 @@ void ProgressBar::logEI(const ErrorInfo & ei)
void ProgressBar::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);
@@ -234,10 +233,14 @@ void ProgressBar::result(ActivityId act, ResultType type, const std::vectoractivities.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);
}
}
@@ -290,60 +293,93 @@ void ProgressBar::update(State & state)
updateCV.notify_one();
}
-std::chrono::milliseconds ProgressBar::draw(State & state)
+std::chrono::milliseconds ProgressBar::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 moreActivities = 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;
+ }
+ /* 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)
+ )
+ );
}
- ++i;
- }
- if (i != state.activities.rend()) {
- line += i->s;
+ 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 moreActivities++;
}
}
}
- auto width = getWindowSize().second;
- if (width <= 0) width = std::numeric_limits::max();
+ if (printMultiline && moreActivities)
+ writeToStderr(fmt("And %d more...", moreActivities));
- 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;
}
@@ -442,9 +478,8 @@ void ProgressBar::writeToStdout(std::string_view s)
{
auto state(state_.lock());
if (state->active) {
- std::cerr << "\r\e[K";
Logger::writeToStdout(s);
- draw(*state);
+ draw(*state, {});
} else {
Logger::writeToStdout(s);
}
@@ -457,7 +492,7 @@ std::optional ProgressBar::ask(std::string_view msg)
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];
}
@@ -466,6 +501,11 @@ void ProgressBar::setPrintBuildLogs(bool printBuildLogs)
this->printBuildLogs = printBuildLogs;
}
+void ProgressBar::setPrintMultiline(bool printMultiline)
+{
+ this->printMultiline = printMultiline;
+}
+
Logger * makeProgressBar()
{
return new ProgressBar(shouldANSI());
diff --git a/src/libmain/progress-bar.hh b/src/libmain/progress-bar.hh
index 76e2ed4ff..176e941e8 100644
--- a/src/libmain/progress-bar.hh
+++ b/src/libmain/progress-bar.hh
@@ -47,6 +47,8 @@ struct ProgressBar : public Logger
std::map activitiesByType;
+ int lastLines = 0;
+
uint64_t filesLinked = 0, bytesLinked = 0;
uint64_t corruptedPaths = 0, untrustedPaths = 0;
@@ -63,6 +65,7 @@ struct ProgressBar : public Logger
std::condition_variable quitCV, updateCV;
bool printBuildLogs = false;
+ bool printMultiline = false;
bool isTTY;
ProgressBar(bool isTTY)
@@ -75,7 +78,7 @@ struct ProgressBar : public Logger
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));
}
});
@@ -114,7 +117,7 @@ struct ProgressBar : public Logger
void update(State & state);
- std::chrono::milliseconds draw(State & state);
+ std::chrono::milliseconds draw(State & state, const std::optional & s);
std::string getStatus(State & state);
@@ -123,6 +126,8 @@ struct ProgressBar : public Logger
std::optional ask(std::string_view msg) override;
void setPrintBuildLogs(bool printBuildLogs) override;
+
+ void setPrintMultiline(bool printMultiline) override;
};
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)
+ { }
};
/**