mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-20 10:02:51 +00:00
Before this change, each AST node had a 64-byte SourceRange member. This SourceRange had the following layout: filename: StringView (16 bytes) start: Position (24 bytes) end: Position (24 bytes) The Position structs have { line, column, offset }, all members size_t. To reduce memory consumption, AST nodes now only store the following: source_code: NonnullRefPtr<SourceCode> (8 bytes) start_offset: u32 (4 bytes) end_offset: u32 (4 bytes) SourceCode is a new ref-counted data structure that keeps the filename and original parsed source code in a single location, and all AST nodes have a pointer to it. The start_offset and end_offset can be turned into (line, column) when necessary by calling SourceCode::range_from_offsets(). This will walk the source code string and compute line/column numbers on the fly, so it's not necessarily fast, but it should be rare since this information is primarily used for diagnostics and exception stack traces. With this, ASTNode shrinks from 80 bytes to 32 bytes. This gives us a ~23% reduction in memory usage when loading twitter.com/awesomekling (330 MiB before, 253 MiB after!) :^)
224 lines
7.2 KiB
C++
224 lines
7.2 KiB
C++
/*
|
|
* Copyright (c) 2020-2022, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "SpreadsheetModel.h"
|
|
#include "ConditionalFormatting.h"
|
|
#include <AK/URL.h>
|
|
#include <LibGUI/AbstractView.h>
|
|
#include <LibJS/Runtime/Error.h>
|
|
#include <LibJS/Runtime/Object.h>
|
|
|
|
namespace Spreadsheet {
|
|
|
|
GUI::Variant SheetModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
|
{
|
|
if (!index.is_valid())
|
|
return {};
|
|
|
|
if (role == GUI::ModelRole::Display) {
|
|
auto const* cell = m_sheet->at({ (size_t)index.column(), (size_t)index.row() });
|
|
if (!cell)
|
|
return String::empty();
|
|
|
|
Function<String(JS::Value)> to_string_as_exception = [&](JS::Value value) {
|
|
auto& vm = cell->sheet().global_object().vm();
|
|
StringBuilder builder;
|
|
builder.append("Error: "sv);
|
|
if (value.is_object()) {
|
|
auto& object = value.as_object();
|
|
if (is<JS::Error>(object)) {
|
|
auto message = object.get_without_side_effects("message");
|
|
auto error = message.to_string(vm);
|
|
if (error.is_throw_completion())
|
|
builder.append(message.to_string_without_side_effects());
|
|
else
|
|
builder.append(error.release_value());
|
|
return builder.to_string();
|
|
}
|
|
}
|
|
auto error_message = value.to_string(vm);
|
|
|
|
if (error_message.is_throw_completion())
|
|
return to_string_as_exception(*error_message.release_error().value());
|
|
|
|
builder.append(error_message.release_value());
|
|
return builder.to_string();
|
|
};
|
|
|
|
if (cell->kind() == Spreadsheet::Cell::Formula) {
|
|
if (auto opt_throw_value = cell->thrown_value(); opt_throw_value.has_value())
|
|
return to_string_as_exception(*opt_throw_value);
|
|
}
|
|
|
|
auto display = cell->typed_display();
|
|
if (display.is_error())
|
|
return to_string_as_exception(*display.release_error().value());
|
|
|
|
return display.release_value();
|
|
}
|
|
|
|
if (role == GUI::ModelRole::MimeData)
|
|
return Position { (size_t)index.column(), (size_t)index.row() }.to_url(m_sheet).to_string();
|
|
|
|
if (role == GUI::ModelRole::TextAlignment) {
|
|
auto const* cell = m_sheet->at({ (size_t)index.column(), (size_t)index.row() });
|
|
if (!cell)
|
|
return {};
|
|
|
|
return cell->type_metadata().alignment;
|
|
}
|
|
|
|
if (role == GUI::ModelRole::ForegroundColor) {
|
|
auto const* cell = m_sheet->at({ (size_t)index.column(), (size_t)index.row() });
|
|
if (!cell)
|
|
return {};
|
|
|
|
if (cell->kind() == Spreadsheet::Cell::Formula) {
|
|
if (cell->thrown_value().has_value())
|
|
return Color(Color::Red);
|
|
}
|
|
|
|
if (cell->evaluated_formats().foreground_color.has_value())
|
|
return cell->evaluated_formats().foreground_color.value();
|
|
|
|
if (auto color = cell->type_metadata().static_format.foreground_color; color.has_value())
|
|
return color.value();
|
|
|
|
return {};
|
|
}
|
|
|
|
if (role == GUI::ModelRole::BackgroundColor) {
|
|
auto const* cell = m_sheet->at({ (size_t)index.column(), (size_t)index.row() });
|
|
if (!cell)
|
|
return {};
|
|
|
|
if (cell->evaluated_formats().background_color.has_value())
|
|
return cell->evaluated_formats().background_color.value();
|
|
|
|
if (auto color = cell->type_metadata().static_format.background_color; color.has_value())
|
|
return color.value();
|
|
|
|
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"sv)) {
|
|
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 "sv)) {
|
|
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 {};
|
|
}
|
|
|
|
RefPtr<Core::MimeData> SheetModel::mime_data(const GUI::ModelSelection& selection) const
|
|
{
|
|
auto mime_data = GUI::Model::mime_data(selection);
|
|
|
|
bool first = true;
|
|
const GUI::ModelIndex* cursor = nullptr;
|
|
const_cast<SheetModel*>(this)->for_each_view([&](const GUI::AbstractView& view) {
|
|
if (!first)
|
|
return;
|
|
cursor = &view.cursor_index();
|
|
first = false;
|
|
});
|
|
|
|
VERIFY(cursor);
|
|
|
|
Position cursor_position { (size_t)cursor->column(), (size_t)cursor->row() };
|
|
auto mime_data_buffer = mime_data->data("text/x-spreadsheet-data");
|
|
auto new_data = String::formatted("{}\n{}",
|
|
cursor_position.to_url(m_sheet).to_string(),
|
|
StringView(mime_data_buffer));
|
|
mime_data->set_data("text/x-spreadsheet-data", new_data.to_byte_buffer());
|
|
|
|
return mime_data;
|
|
}
|
|
|
|
String SheetModel::column_name(int index) const
|
|
{
|
|
if (index < 0)
|
|
return {};
|
|
|
|
return m_sheet->column(index);
|
|
}
|
|
|
|
bool SheetModel::is_editable(const GUI::ModelIndex& index) const
|
|
{
|
|
if (!index.is_valid())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void SheetModel::set_data(const GUI::ModelIndex& index, const GUI::Variant& value)
|
|
{
|
|
if (!index.is_valid())
|
|
return;
|
|
|
|
auto& cell = m_sheet->ensure({ (size_t)index.column(), (size_t)index.row() });
|
|
auto previous_data = cell.data();
|
|
cell.set_data(value.to_string());
|
|
if (on_cell_data_change)
|
|
on_cell_data_change(cell, previous_data);
|
|
did_update(UpdateFlag::DontInvalidateIndices);
|
|
}
|
|
|
|
void SheetModel::update()
|
|
{
|
|
m_sheet->update();
|
|
did_update(UpdateFlag::DontInvalidateIndices);
|
|
}
|
|
|
|
CellsUndoCommand::CellsUndoCommand(Vector<CellChange> cell_changes)
|
|
{
|
|
m_cell_changes = cell_changes;
|
|
}
|
|
|
|
CellsUndoCommand::CellsUndoCommand(Cell& cell, String const& previous_data)
|
|
{
|
|
m_cell_changes.append(CellChange(cell, previous_data));
|
|
}
|
|
|
|
void CellsUndoCommand::undo()
|
|
{
|
|
for (size_t i = 0; i < m_cell_changes.size(); ++i) {
|
|
m_cell_changes[i].cell().set_data(m_cell_changes[i].previous_data());
|
|
}
|
|
}
|
|
|
|
void CellsUndoCommand::redo()
|
|
{
|
|
for (size_t i = 0; i < m_cell_changes.size(); ++i) {
|
|
m_cell_changes[i].cell().set_data(m_cell_changes[i].new_data());
|
|
}
|
|
}
|
|
|
|
}
|