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