Ninja
line_printer.cc
Go to the documentation of this file.
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 #else
22 #include <unistd.h>
23 #include <sys/ioctl.h>
24 #include <sys/time.h>
25 #endif
26 
27 #include "util.h"
28 
29 LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
30 #ifndef _WIN32
31  const char* term = getenv("TERM");
32  smart_terminal_ = isatty(1) && term && string(term) != "dumb";
33 #else
34  // Disable output buffer. It'd be nice to use line buffering but
35  // MSDN says: "For some systems, [_IOLBF] provides line
36  // buffering. However, for Win32, the behavior is the same as _IOFBF
37  // - Full Buffering."
38  setvbuf(stdout, NULL, _IONBF, 0);
39  console_ = GetStdHandle(STD_OUTPUT_HANDLE);
40  CONSOLE_SCREEN_BUFFER_INFO csbi;
41  smart_terminal_ = GetConsoleScreenBufferInfo(console_, &csbi);
42 #endif
43 }
44 
45 void LinePrinter::Print(string to_print, LineType type) {
46  if (console_locked_) {
47  line_buffer_ = to_print;
48  line_type_ = type;
49  return;
50  }
51 
52 #ifdef _WIN32
53  CONSOLE_SCREEN_BUFFER_INFO csbi;
54  GetConsoleScreenBufferInfo(console_, &csbi);
55 #endif
56 
57  if (smart_terminal_) {
58 #ifndef _WIN32
59  printf("\r"); // Print over previous line, if any.
60 #else
61  csbi.dwCursorPosition.X = 0;
62  SetConsoleCursorPosition(console_, csbi.dwCursorPosition);
63 #endif
64  }
65 
66  if (smart_terminal_ && type == ELIDE) {
67 #ifdef _WIN32
68  // Don't use the full width or console will move to next line.
69  size_t width = static_cast<size_t>(csbi.dwSize.X) - 1;
70  to_print = ElideMiddle(to_print, width);
71  // We don't want to have the cursor spamming back and forth, so
72  // use WriteConsoleOutput instead which updates the contents of
73  // the buffer, but doesn't move the cursor position.
74  GetConsoleScreenBufferInfo(console_, &csbi);
75  COORD buf_size = { csbi.dwSize.X, 1 };
76  COORD zero_zero = { 0, 0 };
77  SMALL_RECT target = {
78  csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y,
79  static_cast<SHORT>(csbi.dwCursorPosition.X + csbi.dwSize.X - 1),
80  csbi.dwCursorPosition.Y
81  };
82  CHAR_INFO* char_data = new CHAR_INFO[csbi.dwSize.X];
83  memset(char_data, 0, sizeof(CHAR_INFO) * csbi.dwSize.X);
84  for (int i = 0; i < csbi.dwSize.X; ++i) {
85  char_data[i].Char.AsciiChar = ' ';
86  char_data[i].Attributes = csbi.wAttributes;
87  }
88  for (size_t i = 0; i < to_print.size(); ++i)
89  char_data[i].Char.AsciiChar = to_print[i];
90  WriteConsoleOutput(console_, char_data, buf_size, zero_zero, &target);
91  delete[] char_data;
92 #else
93  // Limit output to width of the terminal if provided so we don't cause
94  // line-wrapping.
95  winsize size;
96  if ((ioctl(0, TIOCGWINSZ, &size) == 0) && size.ws_col) {
97  to_print = ElideMiddle(to_print, size.ws_col);
98  }
99  printf("%s", to_print.c_str());
100  printf("\x1B[K"); // Clear to end of line.
101  fflush(stdout);
102 #endif
103 
104  have_blank_line_ = false;
105  } else {
106  printf("%s\n", to_print.c_str());
107  }
108 }
109 
110 void LinePrinter::PrintOrBuffer(const char* data, size_t size) {
111  if (console_locked_) {
112  output_buffer_.append(data, size);
113  } else {
114  // Avoid printf and C strings, since the actual output might contain null
115  // bytes like UTF-16 does (yuck).
116  fwrite(data, 1, size, stdout);
117  }
118 }
119 
120 void LinePrinter::PrintOnNewLine(const string& to_print) {
121  if (console_locked_ && !line_buffer_.empty()) {
123  output_buffer_.append(1, '\n');
124  line_buffer_.clear();
125  }
126  if (!have_blank_line_) {
127  PrintOrBuffer("\n", 1);
128  }
129  if (!to_print.empty()) {
130  PrintOrBuffer(&to_print[0], to_print.size());
131  }
132  have_blank_line_ = to_print.empty() || *to_print.rbegin() == '\n';
133 }
134 
135 void LinePrinter::SetConsoleLocked(bool locked) {
136  if (locked == console_locked_)
137  return;
138 
139  if (locked)
140  PrintOnNewLine("");
141 
142  console_locked_ = locked;
143 
144  if (!locked) {
146  if (!line_buffer_.empty()) {
148  }
149  output_buffer_.clear();
150  line_buffer_.clear();
151  }
152 }
bool have_blank_line_
Whether the caret is at the beginning of a blank line.
Definition: line_printer.h:50
void PrintOnNewLine(const string &to_print)
Prints a string on a new line, not overprinting previous output.
bool console_locked_
Whether console is locked.
Definition: line_printer.h:53
LineType line_type_
Buffered line type while console is locked.
Definition: line_printer.h:59
void SetConsoleLocked(bool locked)
Lock or unlock the console.
void PrintOrBuffer(const char *data, size_t size)
Print the given data to the console, or buffer it if it is locked.
string line_buffer_
Buffered current line while console is locked.
Definition: line_printer.h:56
void Print(string to_print, LineType type)
Overprints the current line.
Definition: line_printer.cc:45
bool smart_terminal_
Whether we can do fancy terminal control codes.
Definition: line_printer.h:47
string output_buffer_
Buffered console output while console is locked.
Definition: line_printer.h:62
string ElideMiddle(const string &str, size_t width)
Elide the given string str with '...' in the middle if the length exceeds width.
Definition: util.cc:435