Show source-linked diagnostics and expandable build logs
This commit is contained in:
@@ -1,5 +1,28 @@
|
|||||||
# Developer diagnostics
|
# Developer diagnostics
|
||||||
|
|
||||||
|
## Build errors and source navigation
|
||||||
|
|
||||||
|
Build and export jobs report compiler, clang-cl/MSVC, and Lua errors as structured
|
||||||
|
Console rows. Each row shows severity, message, and a one-based source location.
|
||||||
|
Choose **Open source** to open a file under the current project's `Scripts/`
|
||||||
|
directory at that line and column. Diagnostics from engine or external files remain
|
||||||
|
visible but have no source action. The **Jobs** tab shows whether a verified build
|
||||||
|
generation was reused, the elapsed phases, and an expandable raw process log;
|
||||||
|
**Copy full log** retains the complete available output for troubleshooting.
|
||||||
|
|
||||||
|
The same action is available over Editor MCP:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"command":"faset_source_open","arguments":{"path":"Scripts/Gameplay.cpp","line":17,"column":4}}
|
||||||
|
```
|
||||||
|
|
||||||
|
`faset_script_open` remains Lua-only. Both commands use the project's optional
|
||||||
|
`editor.script_editor` argv array, or Zed by default. The tokens `{file}`,
|
||||||
|
`{line}`, `{column}`, and `{project}` may appear within one argument, for
|
||||||
|
example `["zed", "{file}:{line}:{column}"]`. Faset passes each argument
|
||||||
|
literally to the editor process without shell expansion. An invalid editor
|
||||||
|
command produces a source-editor error without changing the build result.
|
||||||
|
|
||||||
The optional Dear ImGui overlay shows renderer counters inside the Editor. The Editor's normal interface remains the retained Faset UI. Enable the diagnostic build explicitly:
|
The optional Dear ImGui overlay shows renderer counters inside the Editor. The Editor's normal interface remains the retained Faset UI. Enable the diagnostic build explicitly:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ struct EditorUI::Impl {
|
|||||||
Json instance_selection = Json::array(), template_conflicts = Json::array();
|
Json instance_selection = Json::array(), template_conflicts = Json::array();
|
||||||
std::string document, selected, source_file, asset_filter,
|
std::string document, selected, source_file, asset_filter,
|
||||||
active_bottom = "assets", menu, command_name = "faset_documents", status = "Ready",
|
active_bottom = "assets", menu, command_name = "faset_documents", status = "Ready",
|
||||||
gizmo_mode = "Move";
|
gizmo_mode = "Move", expanded_job_log;
|
||||||
Json current, resolved, files = Json::array(), assets = Json::array(), schemas = Json::object(),
|
Json current, resolved, files = Json::array(), assets = Json::array(), schemas = Json::object(),
|
||||||
recovery = Json::array();
|
recovery = Json::array();
|
||||||
std::uint64_t shown_revision = std::numeric_limits<std::uint64_t>::max();
|
std::uint64_t shown_revision = std::numeric_limits<std::uint64_t>::max();
|
||||||
@@ -2043,6 +2043,45 @@ struct EditorUI::Impl {
|
|||||||
trim_children(conflict_list, conflict_keep);
|
trim_children(conflict_list, conflict_keep);
|
||||||
auto& console = *ui.find("console-items");
|
auto& console = *ui.find("console-items");
|
||||||
std::set<std::string> keep;
|
std::set<std::string> keep;
|
||||||
|
std::size_t shown_diagnostics{};
|
||||||
|
for (const auto& job : all_jobs) {
|
||||||
|
const auto id = job.at("id").get<std::string>();
|
||||||
|
const auto diagnostics = job.value("diagnostics", Json::array());
|
||||||
|
if (!diagnostics.is_array())
|
||||||
|
continue;
|
||||||
|
for (std::size_t index = 0; index < diagnostics.size() && shown_diagnostics < 60;
|
||||||
|
++index, ++shown_diagnostics) {
|
||||||
|
const auto& diagnostic = diagnostics[index];
|
||||||
|
const auto row_id = "diagnostic-" + id + "-" + std::to_string(index);
|
||||||
|
auto& row = console.add(Kind::Row, row_id);
|
||||||
|
row.layout.height = 27;
|
||||||
|
row.layout.gap = 8;
|
||||||
|
keep.insert(row_id);
|
||||||
|
const auto severity = diagnostic.value("severity", std::string("error"));
|
||||||
|
label(row, row_id + "-severity", severity, 68);
|
||||||
|
auto message = diagnostic.value("message", std::string());
|
||||||
|
auto& detail = row.add(Kind::Label, row_id + "-message", message);
|
||||||
|
detail.layout.flex = 1;
|
||||||
|
detail.tooltip = diagnostic.value("phase", std::string()) + ": " + message;
|
||||||
|
const auto file = diagnostic.value("file", std::string());
|
||||||
|
const auto line = diagnostic.value("line", 1);
|
||||||
|
const auto column = diagnostic.value("column", 1);
|
||||||
|
const auto location = file.empty()
|
||||||
|
? std::string("—")
|
||||||
|
: file + ":" + std::to_string(line) + ":" +
|
||||||
|
std::to_string(column);
|
||||||
|
label(row, row_id + "-location", location, 235);
|
||||||
|
auto& open = button(
|
||||||
|
row, row_id + "-open", "Open source",
|
||||||
|
[this, file, line, column] {
|
||||||
|
call("faset_source_open",
|
||||||
|
{{"path", file}, {"line", line}, {"column", column}});
|
||||||
|
},
|
||||||
|
115);
|
||||||
|
open.enabled = !file.empty();
|
||||||
|
open.appearance = ui::Appearance::Quiet;
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto& logs = session.logs();
|
const auto& logs = session.logs();
|
||||||
for (std::size_t i = logs.size() > 150 ? logs.size() - 150 : 0; i < logs.size(); ++i) {
|
for (std::size_t i = logs.size() > 150 ? logs.size() - 150 : 0; i < logs.size(); ++i) {
|
||||||
std::string line = logs[i];
|
std::string line = logs[i];
|
||||||
@@ -2065,11 +2104,25 @@ struct EditorUI::Impl {
|
|||||||
row.layout.height = 28;
|
row.layout.height = 28;
|
||||||
keep.insert(row.id);
|
keep.insert(row.id);
|
||||||
const auto state = job.value("state", std::string());
|
const auto state = job.value("state", std::string());
|
||||||
|
const auto result = job.value("result", Json::object());
|
||||||
|
std::string reuse;
|
||||||
|
if (result.is_object() && result.value("schema_cache_hit", false))
|
||||||
|
reuse = " · verified reuse";
|
||||||
|
if (result.is_object() && result.contains("phase_times_ms") &&
|
||||||
|
result.at("phase_times_ms").is_object())
|
||||||
|
reuse += " · " +
|
||||||
|
std::to_string(result.at("phase_times_ms").value("total", 0)) + " ms";
|
||||||
auto& text = row.add(Kind::Label, "job-text-" + id,
|
auto& text = row.add(Kind::Label, "job-text-" + id,
|
||||||
job.value("kind", std::string("Job")) + " · " + state + " · " +
|
job.value("kind", std::string("Job")) + " · " + state + " · " +
|
||||||
job.value("stage", std::string()) + " " +
|
job.value("stage", std::string()) + " " +
|
||||||
std::to_string(int(job.value("progress", 0.0) * 100)) + "%");
|
std::to_string(int(job.value("progress", 0.0) * 100)) + "%" +
|
||||||
|
reuse);
|
||||||
text.layout.flex = 1;
|
text.layout.flex = 1;
|
||||||
|
auto& log_button = button(
|
||||||
|
row, "job-log-toggle-" + id,
|
||||||
|
expanded_job_log == id ? "Hide log" : "View log",
|
||||||
|
[this, id] { expanded_job_log = expanded_job_log == id ? "" : id; }, 86);
|
||||||
|
log_button.appearance = ui::Appearance::Quiet;
|
||||||
auto& cancel = button(
|
auto& cancel = button(
|
||||||
row, "job-cancel-" + id, "Cancel",
|
row, "job-cancel-" + id, "Cancel",
|
||||||
[this, id] { call("faset_job_cancel", {{"id", id}}); }, 72);
|
[this, id] { call("faset_job_cancel", {{"id", id}}); }, 72);
|
||||||
@@ -2078,6 +2131,38 @@ struct EditorUI::Impl {
|
|||||||
row, "job-review-" + id, "Review removals", [this] { active_bottom = "conflicts"; },
|
row, "job-review-" + id, "Review removals", [this] { active_bottom = "conflicts"; },
|
||||||
135);
|
135);
|
||||||
review.visible = job.value("kind", std::string()) == "import" && state == "conflict";
|
review.visible = job.value("kind", std::string()) == "import" && state == "conflict";
|
||||||
|
if (expanded_job_log == id) {
|
||||||
|
const auto panel_id = "job-log-panel-" + id;
|
||||||
|
auto& panel = jobs.add(Kind::Column, panel_id);
|
||||||
|
panel.layout.height = 170;
|
||||||
|
panel.layout.scroll = true;
|
||||||
|
panel.layout.gap = 1;
|
||||||
|
keep.insert(panel_id);
|
||||||
|
std::set<std::string> log_keep;
|
||||||
|
const auto raw = job.value("log", std::string());
|
||||||
|
auto& copy = button(panel, panel_id + "-copy", "Copy full log",
|
||||||
|
[this, raw] { renderer.set_clipboard(raw); }, 110);
|
||||||
|
copy.appearance = ui::Appearance::Quiet;
|
||||||
|
log_keep.insert(copy.id);
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
std::size_t begin{};
|
||||||
|
while (begin < raw.size()) {
|
||||||
|
const auto end = raw.find('\n', begin);
|
||||||
|
lines.push_back(raw.substr(begin, end == std::string::npos
|
||||||
|
? std::string::npos
|
||||||
|
: end - begin));
|
||||||
|
if (end == std::string::npos)
|
||||||
|
break;
|
||||||
|
begin = end + 1;
|
||||||
|
}
|
||||||
|
const auto first = lines.size() > 100 ? lines.size() - 100 : 0;
|
||||||
|
for (std::size_t index = first; index < lines.size(); ++index) {
|
||||||
|
const auto line_id = panel_id + "-line-" + std::to_string(index);
|
||||||
|
label(panel, line_id, lines[index].substr(0, 600));
|
||||||
|
log_keep.insert(line_id);
|
||||||
|
}
|
||||||
|
trim_children(panel, log_keep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (keep.empty()) {
|
if (keep.empty()) {
|
||||||
label(jobs, "jobs-empty", "No active import, build or export jobs.");
|
label(jobs, "jobs-empty", "No active import, build or export jobs.");
|
||||||
|
|||||||
Reference in New Issue
Block a user