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