Safe Unicode and Colors
Print the File Summary
Every visited file gets a summary, including files without matches. The terminal print interface accepts text and style changes in one call.
void ElGrepApp::printResult(const FileResult &result) const {
terminal()->print(ct::fg::BrightCyan);
printText(result.displayPath);
terminal()->printLine(
ct::fg::Default,
": "_el,
ct::fg::BrightGreen,
el::String::fromInteger(result.matchCount),
ct::fg::Default,
result.matchCount == 1 ? " match"_el : " matches"_el);
for (const auto &line : result.lines) {
printMatchingLine(line);
}
}
Highlight Every Match
Each matching line is printed once.
The stored byte ranges divide it into unmatched and matched slices, and the matched slices use a bright, bold style.
An empty regular-expression match has no text to color, so elgrep inserts a narrow marker at its position.
void ElGrepApp::printMatchingLine(const MatchingLine &line) const {
terminal()->print(ct::fg::BrightBlack, " "_el, el::String::fromInteger(line.number), ": "_el, ct::fg::Default);
auto lastEnd = std::size_t{0};
for (const auto &range : line.ranges) {
printText(
line.text.slice(el::ByteRange{el::ByteIndex::fromSizeT(lastEnd), el::ByteIndex::fromSizeT(range.begin())}));
terminal()->print(ct::fg::BrightYellow, ct::BlockAttributes{ct::BlockAttributes::Bold});
if (range.isEmpty()) {
terminal()->print("▏"_el);
} else {
printText(line.text.slice(
el::ByteRange{el::ByteIndex::fromSizeT(range.begin()), el::ByteIndex::fromSizeT(range.end())}));
}
terminal()->print(ct::Color::reset(), ct::BlockAttributes::reset());
lastEnd = range.end();
}
printText(line.text.slice(el::StringSide::Back, el::ByteIndex::fromSizeT(lastEnd)));
terminal()->writeLineBreak();
}
Before writing any source text, printText() applies the display escaping format.
Invisible and control characters become readable escape sequences instead of changing the terminal state.
void ElGrepApp::printText(const el::String &text) const {
terminal()->print(text.toEscaped(el::EscapeFormat::Display));
The terminal representation also preserves valid Unicode characters and combining sequences. Malformed UTF-8 is decoded using deterministic replacement characters.
Interactive and Redirected Output
The program entry point enables terminal support before it calls run().
1// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
2// SPDX-License-Identifier: Apache-2.0
3
4#include "ElGrepApp.hpp"
5
6#include <erbsland/cterm/Terminal.hpp>
7
8
9auto main(const int argc, char *argv[]) -> int {
10 auto app = elgrep::ElGrepApp{argc, argv};
11 app.enableTerminal();
12 if (!app.terminal()->isInteractive()) {
13 app.terminal()->setOutputMode(el::cterm::Terminal::OutputMode::BlockText);
14 }
15 return app.run();
16}
On an interactive terminal, Core renders the selected colors. For redirected output or a platform without terminal control, the entry point selects the terminal’s plain block-text mode. It omits ANSI control sequences while the search and output code remain unchanged.