xref: /llvm-project/lldb/source/Utility/DiagnosticsRendering.cpp (revision d9cc37fea7b02954079ca59e8f7f28cffacc7e9e)
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("version", 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("file", sloc->file.GetPath());
36       sloc_up->AddIntegerItem("line", sloc->line);
37       sloc_up->AddIntegerItem("length", sloc->length);
38       sloc_up->AddBooleanItem("hidden", sloc->hidden);
39       sloc_up->AddBooleanItem("in_user_input", sloc->in_user_input);
40       detail_up->AddItem("source_location", 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("severity", severity);
55     detail_up->AddStringItem("message", diag.message);
56     detail_up->AddStringItem("rendered", diag.rendered);
57     array_up->AddItem(std::move(detail_up));
58   }
59   dict_up->AddItem("details", 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, 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(detail);
127       continue;
128     }
129     if (detail.source_location->hidden) {
130       hidden_details.push_back(detail);
131       continue;
132     }
133     if (!detail.source_location->in_user_input) {
134       other_details.push_back(detail);
135       continue;
136     }
137 
138     remaining_details.push_back(detail);
139   }
140 
141   // Sort the diagnostics.
142   auto sort = [](std::vector<DiagnosticDetail> &ds) {
143     std::stable_sort(ds.begin(), ds.end(), [](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(it, end, [&](const DiagnosticDetail &d) {
178         return d.source_location->column != it->source_location->column;
179       });
180       std::reverse(it, 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 (auto detail = remaining_details.rbegin();
189        detail != remaining_details.rend();
190        ++detail, remaining_details.pop_back()) {
191     // Get the information to print this detail and remove it from the stack.
192     // Print all the lines for all the other messages first.
193     stream << std::string(padding, ' ');
194     size_t x_pos = 1;
195     for (auto &remaining_detail :
196          llvm::ArrayRef(remaining_details).drop_back(1)) {
197       uint16_t column = remaining_detail.source_location->column;
198       // Is this a note with the same column as another diagnostic?
199       if (column == detail->source_location->column)
200         continue;
201 
202       if (column >= x_pos) {
203         stream << std::string(column - x_pos, ' ') << vbar;
204         x_pos = column + 1;
205       }
206     }
207 
208     uint16_t column = detail->source_location->column;
209     // Print the line connecting the ^ with the error message.
210     if (column >= x_pos)
211       stream << std::string(column - x_pos, ' ') << joint << hbar << spacer;
212 
213     // Print a colorized string based on the message's severity type.
214     PrintSeverity(stream, detail->severity);
215 
216     // Finally, print the message and start a new line.
217     stream << detail->message << '\n';
218     did_print = true;
219   }
220 
221   // Print the non-located details.
222   for (const DiagnosticDetail &detail : other_details) {
223     PrintSeverity(stream, detail.severity);
224     stream << detail.rendered << '\n';
225     did_print = true;
226   }
227 
228   // Print the hidden details as a last resort.
229   if (!did_print)
230     for (const DiagnosticDetail &detail : hidden_details) {
231       PrintSeverity(stream, detail.severity);
232       stream << detail.rendered << '\n';
233     }
234 }
235 
236 } // namespace lldb_private
237