1 | // Copyright 2013 Google Inc. All Rights Reserved. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | #include "line_printer.h" |
16 | |
17 | #include <stdio.h> |
18 | #include <stdlib.h> |
19 | #ifdef _WIN32 |
20 | #include <windows.h> |
21 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING |
22 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4 |
23 | #endif |
24 | #else |
25 | #include <unistd.h> |
26 | #include <sys/ioctl.h> |
27 | #include <termios.h> |
28 | #include <sys/time.h> |
29 | #endif |
30 | |
31 | #include "util.h" |
32 | |
33 | using namespace std; |
34 | |
35 | LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) { |
36 | const char* term = getenv("TERM" ); |
37 | #ifndef _WIN32 |
38 | smart_terminal_ = isatty(1) && term && string(term) != "dumb" ; |
39 | #else |
40 | if (term && string(term) == "dumb" ) { |
41 | smart_terminal_ = false; |
42 | } else { |
43 | console_ = GetStdHandle(STD_OUTPUT_HANDLE); |
44 | CONSOLE_SCREEN_BUFFER_INFO csbi; |
45 | smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi); |
46 | } |
47 | #endif |
48 | supports_color_ = smart_terminal_; |
49 | #ifdef _WIN32 |
50 | // Try enabling ANSI escape sequence support on Windows 10 terminals. |
51 | if (supports_color_) { |
52 | DWORD mode; |
53 | if (GetConsoleMode(console_, &mode)) { |
54 | if (!SetConsoleMode(console_, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { |
55 | supports_color_ = false; |
56 | } |
57 | } |
58 | } |
59 | #endif |
60 | if (!supports_color_) { |
61 | const char* clicolor_force = getenv("CLICOLOR_FORCE" ); |
62 | supports_color_ = clicolor_force && std::string(clicolor_force) != "0" ; |
63 | } |
64 | } |
65 | |
66 | void LinePrinter::Print(string to_print, LineType type) { |
67 | if (console_locked_) { |
68 | line_buffer_ = to_print; |
69 | line_type_ = type; |
70 | return; |
71 | } |
72 | |
73 | if (smart_terminal_) { |
74 | printf("\r" ); // Print over previous line, if any. |
75 | // On Windows, calling a C library function writing to stdout also handles |
76 | // pausing the executable when the "Pause" key or Ctrl-S is pressed. |
77 | } |
78 | |
79 | if (smart_terminal_ && type == ELIDE) { |
80 | #ifdef _WIN32 |
81 | CONSOLE_SCREEN_BUFFER_INFO csbi; |
82 | GetConsoleScreenBufferInfo(console_, &csbi); |
83 | |
84 | to_print = ElideMiddle(to_print, static_cast<size_t>(csbi.dwSize.X)); |
85 | if (supports_color_) { // this means ENABLE_VIRTUAL_TERMINAL_PROCESSING |
86 | // succeeded |
87 | printf("%s\x1B[K" , to_print.c_str()); // Clear to end of line. |
88 | fflush(stdout); |
89 | } else { |
90 | // We don't want to have the cursor spamming back and forth, so instead of |
91 | // printf use WriteConsoleOutput which updates the contents of the buffer, |
92 | // but doesn't move the cursor position. |
93 | COORD buf_size = { csbi.dwSize.X, 1 }; |
94 | COORD zero_zero = { 0, 0 }; |
95 | SMALL_RECT target = { csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y, |
96 | static_cast<SHORT>(csbi.dwCursorPosition.X + |
97 | csbi.dwSize.X - 1), |
98 | csbi.dwCursorPosition.Y }; |
99 | vector<CHAR_INFO> char_data(csbi.dwSize.X); |
100 | for (size_t i = 0; i < static_cast<size_t>(csbi.dwSize.X); ++i) { |
101 | char_data[i].Char.AsciiChar = i < to_print.size() ? to_print[i] : ' '; |
102 | char_data[i].Attributes = csbi.wAttributes; |
103 | } |
104 | WriteConsoleOutput(console_, &char_data[0], buf_size, zero_zero, &target); |
105 | } |
106 | #else |
107 | // Limit output to width of the terminal if provided so we don't cause |
108 | // line-wrapping. |
109 | winsize size; |
110 | if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == 0) && size.ws_col) { |
111 | to_print = ElideMiddle(to_print, size.ws_col); |
112 | } |
113 | printf("%s" , to_print.c_str()); |
114 | printf("\x1B[K" ); // Clear to end of line. |
115 | fflush(stdout); |
116 | #endif |
117 | |
118 | have_blank_line_ = false; |
119 | } else { |
120 | printf("%s\n" , to_print.c_str()); |
121 | } |
122 | } |
123 | |
124 | void LinePrinter::PrintOrBuffer(const char* data, size_t size) { |
125 | if (console_locked_) { |
126 | output_buffer_.append(data, size); |
127 | } else { |
128 | // Avoid printf and C strings, since the actual output might contain null |
129 | // bytes like UTF-16 does (yuck). |
130 | fwrite(data, 1, size, stdout); |
131 | } |
132 | } |
133 | |
134 | void LinePrinter::PrintOnNewLine(const string& to_print) { |
135 | if (console_locked_ && !line_buffer_.empty()) { |
136 | output_buffer_.append(line_buffer_); |
137 | output_buffer_.append(1, '\n'); |
138 | line_buffer_.clear(); |
139 | } |
140 | if (!have_blank_line_) { |
141 | PrintOrBuffer("\n" , 1); |
142 | } |
143 | if (!to_print.empty()) { |
144 | PrintOrBuffer(&to_print[0], to_print.size()); |
145 | } |
146 | have_blank_line_ = to_print.empty() || *to_print.rbegin() == '\n'; |
147 | } |
148 | |
149 | void LinePrinter::SetConsoleLocked(bool locked) { |
150 | if (locked == console_locked_) |
151 | return; |
152 | |
153 | if (locked) |
154 | PrintOnNewLine("" ); |
155 | |
156 | console_locked_ = locked; |
157 | |
158 | if (!locked) { |
159 | PrintOnNewLine(output_buffer_); |
160 | if (!line_buffer_.empty()) { |
161 | Print(line_buffer_, line_type_); |
162 | } |
163 | output_buffer_.clear(); |
164 | line_buffer_.clear(); |
165 | } |
166 | } |
167 | |