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 : : #ifdef _WIN32
36 : : #include <Windows.h>
37 : : #include <io.h>
38 : : #undef min
39 : : #undef max
40 : : #undef GetMessage
41 : : #define VLINK_TERM_STDOUT_FILENO _fileno(stdout)
42 : : #define VLINK_TERM_WRITE _write
43 : : #define VLINK_TERM_ISATTY _isatty
44 : : #else
45 : : #include <termios.h>
46 : : #include <unistd.h>
47 : : #define VLINK_TERM_STDOUT_FILENO STDOUT_FILENO
48 : : #define VLINK_TERM_WRITE ::write
49 : : #define VLINK_TERM_ISATTY ::isatty
50 : : #endif
51 : :
52 : : namespace vlink {
53 : :
54 : : // TerminalStream
55 : 26 : TerminalStream& TerminalStream::get() noexcept {
56 [ + + + - ]: 26 : static TerminalStream instance;
57 : :
58 : 26 : return instance;
59 : : }
60 : :
61 : 6 : TerminalStream& TerminalStream::endl(TerminalStream& stream) noexcept {
62 : 6 : std::lock_guard lock(stream.mutex_);
63 : 6 : stream.write_to_buffer("\n", 1);
64 : 6 : stream.flush_unlocked();
65 : :
66 : 12 : return stream;
67 : 6 : }
68 : :
69 : 1 : TerminalStream& TerminalStream::flush_manip(TerminalStream& stream) noexcept {
70 : 1 : std::lock_guard lock(stream.mutex_);
71 : 1 : stream.flush_unlocked();
72 : :
73 : 2 : return stream;
74 : 1 : }
75 : :
76 : 1 : TerminalStream::TerminalStream() noexcept : buffer_(kDefaultBufferSize) {}
77 : :
78 : 1 : TerminalStream::~TerminalStream() noexcept {
79 : 1 : std::lock_guard lock(mutex_);
80 : 1 : flush_unlocked();
81 : 1 : }
82 : :
83 : 9 : void TerminalStream::init() noexcept {
84 [ + + ]: 9 : if (initialized_.load(std::memory_order_acquire)) {
85 : 8 : return;
86 : : }
87 : :
88 : 1 : std::lock_guard lock(mutex_);
89 : :
90 [ - + ]: 1 : if (initialized_.load(std::memory_order_relaxed)) {
91 : 0 : return;
92 : : }
93 : :
94 : 1 : fd_ = VLINK_TERM_STDOUT_FILENO;
95 : :
96 : : #ifdef _WIN32
97 : : HANDLE h_out = GetStdHandle(STD_OUTPUT_HANDLE);
98 : :
99 : : if (h_out != INVALID_HANDLE_VALUE) {
100 : : DWORD dw_mode = 0;
101 : :
102 : : if (GetConsoleMode(h_out, &dw_mode)) {
103 : : dw_mode |= 0x0004; // ENABLE_VIRTUAL_TERMINAL_PROCESSING
104 : : SetConsoleMode(h_out, dw_mode);
105 : : }
106 : : }
107 : : #endif
108 : :
109 : 1 : is_tty_.store(VLINK_TERM_ISATTY(fd_) != 0, std::memory_order_release);
110 : 1 : initialized_.store(true, std::memory_order_release);
111 [ + - ]: 1 : }
112 : :
113 : 1 : bool TerminalStream::is_tty() const noexcept { return is_tty_.load(std::memory_order_acquire); }
114 : :
115 : 1 : bool TerminalStream::is_initialized() const noexcept { return initialized_.load(std::memory_order_acquire); }
116 : :
117 : 3 : void TerminalStream::flush() {
118 [ + - ]: 3 : std::lock_guard lock(mutex_);
119 : 3 : flush_unlocked();
120 : 3 : }
121 : :
122 : 2 : TerminalStream& TerminalStream::write_raw(const char* data, size_t len) noexcept {
123 : 2 : std::lock_guard lock(mutex_);
124 : 2 : write_to_buffer(data, len);
125 : :
126 : 4 : return *this;
127 : 2 : }
128 : :
129 : 2 : TerminalStream& TerminalStream::operator<<(char c) noexcept {
130 : 2 : std::lock_guard lock(mutex_);
131 : 2 : write_to_buffer(&c, 1);
132 : :
133 : 4 : return *this;
134 : 2 : }
135 : :
136 : 10 : TerminalStream& TerminalStream::operator<<(const char* str) noexcept {
137 [ + + ]: 10 : if VUNLIKELY (str == nullptr) {
138 : 1 : return *this;
139 : : }
140 : :
141 : 9 : std::lock_guard lock(mutex_);
142 : 9 : write_to_buffer(str, std::strlen(str));
143 : :
144 : 9 : return *this;
145 : 9 : }
146 : :
147 : 1 : TerminalStream& TerminalStream::operator<<(const std::string& str) noexcept {
148 : 1 : std::lock_guard lock(mutex_);
149 : 1 : write_to_buffer(str.data(), str.size());
150 : :
151 : 2 : return *this;
152 : 1 : }
153 : :
154 : 1 : TerminalStream& TerminalStream::operator<<(std::string_view str) noexcept {
155 : 1 : std::lock_guard lock(mutex_);
156 : 1 : write_to_buffer(str.data(), str.size());
157 : :
158 : 2 : return *this;
159 : 1 : }
160 : :
161 [ + + ]: 2 : TerminalStream& TerminalStream::operator<<(bool value) noexcept { return *this << (value ? "true" : "false"); }
162 : :
163 : 1 : TerminalStream& TerminalStream::operator<<(short value) noexcept { // NOLINT(runtime/int,google-runtime-int)
164 : 1 : return write_signed(static_cast<long long>(value)); // NOLINT(runtime/int,google-runtime-int)
165 : : }
166 : :
167 : 1 : TerminalStream& TerminalStream::operator<<(unsigned short value) noexcept { // NOLINT(runtime/int,google-runtime-int)
168 : 1 : return write_unsigned(static_cast<unsigned long long>(value)); // NOLINT(runtime/int,google-runtime-int)
169 : : }
170 : :
171 : 6 : TerminalStream& TerminalStream::operator<<(int value) noexcept { // NOLINT(runtime/int,google-runtime-int)
172 : 6 : return write_signed(static_cast<long long>(value)); // NOLINT(runtime/int,google-runtime-int)
173 : : }
174 : :
175 : 1 : TerminalStream& TerminalStream::operator<<(unsigned int value) noexcept { // NOLINT(runtime/int,google-runtime-int)
176 : 1 : return write_unsigned(static_cast<unsigned long long>(value)); // NOLINT(runtime/int,google-runtime-int)
177 : : }
178 : :
179 : 1 : TerminalStream& TerminalStream::operator<<(long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
180 : 1 : return write_signed(static_cast<long long>(value)); // NOLINT(runtime/int,google-runtime-int)
181 : : }
182 : :
183 : 1 : TerminalStream& TerminalStream::operator<<(unsigned long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
184 : 1 : return write_unsigned(static_cast<unsigned long long>(value)); // NOLINT(runtime/int,google-runtime-int)
185 : : }
186 : :
187 : 3 : TerminalStream& TerminalStream::operator<<(long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
188 : 3 : return write_signed(value); // NOLINT(runtime/int,google-runtime-int)
189 : : }
190 : :
191 : 1 : TerminalStream& TerminalStream::operator<<(
192 : : unsigned long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
193 : 1 : return write_unsigned(value);
194 : : }
195 : :
196 : 2 : TerminalStream& TerminalStream::operator<<(float value) noexcept { return write_double(static_cast<double>(value)); }
197 : :
198 : 1 : TerminalStream& TerminalStream::operator<<(double value) noexcept { return write_double(value); }
199 : :
200 : 1 : TerminalStream& TerminalStream::operator<<(long double value) noexcept { // NOLINT(google-runtime-float)
201 : 1 : return write_long_double(value);
202 : : }
203 : :
204 : 2 : TerminalStream& TerminalStream::operator<<(const void* ptr) noexcept {
205 : 2 : std::lock_guard lock(mutex_);
206 : :
207 : : char buf[32];
208 : 2 : int len = std::snprintf(buf, sizeof(buf), "%p", ptr);
209 : :
210 [ + - ]: 2 : if VLIKELY (len > 0) {
211 : 2 : write_to_buffer(buf, static_cast<size_t>(len));
212 : : }
213 : :
214 : 4 : return *this;
215 : 2 : }
216 : :
217 : 7 : TerminalStream& TerminalStream::operator<<(ManipType manip) noexcept { return manip(*this); }
218 : :
219 : 1 : TerminalStream& TerminalStream::operator<<(std::ostream& (*manip)(std::ostream&)) noexcept {
220 : 1 : std::lock_guard lock(mutex_);
221 : :
222 : 1 : const auto std_flush = static_cast<std::ostream& (*)(std::ostream&)>(&std::flush<char, std::char_traits<char>>);
223 : :
224 [ + - ]: 1 : if (manip != std_flush) {
225 : 1 : write_to_buffer("\n", 1);
226 : : }
227 : :
228 : 1 : flush_unlocked();
229 : :
230 : 2 : return *this;
231 : 1 : }
232 : :
233 : 1 : int TerminalStream::default_stdout_fd() noexcept { return VLINK_TERM_STDOUT_FILENO; }
234 : :
235 : 12 : void TerminalStream::flush_unlocked() noexcept {
236 [ + + ]: 12 : if (write_pos_ == 0) {
237 : 3 : return;
238 : : }
239 : :
240 : 9 : const char* data = buffer_.data();
241 : 9 : size_t remaining = write_pos_;
242 : 9 : size_t flushed = 0;
243 : :
244 [ + + ]: 18 : while (remaining > 0) {
245 : 9 : auto written = VLINK_TERM_WRITE(fd_, data, static_cast<unsigned int>(remaining));
246 : :
247 [ - + ]: 9 : if VUNLIKELY (written < 0) {
248 : : if VUNLIKELY (errno == EINTR) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
249 : : continue; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
250 : : }
251 : :
252 : : break; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
253 : : }
254 : :
255 [ - + ]: 9 : if VUNLIKELY (written == 0) {
256 : : break; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
257 : : }
258 : :
259 : 9 : const auto written_size = static_cast<size_t>(written);
260 : 9 : data += written_size;
261 : 9 : remaining -= written_size;
262 : 9 : flushed += written_size;
263 : : }
264 : :
265 [ - + ]: 9 : if (remaining > 0) {
266 : : // LCOV_EXCL_START GCOVR_EXCL_START
267 : : if (flushed > 0) {
268 : : std::memmove(buffer_.data(), buffer_.data() + flushed, remaining);
269 : : }
270 : :
271 : : write_pos_ = remaining;
272 : :
273 : : return;
274 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
275 : : }
276 : :
277 : 9 : write_pos_ = 0;
278 : :
279 : : #ifndef _WIN32
280 : :
281 [ - + ]: 9 : if (is_tty_.load(std::memory_order_acquire)) {
282 : : ::tcdrain(fd_); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
283 : : }
284 : : #endif
285 : : }
286 : :
287 : 43 : void TerminalStream::write_to_buffer(const char* data, size_t len) noexcept {
288 [ + + ]: 43 : if VUNLIKELY (len == 0) {
289 : 1 : return;
290 : : }
291 : :
292 [ - + ]: 42 : if (len >= buffer_.size()) {
293 : : // LCOV_EXCL_START GCOVR_EXCL_START
294 : : flush_unlocked();
295 : :
296 : : if (write_pos_ != 0) {
297 : : // if VUNLIKELY (len > std::numeric_limits<size_t>::max() - write_pos_) {
298 : : // return;
299 : : // }
300 : : //
301 : : // const size_t target_size = write_pos_ + len;
302 : : // if (target_size > buffer_.size()) {
303 : : // try {
304 : : // buffer_.resize(target_size);
305 : : // } catch (...) {
306 : : // return;
307 : : // }
308 : : // }
309 : : //
310 : : // std::memcpy(buffer_.data() + write_pos_, data, len);
311 : : // write_pos_ = target_size;
312 : : return;
313 : : }
314 : :
315 : : size_t remaining = len;
316 : : while (remaining > 0) {
317 : : auto written = VLINK_TERM_WRITE(fd_, data, static_cast<unsigned int>(remaining));
318 : :
319 : : if VUNLIKELY (written < 0) {
320 : : if VUNLIKELY (errno == EINTR) {
321 : : continue;
322 : : }
323 : :
324 : : break;
325 : : }
326 : :
327 : : if VUNLIKELY (written == 0) {
328 : : break;
329 : : }
330 : :
331 : : const auto written_size = static_cast<size_t>(written);
332 : : data += written_size;
333 : : remaining -= written_size;
334 : : }
335 : :
336 : : if (remaining > 0) {
337 : : if (remaining > buffer_.size()) {
338 : : try {
339 : : buffer_.resize(remaining);
340 : : } catch (...) {
341 : : return;
342 : : }
343 : : }
344 : :
345 : : std::memcpy(buffer_.data(), data, remaining);
346 : : write_pos_ = remaining;
347 : : }
348 : :
349 : : return;
350 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
351 : : }
352 : :
353 [ - + ]: 42 : if (write_pos_ + len > buffer_.size()) {
354 : : flush_unlocked(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
355 : : }
356 : :
357 [ - + ]: 42 : if (write_pos_ + len > buffer_.size()) {
358 : : // LCOV_EXCL_START GCOVR_EXCL_START
359 : : try {
360 : : buffer_.resize(write_pos_ + len);
361 : : } catch (...) {
362 : : return;
363 : : }
364 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
365 : : }
366 : :
367 : 42 : std::memcpy(buffer_.data() + write_pos_, data, len);
368 : 42 : write_pos_ += len;
369 : : }
370 : :
371 : 11 : TerminalStream& TerminalStream::write_signed(long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
372 : 11 : std::lock_guard lock(mutex_);
373 : :
374 : : char buf[32];
375 : 11 : char* ptr = buf + sizeof(buf);
376 : 11 : bool negative = false;
377 : :
378 [ + + ]: 11 : if (value < 0) {
379 : 4 : negative = true;
380 : :
381 [ + + ]: 4 : if VUNLIKELY (value == std::numeric_limits<long long>::min()) { // NOLINT(runtime/int,google-runtime-int)
382 : 1 : int len = std::snprintf(buf, sizeof(buf), "%lld", value);
383 : :
384 [ + - ]: 1 : if VLIKELY (len > 0) {
385 : 1 : write_to_buffer(buf, static_cast<size_t>(len));
386 : : }
387 : :
388 : 1 : return *this;
389 : : }
390 : :
391 : 3 : value = -value;
392 : : }
393 : :
394 [ + + ]: 10 : if (value == 0) {
395 : 1 : *--ptr = '0';
396 : : } else {
397 [ + + ]: 71 : while (value != 0) {
398 : 62 : *--ptr = '0' + static_cast<char>(value % 10);
399 : 62 : value /= 10;
400 : : }
401 : : }
402 : :
403 [ + + ]: 10 : if (negative) {
404 : 3 : *--ptr = '-';
405 : : }
406 : :
407 : 10 : write_to_buffer(ptr, static_cast<size_t>(buf + sizeof(buf) - ptr));
408 : :
409 : 10 : return *this;
410 : 11 : }
411 : :
412 : 4 : TerminalStream& TerminalStream::write_unsigned(
413 : : unsigned long long value) noexcept { // NOLINT(runtime/int,google-runtime-int)
414 : 4 : std::lock_guard lock(mutex_);
415 : :
416 : : char buf[32];
417 : 4 : char* ptr = buf + sizeof(buf);
418 : :
419 [ - + ]: 4 : if (value == 0) {
420 : 0 : *--ptr = '0';
421 : : } else {
422 [ + + ]: 34 : while (value != 0) {
423 : 30 : *--ptr = '0' + static_cast<char>(value % 10);
424 : 30 : value /= 10;
425 : : }
426 : : }
427 : :
428 : 4 : write_to_buffer(ptr, static_cast<size_t>(buf + sizeof(buf) - ptr));
429 : :
430 : 8 : return *this;
431 : 4 : }
432 : :
433 : 3 : TerminalStream& TerminalStream::write_double(double value) noexcept {
434 : 3 : std::lock_guard lock(mutex_);
435 : :
436 : : char buf[64];
437 : 3 : int len = std::snprintf(buf, sizeof(buf), "%g", value);
438 : :
439 [ + - ]: 3 : if VLIKELY (len > 0) {
440 : 3 : write_to_buffer(buf, static_cast<size_t>(len));
441 : : }
442 : :
443 : 6 : return *this;
444 : 3 : }
445 : :
446 : : // NOLINTNEXTLINE(google-runtime-float)
447 : 1 : TerminalStream& TerminalStream::write_long_double(long double value) noexcept {
448 : 1 : std::lock_guard lock(mutex_);
449 : :
450 : : char buf[64];
451 : 1 : int len = std::snprintf(buf, sizeof(buf), "%Lg", value);
452 : :
453 [ + - ]: 1 : if VLIKELY (len > 0) {
454 : 1 : write_to_buffer(buf, static_cast<size_t>(len));
455 : : }
456 : :
457 : 2 : return *this;
458 : 1 : }
459 : :
460 : : } // namespace vlink
|