LLDB mainline
AnsiTerminal.h
Go to the documentation of this file.
1//===---------------------AnsiTerminal.h ------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#ifndef LLDB_UTILITY_ANSITERMINAL_H
10#define LLDB_UTILITY_ANSITERMINAL_H
11
12#define ANSI_FG_COLOR_BLACK 30
13#define ANSI_FG_COLOR_RED 31
14#define ANSI_FG_COLOR_GREEN 32
15#define ANSI_FG_COLOR_YELLOW 33
16#define ANSI_FG_COLOR_BLUE 34
17#define ANSI_FG_COLOR_PURPLE 35
18#define ANSI_FG_COLOR_CYAN 36
19#define ANSI_FG_COLOR_WHITE 37
20
21#define ANSI_FG_COLOR_BRIGHT_BLACK 90
22#define ANSI_FG_COLOR_BRIGHT_RED 91
23#define ANSI_FG_COLOR_BRIGHT_GREEN 92
24#define ANSI_FG_COLOR_BRIGHT_YELLOW 93
25#define ANSI_FG_COLOR_BRIGHT_BLUE 94
26#define ANSI_FG_COLOR_BRIGHT_PURPLE 95
27#define ANSI_FG_COLOR_BRIGHT_CYAN 96
28#define ANSI_FG_COLOR_BRIGHT_WHITE 97
29
30#define ANSI_BG_COLOR_BLACK 40
31#define ANSI_BG_COLOR_RED 41
32#define ANSI_BG_COLOR_GREEN 42
33#define ANSI_BG_COLOR_YELLOW 43
34#define ANSI_BG_COLOR_BLUE 44
35#define ANSI_BG_COLOR_PURPLE 45
36#define ANSI_BG_COLOR_CYAN 46
37#define ANSI_BG_COLOR_WHITE 47
38
39#define ANSI_BG_COLOR_BRIGHT_BLACK 100
40#define ANSI_BG_COLOR_BRIGHT_RED 101
41#define ANSI_BG_COLOR_BRIGHT_GREEN 102
42#define ANSI_BG_COLOR_BRIGHT_YELLOW 103
43#define ANSI_BG_COLOR_BRIGHT_BLUE 104
44#define ANSI_BG_COLOR_BRIGHT_PURPLE 105
45#define ANSI_BG_COLOR_BRIGHT_CYAN 106
46#define ANSI_BG_COLOR_BRIGHT_WHITE 107
47
48#define ANSI_SPECIAL_FRAMED 51
49#define ANSI_SPECIAL_ENCIRCLED 52
50
51#define ANSI_CTRL_NORMAL 0
52#define ANSI_CTRL_BOLD 1
53#define ANSI_CTRL_FAINT 2
54#define ANSI_CTRL_ITALIC 3
55#define ANSI_CTRL_UNDERLINE 4
56#define ANSI_CTRL_SLOW_BLINK 5
57#define ANSI_CTRL_FAST_BLINK 6
58#define ANSI_CTRL_IMAGE_NEGATIVE 7
59#define ANSI_CTRL_CONCEAL 8
60#define ANSI_CTRL_CROSSED_OUT 9
61
62#define ANSI_ESC_START "\033["
63#define ANSI_ESC_END "m"
64
65#define ANSI_STR(s) #s
66#define ANSI_DEF_STR(s) ANSI_STR(s)
67
68#define ANSI_ESCAPE1(s) ANSI_ESC_START ANSI_DEF_STR(s) ANSI_ESC_END
69
70#define ANSI_1_CTRL(ctrl1) "\033["##ctrl1 ANSI_ESC_END
71#define ANSI_2_CTRL(ctrl1, ctrl2) "\033["##ctrl1 ";"##ctrl2 ANSI_ESC_END
72
73#define ANSI_ESC_START_LEN 2
74
75// Cursor Position, set cursor to position [l, c] (default = [1, 1]).
76#define ANSI_CSI_CUP(...) ANSI_ESC_START #__VA_ARGS__ "H"
77// Cursor Position, move cursor forward N columns.
78#define ANSI_CSI_CUF(N) (ANSI_ESC_START + N + "C")
79// Reset cursor to position.
80#define ANSI_CSI_RESET_CURSOR ANSI_CSI_CUP()
81// Erase In Display.
82#define ANSI_CSI_ED(opt) ANSI_ESC_START #opt "J"
83// Erase complete viewport.
84#define ANSI_CSI_ERASE_VIEWPORT ANSI_CSI_ED(2)
85// Erase scrollback.
86#define ANSI_CSI_ERASE_SCROLLBACK ANSI_CSI_ED(3)
87
88// OSC (Operating System Commands)
89// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
90#define OSC_ESCAPE_START "\033"
91#define OSC_ESCAPE_END "\x07"
92
93// https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
94#define OSC_PROGRESS_REMOVE OSC_ESCAPE_START "]9;4;0;0" OSC_ESCAPE_END
95#define OSC_PROGRESS_SHOW OSC_ESCAPE_START "]9;4;1;%u" OSC_ESCAPE_END
96#define OSC_PROGRESS_ERROR OSC_ESCAPE_START "]9;4;2;%u" OSC_ESCAPE_END
97#define OSC_PROGRESS_INDETERMINATE OSC_ESCAPE_START "]9;4;3;%u" OSC_ESCAPE_END
98
99#include "llvm/ADT/ArrayRef.h"
100#include "llvm/ADT/STLExtras.h"
101#include "llvm/ADT/StringRef.h"
102#include "llvm/Support/Locale.h"
103#include "llvm/Support/Unicode.h"
104
105#include "lldb/Utility/Stream.h"
106
107#include <string>
108
109namespace lldb_private {
110
111namespace ansi {
112
113inline std::string FormatAnsiTerminalCodes(llvm::StringRef format,
114 bool do_color = true) {
115 // Convert "${ansi.XXX}" tokens to ansi values or clear them if do_color is
116 // false.
117 // clang-format off
118 static const struct {
119 const char *name;
120 const char *value;
121 } g_color_tokens[] = {
122#define _TO_STR2(_val) #_val
123#define _TO_STR(_val) _TO_STR2(_val)
166#undef _TO_STR
167#undef _TO_STR2
168 };
169 // clang-format on
170 auto codes = llvm::ArrayRef(g_color_tokens);
171
172 static const char tok_hdr[] = "${ansi.";
173
174 std::string fmt;
175 while (!format.empty()) {
176 llvm::StringRef left, right;
177 std::tie(left, right) = format.split(tok_hdr);
178
179 fmt += left;
180
181 if (left == format && right.empty()) {
182 // The header was not found. Just exit.
183 break;
184 }
185
186 bool found_code = false;
187 for (const auto &code : codes) {
188 if (!right.consume_front(code.name))
189 continue;
190
191 if (do_color)
192 fmt.append(code.value);
193 found_code = true;
194 break;
195 }
196 format = right;
197 // If we haven't found a valid replacement value, we just copy the string
198 // to the result without any modifications.
199 if (!found_code)
200 fmt.append(tok_hdr);
201 }
202 return fmt;
203}
204
205inline std::tuple<llvm::StringRef, llvm::StringRef, llvm::StringRef>
206FindNextAnsiSequence(llvm::StringRef str) {
207 llvm::StringRef left;
208 llvm::StringRef right = str;
209
210 while (!right.empty()) {
211 const size_t start = right.find(ANSI_ESC_START);
212
213 // ANSI_ESC_START not found.
214 if (start == llvm::StringRef::npos)
215 return {str, {}, {}};
216
217 // Split the string around the current ANSI_ESC_START.
218 left = str.take_front(left.size() + start);
219 llvm::StringRef escape = right.substr(start);
220 right = right.substr(start + ANSI_ESC_START_LEN + 1);
221
222 const size_t end = right.find_first_not_of("0123456789;");
223
224 // ANSI_ESC_END found.
225 if (end < right.size() && (right[end] == 'm' || right[end] == 'G'))
226 return {left, escape.take_front(ANSI_ESC_START_LEN + 1 + end + 1),
227 right.substr(end + 1)};
228
229 // Maintain the invariant that str == left + right at the start of the loop.
230 left = str.take_front(left.size() + ANSI_ESC_START_LEN + 1);
231 }
232
233 return {str, {}, {}};
234}
235
236inline std::string StripAnsiTerminalCodes(llvm::StringRef str) {
237 std::string stripped;
238 while (!str.empty()) {
239 auto [left, escape, right] = FindNextAnsiSequence(str);
240 stripped += left;
241 str = right;
242 }
243 return stripped;
244}
245
246inline size_t ColumnWidth(llvm::StringRef str) {
247 std::string stripped = ansi::StripAnsiTerminalCodes(str);
248 return llvm::sys::locale::columnWidth(stripped);
249}
250
251/// Trim the given string to the given visible length, at a word boundary.
252/// Visible length means its width when rendered to the terminal.
253/// The string can include ANSI codes and Unicode.
254///
255/// For a single word string, that word is returned in its entirety regardless
256/// of its visible length.
257///
258/// This function is similar to TrimAndPad, except that it must split on a word
259/// boundary. So there are some notable differences:
260/// * Has a special case for single words that exceed desired visible
261/// length.
262/// * Must track whether the most recent modifications was on a word boundary
263/// or not.
264/// * If the trimming finishes without the result ending on a word boundary,
265/// it must find the nearest boundary to that trim point by trimming more.
266inline std::string TrimAtWordBoundary(llvm::StringRef str,
267 size_t visible_length) {
268 str = str.trim();
269 if (str.empty())
270 return str.str();
271
272 auto first_whitespace = str.find_first_of(" \t\n");
273 // No whitespace means a single word, which we cannot split.
274 if (first_whitespace == llvm::StringRef::npos)
275 return str.str();
276
277 // If the first word of a multi-word string is too wide, return that whole
278 // word only.
279 auto to_first_word_boundary = str.substr(0, first_whitespace);
280 // We use ansi::ColumnWidth here because it can handle ANSI and Unicode.
281 if (ansi::ColumnWidth(to_first_word_boundary) > visible_length)
282 return to_first_word_boundary.str();
283
284 std::string result;
285 result.reserve(visible_length);
286 // When there is Unicode or ANSI codes, the visible length will not equal
287 // result.size(), so we track it separately.
288 size_t result_visible_length = 0;
289
290 // The loop below makes many adjustments, and we never know which will be the
291 // last. This tracks whether the most recent adjustment put us at a word
292 // boundary and is checked after the main loop.
293 bool at_word_boundary = false;
294
295 // Trim the string to the given visible length.
296 while (!str.empty()) {
297 auto [left, escape, right] = FindNextAnsiSequence(str);
298 str = right;
299
300 // We know that left does not include ANSI codes. Compute its visible length
301 // and if it fits, append it together with the invisible escape code.
302 size_t column_width = llvm::sys::locale::columnWidth(left);
303 if (result_visible_length + column_width <= visible_length) {
304 result.append(left).append(escape);
305 result_visible_length += column_width;
306 at_word_boundary =
307 right.empty() || std::isspace(static_cast<unsigned char>(right[0]));
308
309 continue;
310 }
311
312 // The string might contain unicode which means it's not safe to truncate.
313 // Repeatedly trim the string until it is valid unicode and fits.
314 llvm::StringRef trimmed = left;
315
316 // A word break can happen at the character we trim to, or the one we
317 // trimmed before that (we are going backwards, so before in the loop is
318 // after in the string).
319
320 // A word break can happen at the point we trim, or just beyond that point.
321 // In other words: at the current back of trimmed, or what was the back last
322 // time around. following_char records the character popped in the previous
323 // loop iteration.
324 std::optional<char> following_char = std::nullopt;
325 while (!trimmed.empty()) {
326 int trimmed_width = llvm::sys::locale::columnWidth(trimmed);
327 if (
328 // If we have a partial Unicode character, keep trimming.
329 trimmed_width !=
330 llvm::sys::unicode::ColumnWidthErrors::ErrorInvalidUTF8 &&
331 // If the trimmed string fits in the column limit, stop trimming.
332 (result_visible_length + static_cast<size_t>(trimmed_width) <=
333 visible_length)) {
334 result.append(trimmed);
335 result_visible_length += trimmed_width;
336 at_word_boundary =
337 std::isspace(static_cast<unsigned char>(trimmed.back())) ||
338 (following_char &&
339 std::isspace(static_cast<unsigned char>(*following_char)));
340
341 break;
342 }
343
344 following_char = trimmed.back();
345 trimmed = trimmed.drop_back();
346 }
347 }
348
349 if (!at_word_boundary) {
350 // Walk backwards to find a word boundary.
351 auto last_whitespace = result.find_last_of(" \t\n");
352 if (last_whitespace != std::string::npos)
353 result = result.substr(0, last_whitespace);
354 }
355
356 // We may have split on whitespace that was the first of a word boundary, or
357 // somewhere in a run of whitespace. Trim the trailing spaces. This must be
358 // done here instead of in the loop because in the loop we may still be
359 // accumulating the result string.
360 return llvm::StringRef(result).rtrim().str();
361}
362
363inline std::string TrimAndPad(llvm::StringRef str, size_t visible_length,
364 char padding = ' ') {
365 std::string result;
366 result.reserve(visible_length);
367 size_t result_visibile_length = 0;
368
369 // Trim the string to the given visible length.
370 while (!str.empty()) {
371 auto [left, escape, right] = FindNextAnsiSequence(str);
372 str = right;
373
374 // Compute the length of the string without escape codes. If it fits, append
375 // it together with the invisible escape code.
376 size_t column_width = llvm::sys::locale::columnWidth(left);
377 if (result_visibile_length + column_width <= visible_length) {
378 result.append(left).append(escape);
379 result_visibile_length += column_width;
380 continue;
381 }
382
383 // The string might contain unicode which means it's not safe to truncate.
384 // Repeatedly trim the string until it its valid unicode and fits.
385 llvm::StringRef trimmed = left;
386 while (!trimmed.empty()) {
387 int trimmed_width = llvm::sys::locale::columnWidth(trimmed);
388 if (
389 // If we have only part of a Unicode character, keep trimming.
390 trimmed_width !=
391 llvm::sys::unicode::ColumnWidthErrors::ErrorInvalidUTF8 &&
392 // If the trimmed string fits, take it.
393 result_visibile_length + static_cast<size_t>(trimmed_width) <=
394 visible_length) {
395 result.append(trimmed);
396 result_visibile_length += static_cast<size_t>(trimmed_width);
397 break;
398 }
399 trimmed = trimmed.drop_back();
400 }
401 }
402
403 // Pad the string.
404 if (result_visibile_length < visible_length)
405 result.append(visible_length - result_visibile_length, padding);
406
407 return result;
408}
409
410// Output text that may contain ANSI codes, word wrapped (wrapped at whitespace)
411// to the given stream. The indent level of the stream is counted towards the
412// output line length.
413inline void OutputWordWrappedLines(Stream &strm, llvm::StringRef text,
414 uint32_t output_max_columns,
415 bool use_color) {
416 // We will indent using the stream, so leading whitespace is not significant.
417 text = text.ltrim();
418 if (text.empty())
419 return;
420
421 // 1 column border on the right side.
422 const uint32_t max_text_width =
423 output_max_columns - strm.GetIndentLevel() - 1;
424 bool first_line = true;
425 const std::string ansi_indent =
426 ANSI_CSI_CUF(std::to_string(strm.GetIndentLevel()));
427
428 while (!text.empty()) {
429 std::string split = TrimAtWordBoundary(text, max_text_width);
430 if (!first_line)
431 strm.EOL();
432 first_line = false;
433
434 if (use_color) {
435 // If we are allowed to use colour (aka ANSI codes), we can indent using
436 // ANSI cursor movement. This means that if an ANSI formatted range of
437 // text is split across two lines, the indentation is not also formatted.
438 // Which it would be if we just emitted spaces.
439 strm << ansi_indent << split;
440 } else {
441 strm.Indent(split);
442 }
443
444 text = text.drop_front(split.size()).ltrim();
445 }
446
447 strm.EOL();
448}
449
450} // namespace ansi
451} // namespace lldb_private
452
453#endif
#define ANSI_FG_COLOR_BRIGHT_GREEN
#define ANSI_BG_COLOR_BRIGHT_BLUE
#define ANSI_ESC_START
#define ANSI_BG_COLOR_PURPLE
#define ANSI_CTRL_UNDERLINE
#define ANSI_CSI_CUF(N)
#define ANSI_BG_COLOR_BRIGHT_WHITE
#define ANSI_BG_COLOR_BRIGHT_RED
#define ANSI_BG_COLOR_RED
#define ANSI_FG_COLOR_BLACK
#define ANSI_FG_COLOR_BRIGHT_CYAN
#define ANSI_FG_COLOR_BRIGHT_BLACK
#define ANSI_BG_COLOR_YELLOW
#define ANSI_FG_COLOR_BRIGHT_WHITE
#define ANSI_BG_COLOR_BLACK
#define ANSI_FG_COLOR_BRIGHT_YELLOW
#define ANSI_FG_COLOR_YELLOW
#define ANSI_FG_COLOR_PURPLE
#define ANSI_BG_COLOR_CYAN
#define ANSI_FG_COLOR_BRIGHT_RED
#define ANSI_BG_COLOR_BRIGHT_YELLOW
#define ANSI_BG_COLOR_GREEN
#define ANSI_CTRL_SLOW_BLINK
#define ANSI_FG_COLOR_RED
#define ANSI_BG_COLOR_BLUE
#define ANSI_CTRL_ITALIC
#define ANSI_FG_COLOR_BRIGHT_BLUE
#define ANSI_FG_COLOR_CYAN
#define ANSI_CTRL_FAST_BLINK
#define ANSI_CTRL_CROSSED_OUT
#define ANSI_CTRL_IMAGE_NEGATIVE
#define ANSI_BG_COLOR_WHITE
#define ANSI_CTRL_CONCEAL
#define ANSI_ESC_START_LEN
#define ANSI_FG_COLOR_GREEN
#define ANSI_CTRL_FAINT
#define ANSI_FG_COLOR_BLUE
#define ANSI_BG_COLOR_BRIGHT_GREEN
#define ANSI_FG_COLOR_BRIGHT_PURPLE
#define ANSI_CTRL_NORMAL
#define ANSI_BG_COLOR_BRIGHT_CYAN
#define ANSI_CTRL_BOLD
#define ANSI_FG_COLOR_WHITE
#define ANSI_BG_COLOR_BRIGHT_PURPLE
#define ANSI_ESC_END
#define ANSI_BG_COLOR_BRIGHT_BLACK
#define _TO_STR(_val)
A stream class that can stream formatted output to a file.
Definition Stream.h:28
size_t Indent(llvm::StringRef s="")
Indent the current line in the stream.
Definition Stream.cpp:157
size_t EOL()
Output and End of Line character to the stream.
Definition Stream.cpp:155
unsigned GetIndentLevel() const
Get the current indentation level.
Definition Stream.cpp:193
std::string FormatAnsiTerminalCodes(llvm::StringRef format, bool do_color=true)
void OutputWordWrappedLines(Stream &strm, llvm::StringRef text, uint32_t output_max_columns, bool use_color)
std::string TrimAtWordBoundary(llvm::StringRef str, size_t visible_length)
Trim the given string to the given visible length, at a word boundary.
size_t ColumnWidth(llvm::StringRef str)
std::tuple< llvm::StringRef, llvm::StringRef, llvm::StringRef > FindNextAnsiSequence(llvm::StringRef str)
std::string TrimAndPad(llvm::StringRef str, size_t visible_length, char padding=' ')
std::string StripAnsiTerminalCodes(llvm::StringRef str)
A class that represents a running process on the host machine.