1 | //===-- DiagnosticsRendering.cpp ------------------------------------------===// |
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 | #include "lldb/Utility/DiagnosticsRendering.h" |
10 | #include <cstdint> |
11 | |
12 | using namespace lldb_private; |
13 | using namespace lldb; |
14 | |
15 | namespace lldb_private { |
16 | |
17 | char DiagnosticError::ID; |
18 | |
19 | lldb::ErrorType DiagnosticError::GetErrorType() const { |
20 | return lldb::eErrorTypeExpression; |
21 | } |
22 | |
23 | StructuredData::ObjectSP Serialize(llvm::ArrayRef<DiagnosticDetail> details) { |
24 | auto make_array = []() { return std::make_unique<StructuredData::Array>(); }; |
25 | auto make_dict = []() { |
26 | return std::make_unique<StructuredData::Dictionary>(); |
27 | }; |
28 | auto dict_up = make_dict(); |
29 | dict_up->AddIntegerItem(key: "version" , value: 1u); |
30 | auto array_up = make_array(); |
31 | for (const DiagnosticDetail &diag : details) { |
32 | auto detail_up = make_dict(); |
33 | if (auto &sloc = diag.source_location) { |
34 | auto sloc_up = make_dict(); |
35 | sloc_up->AddStringItem(key: "file" , value: sloc->file.GetPath()); |
36 | sloc_up->AddIntegerItem(key: "line" , value: sloc->line); |
37 | sloc_up->AddIntegerItem(key: "length" , value: sloc->length); |
38 | sloc_up->AddBooleanItem(key: "hidden" , value: sloc->hidden); |
39 | sloc_up->AddBooleanItem(key: "in_user_input" , value: sloc->in_user_input); |
40 | detail_up->AddItem(key: "source_location" , value_sp: std::move(sloc_up)); |
41 | } |
42 | llvm::StringRef severity = "unknown" ; |
43 | switch (diag.severity) { |
44 | case lldb::eSeverityError: |
45 | severity = "error" ; |
46 | break; |
47 | case lldb::eSeverityWarning: |
48 | severity = "warning" ; |
49 | break; |
50 | case lldb::eSeverityInfo: |
51 | severity = "note" ; |
52 | break; |
53 | } |
54 | detail_up->AddStringItem(key: "severity" , value: severity); |
55 | detail_up->AddStringItem(key: "message" , value: diag.message); |
56 | detail_up->AddStringItem(key: "rendered" , value: diag.rendered); |
57 | array_up->AddItem(item: std::move(detail_up)); |
58 | } |
59 | dict_up->AddItem(key: "details" , value_sp: std::move(array_up)); |
60 | return dict_up; |
61 | } |
62 | |
63 | static llvm::raw_ostream &PrintSeverity(Stream &stream, |
64 | lldb::Severity severity) { |
65 | llvm::HighlightColor color; |
66 | llvm::StringRef text; |
67 | switch (severity) { |
68 | case lldb::eSeverityError: |
69 | color = llvm::HighlightColor::Error; |
70 | text = "error: " ; |
71 | break; |
72 | case lldb::eSeverityWarning: |
73 | color = llvm::HighlightColor::Warning; |
74 | text = "warning: " ; |
75 | break; |
76 | case lldb::eSeverityInfo: |
77 | color = llvm::HighlightColor::Remark; |
78 | text = "note: " ; |
79 | break; |
80 | } |
81 | return llvm::WithColor(stream.AsRawOstream(), color, llvm::ColorMode::Enable) |
82 | << text; |
83 | } |
84 | |
85 | void RenderDiagnosticDetails(Stream &stream, |
86 | std::optional<uint16_t> offset_in_command, |
87 | bool show_inline, |
88 | llvm::ArrayRef<DiagnosticDetail> details) { |
89 | if (details.empty()) |
90 | return; |
91 | |
92 | if (!offset_in_command) { |
93 | for (const DiagnosticDetail &detail : details) { |
94 | PrintSeverity(stream, severity: detail.severity); |
95 | stream << detail.rendered << '\n'; |
96 | } |
97 | return; |
98 | } |
99 | |
100 | // Since there is no other way to find this out, use the color |
101 | // attribute as a proxy for whether the terminal supports Unicode |
102 | // characters. In the future it might make sense to move this into |
103 | // Host so it can be customized for a specific platform. |
104 | llvm::StringRef cursor, underline, vbar, joint, hbar, spacer; |
105 | if (stream.AsRawOstream().colors_enabled()) { |
106 | cursor = "˄" ; |
107 | underline = "˜" ; |
108 | vbar = "│" ; |
109 | joint = "╰" ; |
110 | hbar = "─" ; |
111 | spacer = " " ; |
112 | } else { |
113 | cursor = "^" ; |
114 | underline = "~" ; |
115 | vbar = "|" ; |
116 | joint = "" ; |
117 | hbar = "" ; |
118 | spacer = "" ; |
119 | } |
120 | |
121 | // Partition the diagnostics. |
122 | std::vector<DiagnosticDetail> remaining_details, other_details, |
123 | hidden_details; |
124 | for (const DiagnosticDetail &detail : details) { |
125 | if (!show_inline || !detail.source_location) { |
126 | other_details.push_back(x: detail); |
127 | continue; |
128 | } |
129 | if (detail.source_location->hidden) { |
130 | hidden_details.push_back(x: detail); |
131 | continue; |
132 | } |
133 | if (!detail.source_location->in_user_input) { |
134 | other_details.push_back(x: detail); |
135 | continue; |
136 | } |
137 | |
138 | remaining_details.push_back(x: detail); |
139 | } |
140 | |
141 | // Sort the diagnostics. |
142 | auto sort = [](std::vector<DiagnosticDetail> &ds) { |
143 | llvm::stable_sort(Range&: ds, C: [](auto &d1, auto &d2) { |
144 | auto l1 = d1.source_location.value_or(DiagnosticDetail::SourceLocation{}); |
145 | auto l2 = d2.source_location.value_or(DiagnosticDetail::SourceLocation{}); |
146 | return std::tie(l1.line, l1.column) < std::tie(l2.line, l2.column); |
147 | }); |
148 | }; |
149 | sort(remaining_details); |
150 | sort(other_details); |
151 | sort(hidden_details); |
152 | |
153 | // Print a line with caret indicator(s) below the lldb prompt + command. |
154 | const size_t padding = *offset_in_command; |
155 | stream << std::string(padding, ' '); |
156 | { |
157 | size_t x_pos = 1; |
158 | for (const DiagnosticDetail &detail : remaining_details) { |
159 | auto &loc = *detail.source_location; |
160 | |
161 | if (x_pos > loc.column) |
162 | continue; |
163 | |
164 | stream << std::string(loc.column - x_pos, ' ') << cursor; |
165 | x_pos = loc.column + 1; |
166 | for (unsigned i = 0; i + 1 < loc.length; ++i) { |
167 | stream << underline; |
168 | x_pos += 1; |
169 | } |
170 | } |
171 | } |
172 | stream << '\n'; |
173 | |
174 | // Reverse the order within groups of diagnostics that are on the same column. |
175 | auto group = [](std::vector<DiagnosticDetail> &details) { |
176 | for (auto it = details.begin(), end = details.end(); it != end;) { |
177 | auto eq_end = std::find_if(first: it, last: end, pred: [&](const DiagnosticDetail &d) { |
178 | return d.source_location->column != it->source_location->column; |
179 | }); |
180 | std::reverse(first: it, last: eq_end); |
181 | it = eq_end; |
182 | } |
183 | }; |
184 | group(remaining_details); |
185 | |
186 | // Work through each detail in reverse order using the vector/stack. |
187 | bool did_print = false; |
188 | for (; !remaining_details.empty(); remaining_details.pop_back()) { |
189 | const auto &detail = remaining_details.back(); |
190 | // Get the information to print this detail and remove it from the stack. |
191 | // Print all the lines for all the other messages first. |
192 | stream << std::string(padding, ' '); |
193 | size_t x_pos = 1; |
194 | for (auto &remaining_detail : |
195 | llvm::ArrayRef(remaining_details).drop_back(N: 1)) { |
196 | uint16_t column = remaining_detail.source_location->column; |
197 | // Is this a note with the same column as another diagnostic? |
198 | if (column == detail.source_location->column) |
199 | continue; |
200 | |
201 | if (column >= x_pos) { |
202 | stream << std::string(column - x_pos, ' ') << vbar; |
203 | x_pos = column + 1; |
204 | } |
205 | } |
206 | |
207 | uint16_t column = detail.source_location->column; |
208 | // Print the line connecting the ^ with the error message. |
209 | if (column >= x_pos) |
210 | stream << std::string(column - x_pos, ' ') << joint << hbar << spacer; |
211 | |
212 | // Print a colorized string based on the message's severity type. |
213 | PrintSeverity(stream, severity: detail.severity); |
214 | |
215 | // Finally, print the message and start a new line. |
216 | stream << detail.message << '\n'; |
217 | did_print = true; |
218 | } |
219 | |
220 | // Print the non-located details. |
221 | for (const DiagnosticDetail &detail : other_details) { |
222 | PrintSeverity(stream, severity: detail.severity); |
223 | stream << detail.rendered << '\n'; |
224 | did_print = true; |
225 | } |
226 | |
227 | // Print the hidden details as a last resort. |
228 | if (!did_print) |
229 | for (const DiagnosticDetail &detail : hidden_details) { |
230 | PrintSeverity(stream, severity: detail.severity); |
231 | stream << detail.rendered << '\n'; |
232 | } |
233 | } |
234 | |
235 | } // namespace lldb_private |
236 | |