Serenity Operating System
1/*
2 * Copyright (c) 2020-2022, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "Spreadsheet.h"
8#include "JSIntegration.h"
9#include "Workbook.h"
10#include <AK/ByteBuffer.h>
11#include <AK/Debug.h>
12#include <AK/GenericLexer.h>
13#include <AK/JsonArray.h>
14#include <AK/JsonObject.h>
15#include <AK/JsonParser.h>
16#include <AK/ScopeGuard.h>
17#include <AK/TemporaryChange.h>
18#include <AK/URL.h>
19#include <LibCore/DeprecatedFile.h>
20#include <LibJS/Interpreter.h>
21#include <LibJS/Parser.h>
22#include <LibJS/Runtime/AbstractOperations.h>
23#include <LibJS/Runtime/FunctionObject.h>
24#include <ctype.h>
25#include <unistd.h>
26
27namespace Spreadsheet {
28
29Sheet::Sheet(StringView name, Workbook& workbook)
30 : Sheet(workbook)
31{
32 m_name = name;
33
34 for (size_t i = 0; i < default_row_count; ++i)
35 add_row();
36
37 for (size_t i = 0; i < default_column_count; ++i)
38 add_column();
39}
40
41Sheet::Sheet(Workbook& workbook)
42 : m_workbook(workbook)
43 , m_interpreter(JS::Interpreter::create<SheetGlobalObject>(m_workbook.vm(), *this))
44{
45 JS::DeferGC defer_gc(m_workbook.vm().heap());
46 m_global_object = static_cast<SheetGlobalObject*>(&m_interpreter->realm().global_object());
47 global_object().define_direct_property("workbook", m_workbook.workbook_object(), JS::default_attributes);
48 global_object().define_direct_property("thisSheet", &global_object(), JS::default_attributes); // Self-reference is unfortunate, but required.
49
50 // Sadly, these have to be evaluated once per sheet.
51 constexpr auto runtime_file_path = "/res/js/Spreadsheet/runtime.js"sv;
52 auto file_or_error = Core::DeprecatedFile::open(runtime_file_path, Core::OpenMode::ReadOnly);
53 if (!file_or_error.is_error()) {
54 auto buffer = file_or_error.value()->read_all();
55 auto script_or_error = JS::Script::parse(buffer, interpreter().realm(), runtime_file_path);
56 if (script_or_error.is_error()) {
57 warnln("Spreadsheet: Failed to parse runtime code");
58 for (auto& error : script_or_error.error()) {
59 // FIXME: This doesn't print hints anymore
60 warnln("SyntaxError: {}", error.to_deprecated_string());
61 }
62 } else {
63 auto result = interpreter().run(script_or_error.value());
64 if (result.is_error()) {
65 warnln("Spreadsheet: Failed to run runtime code:");
66 auto thrown_value = *result.throw_completion().value();
67 warn("Threw: {}", MUST(thrown_value.to_string_without_side_effects()));
68 if (thrown_value.is_object() && is<JS::Error>(thrown_value.as_object())) {
69 auto& error = static_cast<JS::Error const&>(thrown_value.as_object());
70 warnln(" with message '{}'", error.get_without_side_effects(interpreter().vm().names.message));
71 for (auto& traceback_frame : error.traceback()) {
72 auto& function_name = traceback_frame.function_name;
73 auto& source_range = traceback_frame.source_range;
74 dbgln(" {} at {}:{}:{}", function_name, source_range.filename(), source_range.start.line, source_range.start.column);
75 }
76 } else {
77 warnln();
78 }
79 }
80 }
81 }
82}
83
84JS::Interpreter& Sheet::interpreter() const
85{
86 return const_cast<JS::Interpreter&>(*m_interpreter);
87}
88
89size_t Sheet::add_row()
90{
91 return m_rows++;
92}
93
94static Optional<size_t> convert_from_string(StringView str, unsigned base = 26, StringView map = {})
95{
96 if (map.is_null())
97 map = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"sv;
98
99 VERIFY(base >= 2 && base <= map.length());
100
101 if (str.is_empty())
102 return {};
103
104 size_t value = 0;
105 auto const len = str.length();
106 for (auto i = 0u; i < len; i++) {
107 auto maybe_index = map.find(str[i]);
108 if (!maybe_index.has_value())
109 return {};
110 size_t digit_value = maybe_index.value();
111 value += (digit_value + 1) * AK::pow<float>(base, len - 1 - i);
112 }
113
114 return value - 1;
115}
116
117DeprecatedString Sheet::add_column()
118{
119 auto next_column = DeprecatedString::bijective_base_from(m_columns.size());
120 m_columns.append(next_column);
121 return next_column;
122}
123
124void Sheet::update()
125{
126 if (m_should_ignore_updates) {
127 m_update_requested = true;
128 return;
129 }
130 m_visited_cells_in_update.clear();
131 Vector<Cell&> cells_copy;
132
133 // Grab a copy as updates might insert cells into the table.
134 for (auto& it : m_cells) {
135 if (it.value->dirty()) {
136 cells_copy.append(*it.value);
137 m_workbook.set_dirty(true);
138 }
139 }
140
141 for (auto& cell : cells_copy)
142 update(cell);
143
144 m_visited_cells_in_update.clear();
145}
146
147void Sheet::update(Cell& cell)
148{
149 if (m_should_ignore_updates) {
150 m_update_requested = true;
151 return;
152 }
153 if (cell.dirty()) {
154 if (has_been_visited(&cell)) {
155 // This may be part of an cyclic reference chain,
156 // so just ignore it.
157 cell.clear_dirty();
158 return;
159 }
160 m_visited_cells_in_update.set(&cell);
161 cell.update_data({});
162 }
163}
164
165JS::ThrowCompletionOr<JS::Value> Sheet::evaluate(StringView source, Cell* on_behalf_of)
166{
167 TemporaryChange cell_change { m_current_cell_being_evaluated, on_behalf_of };
168 auto name = on_behalf_of ? on_behalf_of->name_for_javascript(*this) : "cell <unknown>"sv;
169 auto script_or_error = JS::Script::parse(
170 source,
171 interpreter().realm(),
172 name);
173
174 if (script_or_error.is_error())
175 return interpreter().vm().throw_completion<JS::SyntaxError>(TRY_OR_THROW_OOM(interpreter().vm(), script_or_error.error().first().to_string()));
176
177 return interpreter().run(script_or_error.value());
178}
179
180Cell* Sheet::at(StringView name)
181{
182 auto pos = parse_cell_name(name);
183 if (pos.has_value())
184 return at(pos.value());
185
186 return nullptr;
187}
188
189Cell* Sheet::at(Position const& position)
190{
191 auto it = m_cells.find(position);
192
193 if (it == m_cells.end())
194 return nullptr;
195
196 return it->value;
197}
198
199Optional<Position> Sheet::parse_cell_name(StringView name) const
200{
201 GenericLexer lexer(name);
202 auto col = lexer.consume_while(isalpha);
203 auto row = lexer.consume_while(isdigit);
204
205 if (!lexer.is_eof() || row.is_empty() || col.is_empty())
206 return {};
207
208 auto it = m_columns.find(col);
209 if (it == m_columns.end())
210 return {};
211
212 return Position { it.index(), row.to_uint().value() };
213}
214
215Optional<size_t> Sheet::column_index(StringView column_name) const
216{
217 auto maybe_index = convert_from_string(column_name);
218 if (!maybe_index.has_value())
219 return {};
220
221 auto index = maybe_index.value();
222 if (m_columns.size() <= index || m_columns[index] != column_name) {
223 auto it = m_columns.find(column_name);
224 if (it == m_columns.end())
225 return {};
226 index = it.index();
227 }
228
229 return index;
230}
231
232Optional<DeprecatedString> Sheet::column_arithmetic(StringView column_name, int offset)
233{
234 auto maybe_index = column_index(column_name);
235 if (!maybe_index.has_value())
236 return {};
237
238 if (offset < 0 && maybe_index.value() < (size_t)(0 - offset))
239 return m_columns.first();
240
241 auto index = maybe_index.value() + offset;
242 if (m_columns.size() > index)
243 return m_columns[index];
244
245 for (size_t i = m_columns.size(); i <= index; ++i)
246 add_column();
247
248 return m_columns.last();
249}
250
251Cell* Sheet::from_url(const URL& url)
252{
253 auto maybe_position = position_from_url(url);
254 if (!maybe_position.has_value())
255 return nullptr;
256
257 return at(maybe_position.value());
258}
259
260Optional<Position> Sheet::position_from_url(const URL& url) const
261{
262 if (!url.is_valid()) {
263 dbgln("Invalid url: {}", url.to_deprecated_string());
264 return {};
265 }
266
267 if (url.scheme() != "spreadsheet" || url.host() != "cell") {
268 dbgln("Bad url: {}", url.to_deprecated_string());
269 return {};
270 }
271
272 // FIXME: Figure out a way to do this cross-process.
273 VERIFY(url.path() == DeprecatedString::formatted("/{}", getpid()));
274
275 return parse_cell_name(url.fragment());
276}
277
278Position Sheet::offset_relative_to(Position const& base, Position const& offset, Position const& offset_base) const
279{
280 if (offset.column >= m_columns.size()) {
281 dbgln("Column '{}' does not exist!", offset.column);
282 return base;
283 }
284 if (offset_base.column >= m_columns.size()) {
285 dbgln("Column '{}' does not exist!", offset_base.column);
286 return base;
287 }
288 if (base.column >= m_columns.size()) {
289 dbgln("Column '{}' does not exist!", base.column);
290 return offset;
291 }
292
293 auto new_column = offset.column + base.column - offset_base.column;
294 auto new_row = offset.row + base.row - offset_base.row;
295
296 return { new_column, new_row };
297}
298
299Vector<CellChange> Sheet::copy_cells(Vector<Position> from, Vector<Position> to, Optional<Position> resolve_relative_to, CopyOperation copy_operation)
300{
301 Vector<CellChange> cell_changes;
302 // Disallow misaligned copies.
303 if (to.size() > 1 && from.size() != to.size()) {
304 dbgln("Cannot copy {} cells to {} cells", from.size(), to.size());
305 return cell_changes;
306 }
307
308 Vector<Position> target_cells;
309 for (auto& position : from)
310 target_cells.append(resolve_relative_to.has_value() ? offset_relative_to(to.first(), position, resolve_relative_to.value()) : to.first());
311
312 auto copy_to = [&](auto& source_position, Position target_position) {
313 auto& target_cell = ensure(target_position);
314 auto* source_cell = at(source_position);
315 auto previous_data = target_cell.data();
316
317 if (!source_cell) {
318 target_cell.set_data("");
319 cell_changes.append(CellChange(target_cell, previous_data));
320 return;
321 }
322
323 target_cell.copy_from(*source_cell);
324 cell_changes.append(CellChange(target_cell, previous_data));
325 if (copy_operation == CopyOperation::Cut && !target_cells.contains_slow(source_position)) {
326 cell_changes.append(CellChange(*source_cell, source_cell->data()));
327 source_cell->set_data("");
328 }
329 };
330
331 // Resolve each index as relative to the first index offset from the selection.
332 auto& target = to.first();
333
334 auto top_left_most_position_from = from.first();
335 auto bottom_right_most_position_from = from.first();
336 for (auto& position : from) {
337 if (position.row > bottom_right_most_position_from.row)
338 bottom_right_most_position_from = position;
339 else if (position.column > bottom_right_most_position_from.column)
340 bottom_right_most_position_from = position;
341
342 if (position.row < top_left_most_position_from.row)
343 top_left_most_position_from = position;
344 else if (position.column < top_left_most_position_from.column)
345 top_left_most_position_from = position;
346 }
347
348 Vector<Position> ordered_from;
349 auto current_column = top_left_most_position_from.column;
350 auto current_row = top_left_most_position_from.row;
351 for ([[maybe_unused]] auto& position : from) {
352 for (auto& position : from)
353 if (position.row == current_row && position.column == current_column)
354 ordered_from.append(position);
355
356 if (current_column >= bottom_right_most_position_from.column) {
357 current_column = top_left_most_position_from.column;
358 current_row += 1;
359 } else {
360 current_column += 1;
361 }
362 }
363
364 if (target.row > top_left_most_position_from.row || (target.row >= top_left_most_position_from.row && target.column > top_left_most_position_from.column))
365 ordered_from.reverse();
366
367 for (auto& position : ordered_from) {
368 dbgln_if(COPY_DEBUG, "Paste from '{}' to '{}'", position.to_url(*this), target.to_url(*this));
369 copy_to(position, resolve_relative_to.has_value() ? offset_relative_to(target, position, resolve_relative_to.value()) : target);
370 }
371
372 return cell_changes;
373}
374
375RefPtr<Sheet> Sheet::from_json(JsonObject const& object, Workbook& workbook)
376{
377 auto sheet = adopt_ref(*new Sheet(workbook));
378 auto rows = object.get_u32("rows"sv).value_or(default_row_count);
379 auto columns = object.get_array("columns"sv);
380 auto name = object.get_deprecated_string("name"sv).value_or("Sheet");
381 if (object.has("cells"sv) && !object.has_object("cells"sv))
382 return {};
383
384 sheet->set_name(name);
385
386 for (size_t i = 0; i < max(rows, (unsigned)Sheet::default_row_count); ++i)
387 sheet->add_row();
388
389 // FIXME: Better error checking.
390 if (columns.has_value()) {
391 columns->for_each([&](auto& value) {
392 sheet->m_columns.append(value.as_string());
393 return IterationDecision::Continue;
394 });
395 }
396
397 if (sheet->m_columns.size() < default_column_count && sheet->columns_are_standard()) {
398 for (size_t i = sheet->m_columns.size(); i < default_column_count; ++i)
399 sheet->add_column();
400 }
401
402 auto json = sheet->global_object().get_without_side_effects("JSON");
403 auto& parse_function = json.as_object().get_without_side_effects("parse").as_function();
404
405 auto read_format = [](auto& format, auto const& obj) {
406 if (auto value = obj.get_deprecated_string("foreground_color"sv); value.has_value())
407 format.foreground_color = Color::from_string(*value);
408 if (auto value = obj.get_deprecated_string("background_color"sv); value.has_value())
409 format.background_color = Color::from_string(*value);
410 };
411
412 if (auto cells = object.get_object("cells"sv); cells.has_value()) {
413 cells->for_each_member([&](auto& name, JsonValue const& value) {
414 auto position_option = sheet->parse_cell_name(name);
415 if (!position_option.has_value())
416 return IterationDecision::Continue;
417
418 auto position = position_option.value();
419 auto& obj = value.as_object();
420 auto kind = obj.get_deprecated_string("kind"sv).value_or("LiteralString") == "LiteralString" ? Cell::LiteralString : Cell::Formula;
421
422 OwnPtr<Cell> cell;
423 switch (kind) {
424 case Cell::LiteralString:
425 cell = make<Cell>(obj.get_deprecated_string("value"sv).value_or({}), position, *sheet);
426 break;
427 case Cell::Formula: {
428 auto& vm = sheet->interpreter().vm();
429 auto value_or_error = JS::call(vm, parse_function, json, JS::PrimitiveString::create(vm, obj.get_deprecated_string("value"sv).value_or({})));
430 if (value_or_error.is_error()) {
431 warnln("Failed to load previous value for cell {}, leaving as undefined", position.to_cell_identifier(sheet));
432 value_or_error = JS::js_undefined();
433 }
434 cell = make<Cell>(obj.get_deprecated_string("source"sv).value_or({}), value_or_error.release_value(), position, *sheet);
435 break;
436 }
437 }
438
439 auto type_name = obj.has("type"sv) ? obj.get_deprecated_string("type"sv).value_or({}) : "Numeric";
440 cell->set_type(type_name);
441
442 auto type_meta = obj.get_object("type_metadata"sv);
443 if (type_meta.has_value()) {
444 auto& meta_obj = type_meta.value();
445 auto meta = cell->type_metadata();
446 if (auto value = meta_obj.get_i32("length"sv); value.has_value())
447 meta.length = value.value();
448 if (auto value = meta_obj.get_deprecated_string("format"sv); value.has_value())
449 meta.format = value.value();
450 if (auto value = meta_obj.get_deprecated_string("alignment"sv); value.has_value()) {
451 auto alignment = Gfx::text_alignment_from_string(*value);
452 if (alignment.has_value())
453 meta.alignment = alignment.value();
454 }
455 read_format(meta.static_format, meta_obj);
456
457 cell->set_type_metadata(move(meta));
458 }
459
460 auto conditional_formats = obj.get_array("conditional_formats"sv);
461 auto cformats = cell->conditional_formats();
462 if (conditional_formats.has_value()) {
463 conditional_formats->for_each([&](const auto& fmt_val) {
464 if (!fmt_val.is_object())
465 return IterationDecision::Continue;
466
467 auto& fmt_obj = fmt_val.as_object();
468 auto fmt_cond = fmt_obj.get_deprecated_string("condition"sv).value_or({});
469 if (fmt_cond.is_empty())
470 return IterationDecision::Continue;
471
472 ConditionalFormat fmt;
473 fmt.condition = move(fmt_cond);
474 read_format(fmt, fmt_obj);
475 cformats.append(move(fmt));
476
477 return IterationDecision::Continue;
478 });
479 cell->set_conditional_formats(move(cformats));
480 }
481
482 auto evaluated_format = obj.get_object("evaluated_formats"sv);
483 if (evaluated_format.has_value()) {
484 auto& evaluated_format_obj = evaluated_format.value();
485 auto& evaluated_fmts = cell->evaluated_formats();
486
487 read_format(evaluated_fmts, evaluated_format_obj);
488 }
489
490 sheet->m_cells.set(position, cell.release_nonnull());
491 return IterationDecision::Continue;
492 });
493 }
494
495 return sheet;
496}
497
498Position Sheet::written_data_bounds(Optional<size_t> column_index) const
499{
500 Position bound;
501 for (auto const& entry : m_cells) {
502 if (entry.value->data().is_empty())
503 continue;
504 if (column_index.has_value() && entry.key.column != *column_index)
505 continue;
506 if (entry.key.row >= bound.row)
507 bound.row = entry.key.row;
508 if (entry.key.column >= bound.column)
509 bound.column = entry.key.column;
510 }
511
512 return bound;
513}
514
515/// The sheet is allowed to have nonstandard column names
516/// this checks whether all existing columns are 'standard'
517/// (i.e. as generated by 'String::bijective_base_from()'
518bool Sheet::columns_are_standard() const
519{
520 for (size_t i = 0; i < m_columns.size(); ++i) {
521 if (m_columns[i] != DeprecatedString::bijective_base_from(i))
522 return false;
523 }
524
525 return true;
526}
527
528JsonObject Sheet::to_json() const
529{
530 JsonObject object;
531 object.set("name", m_name);
532
533 auto save_format = [](auto const& format, auto& obj) {
534 if (format.foreground_color.has_value())
535 obj.set("foreground_color", format.foreground_color.value().to_deprecated_string());
536 if (format.background_color.has_value())
537 obj.set("background_color", format.background_color.value().to_deprecated_string());
538 };
539
540 auto bottom_right = written_data_bounds();
541
542 if (!columns_are_standard()) {
543 auto columns = JsonArray();
544 for (auto& column : m_columns)
545 columns.append(column);
546 object.set("columns", move(columns));
547 }
548 object.set("rows", bottom_right.row + 1);
549
550 JsonObject cells;
551 for (auto& it : m_cells) {
552 StringBuilder builder;
553 builder.append(column(it.key.column));
554 builder.appendff("{}", it.key.row);
555 auto key = builder.to_deprecated_string();
556
557 JsonObject data;
558 data.set("kind", it.value->kind() == Cell::Kind::Formula ? "Formula" : "LiteralString");
559 if (it.value->kind() == Cell::Formula) {
560 auto& vm = interpreter().vm();
561 data.set("source", it.value->data());
562 auto json = interpreter().realm().global_object().get_without_side_effects("JSON");
563 auto stringified_or_error = JS::call(vm, json.as_object().get_without_side_effects("stringify").as_function(), json, it.value->evaluated_data());
564 VERIFY(!stringified_or_error.is_error());
565 data.set("value", stringified_or_error.release_value().to_string_without_side_effects().release_value_but_fixme_should_propagate_errors().to_deprecated_string());
566 } else {
567 data.set("value", it.value->data());
568 }
569
570 // Set type & meta
571 auto& type = it.value->type();
572 auto& meta = it.value->type_metadata();
573 data.set("type", type.name());
574
575 JsonObject metadata_object;
576 metadata_object.set("length", meta.length);
577 metadata_object.set("format", meta.format);
578 metadata_object.set("alignment", Gfx::to_string(meta.alignment));
579 save_format(meta.static_format, metadata_object);
580
581 data.set("type_metadata", move(metadata_object));
582
583 // Set conditional formats
584 JsonArray conditional_formats;
585 for (auto& fmt : it.value->conditional_formats()) {
586 JsonObject fmt_object;
587 fmt_object.set("condition", fmt.condition);
588 save_format(fmt, fmt_object);
589
590 conditional_formats.append(move(fmt_object));
591 }
592
593 data.set("conditional_formats", move(conditional_formats));
594
595 auto& evaluated_formats = it.value->evaluated_formats();
596 JsonObject evaluated_formats_obj;
597
598 save_format(evaluated_formats, evaluated_formats_obj);
599 data.set("evaluated_formats", move(evaluated_formats_obj));
600
601 cells.set(key, move(data));
602 }
603 object.set("cells", move(cells));
604
605 return object;
606}
607
608Vector<Vector<DeprecatedString>> Sheet::to_xsv() const
609{
610 Vector<Vector<DeprecatedString>> data;
611
612 auto bottom_right = written_data_bounds();
613
614 // First row = headers.
615 size_t column_count = m_columns.size();
616 if (columns_are_standard()) {
617 column_count = bottom_right.column + 1;
618 Vector<DeprecatedString> cols;
619 for (size_t i = 0; i < column_count; ++i)
620 cols.append(m_columns[i]);
621 data.append(move(cols));
622 } else {
623 data.append(m_columns);
624 }
625
626 for (size_t i = 0; i <= bottom_right.row; ++i) {
627 Vector<DeprecatedString> row;
628 row.resize(column_count);
629 for (size_t j = 0; j < column_count; ++j) {
630 auto cell = at({ j, i });
631 if (cell) {
632 auto result = cell->typed_display();
633 if (result.has_value())
634 row[j] = result.value();
635 }
636 }
637
638 data.append(move(row));
639 }
640
641 return data;
642}
643
644RefPtr<Sheet> Sheet::from_xsv(Reader::XSV const& xsv, Workbook& workbook)
645{
646 auto cols = xsv.headers();
647 auto rows = xsv.size();
648
649 auto sheet = adopt_ref(*new Sheet(workbook));
650 if (xsv.has_explicit_headers()) {
651 sheet->m_columns = cols;
652 } else {
653 sheet->m_columns.ensure_capacity(cols.size());
654 for (size_t i = 0; i < cols.size(); ++i)
655 sheet->m_columns.append(DeprecatedString::bijective_base_from(i));
656 }
657 for (size_t i = 0; i < max(rows, Sheet::default_row_count); ++i)
658 sheet->add_row();
659 if (sheet->columns_are_standard()) {
660 for (size_t i = sheet->m_columns.size(); i < Sheet::default_column_count; ++i)
661 sheet->add_column();
662 }
663
664 for (auto row : xsv) {
665 for (size_t i = 0; i < cols.size(); ++i) {
666 auto str = row[i];
667 if (str.is_empty())
668 continue;
669 Position position { i, row.index() };
670 auto cell = make<Cell>(str, position, *sheet);
671 sheet->m_cells.set(position, move(cell));
672 }
673 }
674
675 return sheet;
676}
677
678JsonObject Sheet::gather_documentation() const
679{
680 JsonObject object;
681 const JS::PropertyKey doc_name { "__documentation" };
682
683 auto add_docs_from = [&](auto& it, auto& global_object) {
684 auto value = global_object.get(it.key).release_value();
685 if (!value.is_function() && !value.is_object())
686 return;
687
688 auto& value_object = value.is_object() ? value.as_object() : value.as_function();
689 if (!value_object.has_own_property(doc_name).release_value())
690 return;
691
692 auto doc = value_object.get(doc_name).release_value();
693 if (!doc.is_string())
694 return;
695
696 JsonParser parser(doc.to_string_without_side_effects().release_value_but_fixme_should_propagate_errors());
697 auto doc_object = parser.parse();
698
699 if (!doc_object.is_error())
700 object.set(it.key.to_display_string(), doc_object.value());
701 else
702 dbgln("Sheet::gather_documentation(): Failed to parse the documentation for '{}'!", it.key.to_display_string());
703 };
704
705 for (auto& it : interpreter().realm().global_object().shape().property_table())
706 add_docs_from(it, interpreter().realm().global_object());
707
708 for (auto& it : global_object().shape().property_table())
709 add_docs_from(it, global_object());
710
711 m_cached_documentation = move(object);
712 return m_cached_documentation.value();
713}
714
715DeprecatedString Sheet::generate_inline_documentation_for(StringView function, size_t argument_index)
716{
717 if (!m_cached_documentation.has_value())
718 gather_documentation();
719
720 auto& docs = m_cached_documentation.value();
721 auto entry = docs.get_object(function);
722 if (!entry.has_value())
723 return DeprecatedString::formatted("{}(...???{})", function, argument_index);
724
725 auto& entry_object = entry.value();
726 size_t argc = entry_object.get_integer<int>("argc"sv).value_or(0);
727 auto argnames_value = entry_object.get_array("argnames"sv);
728 if (!argnames_value.has_value())
729 return DeprecatedString::formatted("{}(...{}???{})", function, argc, argument_index);
730 auto& argnames = argnames_value.value();
731 StringBuilder builder;
732 builder.appendff("{}(", function);
733 for (size_t i = 0; i < (size_t)argnames.size(); ++i) {
734 if (i != 0 && i < (size_t)argnames.size())
735 builder.append(", "sv);
736 if (i == argument_index)
737 builder.append('<');
738 else if (i >= argc)
739 builder.append('[');
740 builder.append(argnames[i].to_deprecated_string());
741 if (i == argument_index)
742 builder.append('>');
743 else if (i >= argc)
744 builder.append(']');
745 }
746
747 builder.append(')');
748 return builder.to_deprecated_string();
749}
750
751DeprecatedString Position::to_cell_identifier(Sheet const& sheet) const
752{
753 return DeprecatedString::formatted("{}{}", sheet.column(column), row);
754}
755
756URL Position::to_url(Sheet const& sheet) const
757{
758 URL url;
759 url.set_scheme("spreadsheet");
760 url.set_host("cell");
761 url.set_paths({ DeprecatedString::number(getpid()) });
762 url.set_fragment(to_cell_identifier(sheet));
763 return url;
764}
765
766CellChange::CellChange(Cell& cell, DeprecatedString const& previous_data)
767 : m_cell(cell)
768 , m_previous_data(previous_data)
769{
770 m_new_data = cell.data();
771}
772
773}