Forráskód Böngészése

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).
Ali Mohammad Pur 3 éve
szülő
commit
db4a5aafc8

+ 10 - 0
Userland/Applications/Spreadsheet/Cell.h

@@ -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;

+ 5 - 1
Userland/Applications/Spreadsheet/Spreadsheet.cpp

@@ -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());
 

+ 30 - 0
Userland/Applications/Spreadsheet/SpreadsheetModel.cpp

@@ -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 {};
 }
 

+ 5 - 0
Userland/Applications/Spreadsheet/SpreadsheetModel.h

@@ -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;
 

+ 10 - 0
Userland/Applications/Spreadsheet/SpreadsheetView.cpp

@@ -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) {

+ 2 - 0
Userland/Applications/Spreadsheet/SpreadsheetView.h

@@ -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 };