236 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Host/common/DiagnosticsRendering.h"10#include <cstdint>11 12using namespace lldb_private;13using namespace lldb;14 15namespace lldb_private {16 17char DiagnosticError::ID;18 19lldb::ErrorType DiagnosticError::GetErrorType() const {20 return lldb::eErrorTypeExpression;21}22 23StructuredData::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 63static 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 85void 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 color101 // attribute as a proxy for whether the terminal supports Unicode102 // characters. In the future it might make sense to move this into103 // 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 llvm::stable_sort(ds, [](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 (; !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(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, 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, 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, detail.severity);231 stream << detail.rendered << '\n';232 }233}234 235} // namespace lldb_private236