Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 : : * Author: Thun Lu <thun.lu@zohomail.cn>
4 : : * Repo: https://github.com/thun-res/vlink
5 : : * _ __ __ _ __
6 : : * | | / / / / (_) ____ / /__
7 : : * | | / / / / / / / __ \ / //_/
8 : : * | |/ / / /___ / / / / / / / ,<
9 : : * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 : : *
11 : : * Licensed under the Apache License, Version 2.0 (the "License");
12 : : * you may not use this file except in compliance with the License.
13 : : * You may obtain a copy of the License at
14 : : *
15 : : * http://www.apache.org/licenses/LICENSE-2.0
16 : : *
17 : : * Unless required by applicable law or agreed to in writing, software
18 : : * distributed under the License is distributed on an "AS IS" BASIS,
19 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 : : * See the License for the specific language governing permissions and
21 : : * limitations under the License.
22 : : */
23 : :
24 : : #include "./base/terminal_stream.h"
25 : :
26 : : #include <cerrno>
27 : : #include <cstdio>
28 : : #include <cstring>
29 : : #include <limits>
30 : : #include <mutex>
31 : : #include <ostream>
32 : : #include <string>
33 : : #include <string_view>
34 : :
35 : : #include "./base/utils.h"
36 : :
37 : : #ifdef _WIN32
38 : : #include <Windows.h>
39 : : #include <io.h>
40 : : #undef min
41 : : #undef max
42 : : #undef GetMessage
43 : : #define VLINK_TERM_STDOUT_FILENO _fileno(stdout)
44 : : #define VLINK_TERM_WRITE _write
45 : : #define VLINK_TERM_ISATTY _isatty
46 : : #define VLINK_TERM_WRITE_LEN(n) static_cast<unsigned int>((n) > 0x7FFFFFFFU ? 0x7FFFFFFFU : (n))
47 : : #else
48 : : #include <termios.h>
49 : : #include <unistd.h>
50 : : #define VLINK_TERM_STDOUT_FILENO STDOUT_FILENO
51 : : #define VLINK_TERM_WRITE ::write
52 : : #define VLINK_TERM_ISATTY ::isatty
53 : : #define VLINK_TERM_WRITE_LEN(n) ((n) > 0x7FFFFFFFU ? size_t{0x7FFFFFFFU} : (n))
54 : : #endif
55 : :
56 : : namespace vlink {
57 : :
58 : : // TerminalProbeBuffer
59 : : class TerminalProbeBuffer final : public std::streambuf {
60 : : public:
61 : 1 : [[nodiscard]] bool wrote() const noexcept { return wrote_; }
62 : :
63 : 1 : [[nodiscard]] bool flushed() const noexcept { return flushed_; }
64 : :
65 : : protected:
66 : 1 : int_type overflow(int_type value) override {
67 [ + - ]: 1 : if (!traits_type::eq_int_type(value, traits_type::eof())) {
68 : 1 : wrote_ = true;
69 : : }
70 : :
71 : 1 : return traits_type::not_eof(value);
72 : : }
73 : :
74 : 0 : std::streamsize xsputn(const char*, std::streamsize count) override {
75 [ # # # # ]: 0 : wrote_ = wrote_ || count > 0;
76 : 0 : return count;
77 : : }
78 : :
79 : 1 : int sync() override {
80 : 1 : flushed_ = true;
81 : 1 : return 0;
82 : : }
83 : :
84 : : private:
85 : : bool wrote_{false};
86 : : bool flushed_{false};
87 : : };
88 : :
89 : : // TerminalStream
90 : 26 : TerminalStream& TerminalStream::get() noexcept {
91 [ + + + - ]: 26 : static TerminalStream instance;
92 : :
93 : 26 : return instance;
94 : : }
95 : :
96 : 6 : TerminalStream& TerminalStream::endl(TerminalStream& stream) noexcept {
97 : 6 : std::lock_guard lock(stream.mutex_);
98 : 6 : stream.write_to_buffer("\n", 1);
99 : 6 : stream.flush_unlocked();
100 : :
101 : 12 : return stream;
102 : 6 : }
103 : :
104 : 1 : TerminalStream& TerminalStream::flush_manip(TerminalStream& stream) noexcept {
105 : 1 : std::lock_guard lock(stream.mutex_);
106 : 1 : stream.flush_unlocked();
107 : :
108 : 2 : return stream;
109 : 1 : }
110 : :
111 : 1 : TerminalStream::TerminalStream() noexcept : buffer_(kDefaultBufferSize) {}
112 : :
113 : 1 : TerminalStream::~TerminalStream() noexcept {
114 : : #ifdef _WIN32
115 : : if (Utils::is_terminating()) {
116 : : return;
117 : : }
118 : : #endif
119 : :
120 : 1 : std::lock_guard lock(mutex_);
121 : 1 : flush_unlocked();
122 : 1 : }
123 : :
124 : 9 : void TerminalStream::init() noexcept {
125 [ + + ]: 9 : if (initialized_.load(std::memory_order_acquire)) {
126 : 8 : return;
127 : : }
128 : :
129 : 1 : std::lock_guard lock(mutex_);
130 : :
131 [ - + ]: 1 : if (initialized_.load(std::memory_order_relaxed)) {
132 : 0 : return;
133 : : }
134 : :
135 : 1 : fd_ = VLINK_TERM_STDOUT_FILENO;
136 : :
137 : : #ifdef _WIN32
138 : : HANDLE h_out = GetStdHandle(STD_OUTPUT_HANDLE);
139 : :
140 : : if (h_out != INVALID_HANDLE_VALUE) {
141 : : DWORD dw_mode = 0;
142 : :
143 : : if (GetConsoleMode(h_out, &dw_mode)) {
144 : : dw_mode |= 0x0004; // ENABLE_VIRTUAL_TERMINAL_PROCESSING
145 : : SetConsoleMode(h_out, dw_mode);
146 : : }
147 : : }
148 : : #endif
149 : :
150 : 1 : is_tty_.store(VLINK_TERM_ISATTY(fd_) != 0, std::memory_order_release);
151 : 1 : initialized_.store(true, std::memory_order_release);
152 [ + - ]: 1 : }
153 : :
154 : 1 : bool TerminalStream::is_tty() const noexcept { return is_tty_.load(std::memory_order_acquire); }
155 : :
156 : 1 : bool TerminalStream::is_initialized() const noexcept { return initialized_.load(std::memory_order_acquire); }
157 : :
158 : 3 : void TerminalStream::flush() {
159 [ + - ]: 3 : std::lock_guard lock(mutex_);
160 : 3 : flush_unlocked();
161 : 3 : }
162 : :
163 : 2 : TerminalStream& TerminalStream::write_raw(const char* data, size_t len) noexcept {
164 : 2 : std::lock_guard lock(mutex_);
165 : 2 : write_to_buffer(data, len);
166 : :
167 : 4 : return *this;
168 : 2 : }
169 : :
170 : 2 : TerminalStream& TerminalStream::operator<<(char c) noexcept {
171 : 2 : std::lock_guard lock(mutex_);
172 : 2 : write_to_buffer(&c, 1);
173 : :
174 : 4 : return *this;
175 : 2 : }
176 : :
177 : 10 : TerminalStream& TerminalStream::operator<<(const char* str) noexcept {
178 [ + + ]: 10 : if VUNLIKELY (str == nullptr) {
179 : 1 : return *this;
180 : : }
181 : :
182 : 9 : std::lock_guard lock(mutex_);
183 : 9 : write_to_buffer(str, std::strlen(str));
184 : :
185 : 9 : return *this;
186 : 9 : }
187 : :
188 : 1 : TerminalStream& TerminalStream::operator<<(const std::string& str) noexcept {
189 : 1 : std::lock_guard lock(mutex_);
190 : 1 : write_to_buffer(str.data(), str.size());
191 : :
192 : 2 : return *this;
193 : 1 : }
194 : :
195 : 1 : TerminalStream& TerminalStream::operator<<(std::string_view str) noexcept {
196 : 1 : std::lock_guard lock(mutex_);
197 : 1 : write_to_buffer(str.data(), str.size());
198 : :
199 : 2 : return *this;
200 : 1 : }
201 : :
202 [ + + ]: 2 : TerminalStream& TerminalStream::operator<<(bool value) noexcept { return *this << (value ? "true" : "false"); }
203 : :
204 : 1 : TerminalStream& TerminalStream::operator<<(short value) noexcept { // NOLINT(runtime/int,google-runtime-int)
205 : 1 : return write_signed(static_cast<long long>(value)); // NOLINT(runtime/int,google-runtime-int)
206 : : }
207 : :
208 : 1 : TerminalStream& TerminalStream::operator<<(unsigned short value) noexcept { // NOLINT(runtime/int,google-runtime-int)
209 : 1 : return write_unsigned(static_cast<unsigned long long>(value)); // NOLINT(runtime/int,google-runtime-int)
210 : : }
211 : :
212 : 6 : TerminalStream& TerminalStream::operator<<(int value) noexcept { // NOLINT(runtime/int,google-runtime-int)
213 : 6 : return write_signed(static_cast<long long>(value)); // NOLINT(runtime/int,google-runtime-int)
214 : : }
215 : :
216 : 1 : TerminalStream& TerminalStream::operator<<(unsigned int value) noexcept { // NOLINT(runtime/int,google-runtime-int)
217 : 1 : return write_unsigned(static_cast<unsigned long long>(value)); // NOLINT(runtime/int,google-runtime-int)
218 : : }
219 : :
220 : 1 : TerminalStream& TerminalStream::operator<<(long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
221 : 1 : return write_signed(static_cast<long long>(value)); // NOLINT(runtime/int,google-runtime-int)
222 : : }
223 : :
224 : 1 : TerminalStream& TerminalStream::operator<<(unsigned long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
225 : 1 : return write_unsigned(static_cast<unsigned long long>(value)); // NOLINT(runtime/int,google-runtime-int)
226 : : }
227 : :
228 : 3 : TerminalStream& TerminalStream::operator<<(long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
229 : 3 : return write_signed(value); // NOLINT(runtime/int,google-runtime-int)
230 : : }
231 : :
232 : 1 : TerminalStream& TerminalStream::operator<<(
233 : : unsigned long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
234 : 1 : return write_unsigned(value);
235 : : }
236 : :
237 : 2 : TerminalStream& TerminalStream::operator<<(float value) noexcept { return write_double(static_cast<double>(value)); }
238 : :
239 : 1 : TerminalStream& TerminalStream::operator<<(double value) noexcept { return write_double(value); }
240 : :
241 : 1 : TerminalStream& TerminalStream::operator<<(long double value) noexcept { // NOLINT(google-runtime-float)
242 : 1 : return write_long_double(value);
243 : : }
244 : :
245 : 2 : TerminalStream& TerminalStream::operator<<(const void* ptr) noexcept {
246 : 2 : std::lock_guard lock(mutex_);
247 : :
248 : : char buf[32];
249 : 2 : int len = std::snprintf(buf, sizeof(buf), "%p", ptr);
250 : :
251 [ + - ]: 2 : if VLIKELY (len > 0) {
252 : 2 : write_to_buffer(buf, static_cast<size_t>(len));
253 : : }
254 : :
255 : 4 : return *this;
256 : 2 : }
257 : :
258 : 7 : TerminalStream& TerminalStream::operator<<(ManipType manip) noexcept { return manip(*this); }
259 : :
260 : 1 : TerminalStream& TerminalStream::operator<<(std::ostream& (*manip)(std::ostream&)) noexcept {
261 : 1 : TerminalProbeBuffer probe_buffer;
262 : :
263 : 1 : std::ostream probe_stream(&probe_buffer);
264 : :
265 : 1 : manip(probe_stream);
266 : :
267 : 1 : std::lock_guard lock(mutex_);
268 : :
269 [ + - + - : 1 : if (!probe_buffer.flushed() || probe_buffer.wrote()) {
+ - ]
270 : 1 : write_to_buffer("\n", 1);
271 : : }
272 : :
273 : 1 : flush_unlocked();
274 : :
275 : 2 : return *this;
276 : 1 : }
277 : :
278 : 1 : int TerminalStream::default_stdout_fd() noexcept { return VLINK_TERM_STDOUT_FILENO; }
279 : :
280 : 12 : void TerminalStream::flush_unlocked() noexcept {
281 [ + + ]: 12 : if (write_pos_ == 0) {
282 : 3 : return;
283 : : }
284 : :
285 : 9 : const char* data = buffer_.data();
286 : 9 : size_t remaining = write_pos_;
287 : 9 : size_t flushed = 0;
288 : :
289 [ + + ]: 18 : while (remaining > 0) {
290 [ - + ]: 9 : auto written = VLINK_TERM_WRITE(fd_, data, VLINK_TERM_WRITE_LEN(remaining));
291 : :
292 [ - + ]: 9 : if VUNLIKELY (written < 0) {
293 : : if VUNLIKELY (errno == EINTR) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
294 : : continue; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
295 : : }
296 : :
297 : : break; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
298 : : }
299 : :
300 [ - + ]: 9 : if VUNLIKELY (written == 0) {
301 : : break; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
302 : : }
303 : :
304 : 9 : const auto written_size = static_cast<size_t>(written);
305 : 9 : data += written_size;
306 : 9 : remaining -= written_size;
307 : 9 : flushed += written_size;
308 : : }
309 : :
310 [ - + ]: 9 : if (remaining > 0) {
311 : : // LCOV_EXCL_START GCOVR_EXCL_START
312 : : if (flushed > 0) {
313 : : std::memmove(buffer_.data(), buffer_.data() + flushed, remaining);
314 : : }
315 : :
316 : : write_pos_ = remaining;
317 : :
318 : : return;
319 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
320 : : }
321 : :
322 : 9 : write_pos_ = 0;
323 : :
324 : : #ifndef _WIN32
325 : :
326 [ - + ]: 9 : if (is_tty_.load(std::memory_order_acquire)) {
327 : : ::tcdrain(fd_); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
328 : : }
329 : : #endif
330 : : }
331 : :
332 : 43 : void TerminalStream::write_to_buffer(const char* data, size_t len) noexcept {
333 [ + + ]: 43 : if VUNLIKELY (len == 0) {
334 : 1 : return;
335 : : }
336 : :
337 [ - + ]: 42 : if (len >= buffer_.size()) {
338 : : // LCOV_EXCL_START GCOVR_EXCL_START
339 : : flush_unlocked();
340 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
341 : : }
342 : :
343 [ - + - - : 42 : if (len >= buffer_.size() && write_pos_ == 0) {
- + ]
344 : : // LCOV_EXCL_START GCOVR_EXCL_START
345 : : size_t remaining = len;
346 : : while (remaining > 0) {
347 : : auto written = VLINK_TERM_WRITE(fd_, data, VLINK_TERM_WRITE_LEN(remaining));
348 : :
349 : : if VUNLIKELY (written < 0) {
350 : : if VUNLIKELY (errno == EINTR) {
351 : : continue;
352 : : }
353 : :
354 : : break;
355 : : }
356 : :
357 : : if VUNLIKELY (written == 0) {
358 : : break;
359 : : }
360 : :
361 : : const auto written_size = static_cast<size_t>(written);
362 : : data += written_size;
363 : : remaining -= written_size;
364 : : }
365 : :
366 : : if (remaining > 0) {
367 : : if (remaining > buffer_.size()) {
368 : : try {
369 : : buffer_.resize(remaining);
370 : : } catch (...) {
371 : : return;
372 : : }
373 : : }
374 : :
375 : : std::memcpy(buffer_.data(), data, remaining);
376 : : write_pos_ = remaining;
377 : : }
378 : :
379 : : return;
380 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
381 : : }
382 : :
383 [ - + ]: 42 : if (write_pos_ + len > buffer_.size()) {
384 : : flush_unlocked(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
385 : : }
386 : :
387 [ - + ]: 42 : if (write_pos_ + len > buffer_.size()) {
388 : : // LCOV_EXCL_START GCOVR_EXCL_START
389 : : try {
390 : : buffer_.resize(write_pos_ + len);
391 : : } catch (...) {
392 : : return;
393 : : }
394 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
395 : : }
396 : :
397 : 42 : std::memcpy(buffer_.data() + write_pos_, data, len);
398 : 42 : write_pos_ += len;
399 : : }
400 : :
401 : 11 : TerminalStream& TerminalStream::write_signed(long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
402 : 11 : std::lock_guard lock(mutex_);
403 : :
404 : : char buf[32];
405 : 11 : char* ptr = buf + sizeof(buf);
406 : 11 : bool negative = false;
407 : :
408 [ + + ]: 11 : if (value < 0) {
409 : 4 : negative = true;
410 : :
411 [ + + ]: 4 : if VUNLIKELY (value == std::numeric_limits<long long>::min()) { // NOLINT(runtime/int,google-runtime-int)
412 : 1 : int len = std::snprintf(buf, sizeof(buf), "%lld", value);
413 : :
414 [ + - ]: 1 : if VLIKELY (len > 0) {
415 : 1 : write_to_buffer(buf, static_cast<size_t>(len));
416 : : }
417 : :
418 : 1 : return *this;
419 : : }
420 : :
421 : 3 : value = -value;
422 : : }
423 : :
424 [ + + ]: 10 : if (value == 0) {
425 : 1 : *--ptr = '0';
426 : : } else {
427 [ + + ]: 71 : while (value != 0) {
428 : 62 : *--ptr = '0' + static_cast<char>(value % 10);
429 : 62 : value /= 10;
430 : : }
431 : : }
432 : :
433 [ + + ]: 10 : if (negative) {
434 : 3 : *--ptr = '-';
435 : : }
436 : :
437 : 10 : write_to_buffer(ptr, static_cast<size_t>(buf + sizeof(buf) - ptr));
438 : :
439 : 10 : return *this;
440 : 11 : }
441 : :
442 : 4 : TerminalStream& TerminalStream::write_unsigned(
443 : : unsigned long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
444 : 4 : std::lock_guard lock(mutex_);
445 : :
446 : : char buf[32];
447 : 4 : char* ptr = buf + sizeof(buf);
448 : :
449 [ - + ]: 4 : if (value == 0) {
450 : 0 : *--ptr = '0';
451 : : } else {
452 [ + + ]: 34 : while (value != 0) {
453 : 30 : *--ptr = '0' + static_cast<char>(value % 10);
454 : 30 : value /= 10;
455 : : }
456 : : }
457 : :
458 : 4 : write_to_buffer(ptr, static_cast<size_t>(buf + sizeof(buf) - ptr));
459 : :
460 : 8 : return *this;
461 : 4 : }
462 : :
463 : 3 : TerminalStream& TerminalStream::write_double(double value) noexcept {
464 : 3 : std::lock_guard lock(mutex_);
465 : :
466 : : char buf[64];
467 : 3 : int len = std::snprintf(buf, sizeof(buf), "%g", value);
468 : :
469 [ + - ]: 3 : if VLIKELY (len > 0) {
470 : 3 : write_to_buffer(buf, static_cast<size_t>(len));
471 : : }
472 : :
473 : 6 : return *this;
474 : 3 : }
475 : :
476 : : // NOLINTNEXTLINE(google-runtime-float)
477 : 1 : TerminalStream& TerminalStream::write_long_double(long double value) noexcept {
478 : 1 : std::lock_guard lock(mutex_);
479 : :
480 : : char buf[64];
481 : 1 : int len = std::snprintf(buf, sizeof(buf), "%Lg", value);
482 : :
483 [ + - ]: 1 : if VLIKELY (len > 0) {
484 : 1 : write_to_buffer(buf, static_cast<size_t>(len));
485 : : }
486 : :
487 : 2 : return *this;
488 : 1 : }
489 : :
490 : : } // namespace vlink
|