mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 04:27:44 +00:00
Spreadsheet: Display a detailed view of a cell error on hover
With this, Spreadsheet can now show an almost full stack trace for the error (which is infintely better than just the error message).
This commit is contained in:
parent
64ef808aeb
commit
db4a5aafc8
6 changed files with 62 additions and 1 deletions
|
@ -50,6 +50,15 @@ struct Cell : public Weakable<Cell> {
|
|||
bool dirty() const { return m_dirty; }
|
||||
void clear_dirty() { m_dirty = false; }
|
||||
|
||||
StringView name_for_javascript(Sheet const& sheet) const
|
||||
{
|
||||
if (!m_name_for_javascript.is_empty())
|
||||
return m_name_for_javascript;
|
||||
|
||||
m_name_for_javascript = String::formatted("cell {}", m_position.to_cell_identifier(sheet));
|
||||
return m_name_for_javascript;
|
||||
}
|
||||
|
||||
void set_thrown_value(JS::Value value) { m_thrown_value = value; }
|
||||
Optional<JS::Value> thrown_value() const
|
||||
{
|
||||
|
@ -116,6 +125,7 @@ private:
|
|||
CellType const* m_type { nullptr };
|
||||
CellTypeMetadata m_type_metadata;
|
||||
Position m_position;
|
||||
mutable String m_name_for_javascript;
|
||||
|
||||
Vector<ConditionalFormat> m_conditional_formats;
|
||||
Format m_evaluated_formats;
|
||||
|
|
|
@ -165,8 +165,12 @@ void Sheet::update(Cell& cell)
|
|||
JS::ThrowCompletionOr<JS::Value> Sheet::evaluate(StringView source, Cell* on_behalf_of)
|
||||
{
|
||||
TemporaryChange cell_change { m_current_cell_being_evaluated, on_behalf_of };
|
||||
auto name = on_behalf_of ? on_behalf_of->name_for_javascript(*this) : "cell <unknown>"sv;
|
||||
auto script_or_error = JS::Script::parse(
|
||||
source,
|
||||
interpreter().realm(),
|
||||
name);
|
||||
|
||||
auto script_or_error = JS::Script::parse(source, interpreter().realm());
|
||||
if (script_or_error.is_error())
|
||||
return interpreter().vm().throw_completion<JS::SyntaxError>(interpreter().global_object(), script_or_error.error().first().to_string());
|
||||
|
||||
|
|
|
@ -103,6 +103,36 @@ GUI::Variant SheetModel::data(const GUI::ModelIndex& index, GUI::ModelRole role)
|
|||
return {};
|
||||
}
|
||||
|
||||
if (to_underlying(role) == to_underlying(Role::Tooltip)) {
|
||||
auto const* cell = m_sheet->at({ (size_t)index.column(), (size_t)index.row() });
|
||||
if (!cell || !cell->thrown_value().has_value())
|
||||
return {};
|
||||
|
||||
auto value = cell->thrown_value().value();
|
||||
if (!value.is_object())
|
||||
return {};
|
||||
|
||||
auto& object = value.as_object();
|
||||
if (!is<JS::Error>(object))
|
||||
return {};
|
||||
|
||||
auto& error = static_cast<JS::Error&>(object);
|
||||
auto const& trace = error.traceback();
|
||||
StringBuilder builder;
|
||||
builder.appendff("{}\n", error.get_without_side_effects(object.vm().names.message).to_string_without_side_effects());
|
||||
for (auto const& frame : trace.in_reverse()) {
|
||||
if (frame.source_range.filename.contains("runtime.js")) {
|
||||
if (frame.function_name == "<unknown>")
|
||||
builder.appendff(" in a builtin function at line {}, column {}\n", frame.source_range.start.line, frame.source_range.start.column);
|
||||
else
|
||||
builder.appendff(" while evaluating builtin '{}'\n", frame.function_name);
|
||||
} else if (frame.source_range.filename.starts_with("cell ")) {
|
||||
builder.appendff(" in cell '{}', at line {}, column {}\n", frame.source_range.filename.substring_view(5), frame.source_range.start.line, frame.source_range.start.column);
|
||||
}
|
||||
}
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,11 @@ namespace Spreadsheet {
|
|||
|
||||
class SheetModel final : public GUI::Model {
|
||||
public:
|
||||
enum class Role : UnderlyingType<GUI::ModelRole> {
|
||||
_Custom = to_underlying(GUI::ModelRole::Custom),
|
||||
Tooltip,
|
||||
};
|
||||
|
||||
static NonnullRefPtr<SheetModel> create(Sheet& sheet) { return adopt_ref(*new SheetModel(sheet)); }
|
||||
virtual ~SheetModel() override = default;
|
||||
|
||||
|
|
|
@ -77,6 +77,16 @@ void InfinitelyScrollableTableView::mousemove_event(GUI::MouseEvent& event)
|
|||
sheet.disable_updates();
|
||||
ScopeGuard sheet_update_enabler { [&] { sheet.enable_updates(); } };
|
||||
|
||||
if (!is_dragging()) {
|
||||
auto tooltip = model->data(index, static_cast<GUI::ModelRole>(SheetModel::Role::Tooltip));
|
||||
if (tooltip.is_string()) {
|
||||
set_tooltip(tooltip.as_string());
|
||||
show_or_hide_tooltip();
|
||||
} else {
|
||||
set_tooltip({});
|
||||
}
|
||||
}
|
||||
|
||||
m_is_hovering_cut_zone = false;
|
||||
m_is_hovering_extend_zone = false;
|
||||
if (selection().size() > 0 && !m_is_dragging_for_select) {
|
||||
|
|
|
@ -78,6 +78,8 @@ private:
|
|||
virtual void mouseup_event(GUI::MouseEvent&) override;
|
||||
virtual void drop_event(GUI::DropEvent&) override;
|
||||
|
||||
bool is_dragging() const { return m_is_dragging_for_cut || m_is_dragging_for_extend || m_is_dragging_for_select; }
|
||||
|
||||
bool m_is_hovering_extend_zone { false };
|
||||
bool m_is_hovering_cut_zone { false };
|
||||
bool m_is_dragging_for_select { false };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue