diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..35049cb --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index eb4a27c..7498835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,6 +2928,23 @@ dependencies = [ "sheetcraft-engine", ] +[[package]] +name = "sheetcraft-web" +version = "0.1.0" +dependencies = [ + "eframe", + "egui", + "js-sys", + "log", + "rfd", + "serde_json", + "sheetcraft-engine", + "sheetcraft-ui-egui", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "sheetcraft-xlsx" version = "0.1.0" diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..61538de --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,84 @@ +# SheetCraft roadmap + +SheetCraft aims at full Microsoft Excel parity — and to be better: faster, open (XLSX is the +native format), fully scriptable by agents (MCP + control channel + CLI), and available on the +web. + +## Status (2026-10-07) + +**Parity estimate** + +| Measure | Value | Notes | +|---|---|---| +| Ribbon/menu command catalog | **60%** (169 / 281) | `cargo xtask parity` → [`docs/parity.md`](docs/parity.md). Home is 97%. | +| Worksheet functions | **~93%** (478 + 32 evaluator built-ins of ~545) | Missing: ODDF*/ODDL*, BAHTTEXT, IMAGE, TRANSLATE, COPILOT/PY, STOCKHISTORY, RTD | +| Feature depth (weighted) | **~38%** | Pivot tables, Power Query, print/PDF, page layout view, chart formatting depth, ink, macros/scripts, threaded collaboration and solver/analysis tools are the big missing areas | + +**Estimated remaining effort to full parity: ~220 wall-clock hours of Claude Opus 5.5 agent +work** (≈ 9–10 days running continuously with 3–4 parallel agents), broken down below. + +**Working today** +- Engine: A1/R1C1 formula parser with reference adjustment, dependency-graph recalculation with + dynamic arrays (spill + `#SPILL!`), cycle detection, LET/LAMBDA/MAP/REDUCE/SCAN/BYROW/BYCOL/ + MAKEARRAY, structured table references, defined names, INDIRECT/OFFSET/INDEX references, + 478 worksheet functions (math, statistics and distributions, text and regex, lookup incl. + XLOOKUP/FILTER/SORT/UNIQUE/VSTACK, dates, financial incl. bonds, engineering incl. complex + numbers and Bessel, database, information). +- Number formats: Excel's full format-code language (sections, conditions, colours, dates and + elapsed time, fractions, scientific, accounting fill), General narrowing to column width. +- Files: XLSX read/write (styles, themes, merges, CF, validation, tables, comments, hyperlinks, + charts, pictures, sparklines, print settings, defined names, shared/array/dynamic formulas), + CSV/TSV with encoding and delimiter detection, HTML export, JSON. +- Editing: cell entry with type detection and auto-formats, in-cell and formula-bar editing, + point mode (click/drag/arrow references), reference colouring, F4 anchor cycling, function + autocomplete and argument hints, copy/cut/paste and Paste Special (values, formats, transpose, + operations, link, skip blanks), AutoFill series (numbers, dates, months, weekdays, custom lists, + text+number), Fill Series, Flash Fill, find/replace with wildcards, Go To / Go To Special, + undo/redo with history, format painter. +- Formatting: fonts, fills, all border styles, alignment (wrap, indent, rotation, merge), number + formats, 47 cell styles, 60 table styles, conditional formatting (cell rules, text, dates, + duplicates, top/bottom, averages, data bars, colour scales, icon sets, formulas), automatic + row heights, autofit, hide/unhide, freeze panes. +- Data: sort (multi-level, by colour, custom lists), AutoFilter (values, custom, top 10, + average, colour), tables with totals rows, remove duplicates, text to columns, data validation + with error alerts, grouping/outline, subtotals. +- Charts: column, bar, line, area, pie, doughnut, scatter, bubble, radar, histogram, waterfall, + funnel, treemap, sunburst, box & whisker, stock and combo charts; sparklines. +- UI (egui): Excel-style title bar with Quick Access Toolbar and AutoSave, the full ribbon with + galleries (cell styles, table styles, colour palettes, conditional formatting menus), + contextual Table/Chart Design tabs, formula bar with Name Box, virtualised grid (smooth + scrolling, zoom, frozen panes, text overflow), sheet tabs (rename, reorder, colour, hide), + status bar with Sum/Count/Average and zoom, dialogs (Format Cells, Formula Builder, Find and + Replace, Sort, Name Manager, Data Validation, Paste Special…), command palette, dark mode. +- Agents: every action is an engine command; JSON control channel in the desktop app (pointer, + keyboard, dialogs, screenshots); MCP server (headless or bridged); CLI (`info`, `convert`, + `eval`, `cat`, `run`, `commands`, `functions`, `mcp`, `send`). +- Platforms: macOS, Windows, Linux, FreeBSD; web (WASM via trunk). Release workflows for + signed/notarized macOS universal DMG, signed Windows x64/x86 MSI + zip, Linux AppImage/deb/rpm/ + tar.gz + Flatpak manifest, FreeBSD tarball and a web zip. + +## Milestones + +| # | Milestone | Status | Est. hours left | +|---|---|---|---| +| M0 | Foundation: core, number formats, formula language, functions, model, calc, XLSX/CSV, engine | ✅ done | — | +| M1 | Excel look: chrome, ribbon, formula bar, grid, tabs, status bar, control channel | ✅ done (polish continues) | 8 | +| M2 | Formatting completeness (Format Cells parity, borders drawing, themes fonts/effects, styles authoring) | 🟡 most | 10 | +| M3 | Editing power (drag-move/copy with Alt, insert copied cells, AutoComplete in column, AutoCorrect, spelling) | 🟡 most | 12 | +| M4 | Data (advanced filter, consolidate, what-if: goal seek, scenarios, data tables; Get & Transform lite) | 🟡 partial | 24 | +| M5 | Formulas tab (trace arrows on the grid, watch window, evaluate stepper UI, error-check UI, remaining functions) | 🟡 partial | 12 | +| M6 | Conditional formatting manager parity (rule editor dialog, all icon sets drawn, stop-if-true UI) | 🟡 most | 6 | +| M7 | Charts parity (element selection & Format pane, axes options, trendlines, error bars, chart sheets, styles/colours galleries, PivotCharts) | 🟡 partial | 28 | +| M8 | Insert & Review (icons library of our own, SmartArt-lite, equations, threaded comments pane, show changes, accessibility pane) | 🟡 partial | 18 | +| M9 | Page layout & print (Page Layout and Page Break Preview views, header/footer editor, print to PDF, print preview) | ⬜ | 26 | +| M10 | PivotTables (field list, layouts, grouping, calculated fields, slicers, timelines), Solver, Analysis ToolPak | ⬜ | 40 | +| M11 | Automation (record actions → replayable scripts, Rhai script editor, form controls) | ⬜ | 14 | +| M12 | Performance (1M-row files, parallel recalc, incremental geometry caches), accessibility, i18n | 🟡 partial | 16 | +| M13 | Release & polish (signed builds green on every platform, side-by-side pixel tuning, README screenshots) | 🟡 tooling ready | 6 | +| | **Total** | | **≈ 220** | + +## How to measure + +- `cargo xtask parity` — catalog coverage (feature names from Excel's ribbon and menus). +- `cargo test --workspace` — engine, formula, function, XLSX and UI behaviour. +- `cargo run --release -p sheetcraft-ui-egui --example snapshot -- --sample sales out.png` — look at the UI offscreen. diff --git a/apps/sheetcraft-web/Cargo.toml b/apps/sheetcraft-web/Cargo.toml new file mode 100644 index 0000000..aad352e --- /dev/null +++ b/apps/sheetcraft-web/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "sheetcraft-web" +description = "SheetCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)" +version.workspace = true +edition.workspace = true +license.workspace = true +publish = false + +[lints] +workspace = true + +[[bin]] +name = "sheetcraft-web" +path = "src/main.rs" + +[dependencies] +sheetcraft-engine = { workspace = true } +sheetcraft-ui-egui = { workspace = true } +egui = { workspace = true } +log = { workspace = true } +serde_json = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit"] } +rfd = { workspace = true } +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "HtmlAnchorElement", "Blob", "BlobPropertyBag", "Url", "Location", "CssStyleDeclaration"] } diff --git a/apps/sheetcraft-web/Trunk.toml b/apps/sheetcraft-web/Trunk.toml new file mode 100644 index 0000000..948d368 --- /dev/null +++ b/apps/sheetcraft-web/Trunk.toml @@ -0,0 +1,10 @@ +# Build with: trunk build --release (from apps/sheetcraft-web) +[build] +target = "index.html" +dist = "../../dist/web" +public_url = "./" +filehash = true + +[serve] +addresses = ["127.0.0.1"] +port = 8771 diff --git a/apps/sheetcraft-web/index.html b/apps/sheetcraft-web/index.html new file mode 100644 index 0000000..bf979bd --- /dev/null +++ b/apps/sheetcraft-web/index.html @@ -0,0 +1,19 @@ + + + + + + SheetCraft + + + + + +
Loading SheetCraft…
+ + diff --git a/apps/sheetcraft-web/src/main.rs b/apps/sheetcraft-web/src/main.rs new file mode 100644 index 0000000..599ccdb --- /dev/null +++ b/apps/sheetcraft-web/src/main.rs @@ -0,0 +1,25 @@ +//! SheetCraft in the browser. +//! +//! Runs the same [`sheetcraft_ui_egui::SheetApp`] as the desktop app through eframe's web runner +//! (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release` from +//! this directory (output in `dist/web`). +//! +//! Differences from the desktop app: no TCP control channel; Open uses the browser file picker +//! (bytes arrive asynchronously through `Services::inbox`); Save downloads the file; dropped +//! files are read asynchronously. +//! +//! URL query flags: `?webgl` forces WebGL2; `?sample=sales|budget|grades` opens a sample. +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] + +#[cfg(target_arch = "wasm32")] +mod web; + +#[cfg(target_arch = "wasm32")] +fn main() { + web::start(); +} + +#[cfg(not(target_arch = "wasm32"))] +fn main() { + eprintln!("sheetcraft-web only runs in the browser: build it with `trunk build --release` in apps/sheetcraft-web"); +} diff --git a/apps/sheetcraft-web/src/web.rs b/apps/sheetcraft-web/src/web.rs new file mode 100644 index 0000000..8393b7f --- /dev/null +++ b/apps/sheetcraft-web/src/web.rs @@ -0,0 +1,146 @@ +//! The browser shell: web `Services`, drag-and-drop, and the eframe web runner. + +use sheetcraft_engine::Session; +use sheetcraft_ui_egui::{Inbox, Services, SheetApp}; +use wasm_bindgen::JsCast as _; + +const CANVAS_ID: &str = "sheetcraft_canvas"; +const LOADING_ID: &str = "sheetcraft_loading"; + +pub fn start() { + eframe::WebLogger::init(log::LevelFilter::Info).ok(); + wasm_bindgen_futures::spawn_local(async { + let Some(document) = web_sys::window().and_then(|w| w.document()) else { + log::error!("no document"); + return; + }; + let Some(canvas) = document.get_element_by_id(CANVAS_ID).and_then(|e| e.dyn_into::().ok()) else { + log::error!("missing "); + return; + }; + let mut options = eframe::WebOptions::default(); + if query().contains("webgl") + && let eframe::egui_wgpu::WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup + { + create.instance_descriptor.backends = eframe::wgpu::Backends::GL; + } + let result = eframe::WebRunner::new() + .start( + canvas, + options, + Box::new(move |cc| { + let inbox: Inbox = Inbox::default(); + let mut session = Session::new(); + let q = query(); + if let Some(name) = q.split(['?', '&']).find_map(|kv| kv.strip_prefix("sample=")) { + let _ = session.execute("file.new", serde_json::json!({"sample": name})); + } else if q.contains("sample") { + let _ = session.execute("file.new", serde_json::json!({"sample": "sales"})); + } + let app = SheetApp::new(session, services(inbox.clone(), cc.egui_ctx.clone())); + SheetApp::setup_context(&cc.egui_ctx, false); + Ok(Box::new(WebShell { app, inbox })) + }), + ) + .await; + if let Some(el) = document.get_element_by_id(LOADING_ID) { + match result { + Ok(()) => el.remove(), + Err(e) => el.set_inner_html(&format!("

SheetCraft failed to start: {e:?}

A browser with WebGPU or WebGL2 is required.

")), + } + } + }); +} + +fn query() -> String { + web_sys::window().and_then(|w| w.location().search().ok()).unwrap_or_default() +} + +struct WebShell { + app: SheetApp, + inbox: Inbox, +} + +impl eframe::App for WebShell { + fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + let dropped = ctx.input_mut(|i| std::mem::take(&mut i.raw.dropped_files)); + for f in dropped { + let inbox = self.inbox.clone(); + let ctx = ctx.clone(); + wasm_bindgen_futures::spawn_local(async move { + let name = f.path().file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| "dropped.xlsx".into()); + match f.bytes_async().await { + Ok(bytes) => { + inbox.lock().unwrap_or_else(|e| e.into_inner()).push((name, bytes)); + ctx.request_repaint(); + } + Err(e) => log::error!("couldn't read dropped file {name}: {e}"), + } + }); + } + self.app.logic(ctx); + } + + fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) { + self.app.raw_input_hook(raw); + } + + fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { + self.app.ui(ui); + } +} + +fn services(inbox: Inbox, ctx: egui::Context) -> Services { + let open_inbox = inbox.clone(); + Services { + open_async: Some(Box::new(move || { + let inbox = open_inbox.clone(); + let ctx = ctx.clone(); + wasm_bindgen_futures::spawn_local(async move { + let Some(file) = rfd::AsyncFileDialog::new().add_filter("Spreadsheets", &["xlsx", "xlsm", "csv", "tsv", "txt", "json"]).pick_file().await else { + return; + }; + let bytes = file.read().await; + inbox.lock().unwrap_or_else(|e| e.into_inner()).push((file.file_name(), bytes)); + ctx.request_repaint(); + }); + })), + download: Some(Box::new(|name: &str, bytes: &[u8]| { + if let Err(e) = download(name, bytes) { + log::error!("download of {name} failed: {e}"); + } + })), + open_url: Some(Box::new(|url: &str| { + if let Some(w) = web_sys::window() { + let _ = w.open_with_url_and_target(url, "_blank"); + } + })), + inbox: Some(inbox), + ..Default::default() + } +} + +/// Triggers a browser download of `bytes`. +fn download(name: &str, bytes: &[u8]) -> Result<(), String> { + let js = |e: wasm_bindgen::JsValue| format!("{e:?}"); + let window = web_sys::window().ok_or("no window")?; + let document = window.document().ok_or("no document")?; + let parts = js_sys::Array::of1(&js_sys::Uint8Array::from(bytes)); + let opts = web_sys::BlobPropertyBag::new(); + opts.set_type(if name.ends_with(".csv") { "text/csv" } else { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); + let blob = web_sys::Blob::new_with_u8_array_sequence_and_options(&parts, &opts).map_err(js)?; + let url = web_sys::Url::create_object_url_with_blob(&blob).map_err(js)?; + let a: web_sys::HtmlAnchorElement = document.create_element("a").map_err(js)?.dyn_into().map_err(|_| "not an anchor")?; + a.set_href(&url); + a.set_download(name); + a.style().set_property("display", "none").map_err(js)?; + let body = document.body().ok_or("no body")?; + body.append_child(&a).map_err(js)?; + a.click(); + a.remove(); + let revoke = wasm_bindgen::closure::Closure::once_into_js(move || { + web_sys::Url::revoke_object_url(&url).ok(); + }); + window.set_timeout_with_callback_and_timeout_and_arguments_0(revoke.unchecked_ref(), 10_000).map_err(js)?; + Ok(()) +} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index a2c9633..5ffa242 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -68,8 +68,15 @@ pub struct Services { pub open_url: Option>, /// Web: deliver a file to the user (name, bytes). pub download: Option>, + /// Web: start an asynchronous file pick; the bytes arrive later in `inbox`. + pub open_async: Option>, + /// Files delivered asynchronously (name, bytes), opened on the next frame. + pub inbox: Option, } +/// Shared queue of files read asynchronously (browser file picker, drag and drop). +pub type Inbox = std::sync::Arc)>>>; + #[derive(Clone, Debug, Default)] pub struct Perf { pub frame_ms: f64, @@ -210,7 +217,9 @@ impl SheetApp { pub fn open_dialog(&mut self, name: &str, params: Json) { match name { "open" => { - if let Some(pick) = &self.services.pick_open + if let Some(start) = &self.services.open_async { + start(); + } else if let Some(pick) = &self.services.pick_open && let Some(path) = pick() { self.open_path(&path); @@ -347,6 +356,15 @@ impl SheetApp { theme::apply(ctx, self.ui.dark); } control::poll(self, ctx); + // Files read asynchronously (web file picker, dropped files). + let arrived: Vec<(String, Vec)> = self.services.inbox.as_ref().map(|i| std::mem::take(&mut *i.lock().unwrap_or_else(std::sync::PoisonError::into_inner))).unwrap_or_default(); + for (name, bytes) in arrived { + let b64 = sheetcraft_engine::io::base64_encode(&bytes); + if let Err(e) = self.session.run("file.open", json!({"name": name, "base64": b64})) { + self.message = Some(("SheetCraft".into(), clean_error(&e))); + } + self.after_engine(); + } if self.session.active().is_none() { self.session.new_workbook(); } diff --git a/docs/parity.md b/docs/parity.md new file mode 100644 index 0000000..3e5f7fe --- /dev/null +++ b/docs/parity.md @@ -0,0 +1,411 @@ +# Excel parity + + + +Each entry is an Excel ribbon or menu command (feature names only). It counts as implemented when SheetCraft registers a command with the same id. + +**Overall: 169 / 281 commands implemented (60.1%).** + +| Tab | Implemented | Total | Parity | +|---|---|---|---| +| File | 8 | 11 | 73% | +| Home | 91 | 94 | 97% | +| Insert | 11 | 25 | 44% | +| Draw | 0 | 4 | 0% | +| Page Layout | 12 | 23 | 52% | +| Formulas | 13 | 23 | 57% | +| Data | 10 | 28 | 36% | +| Review | 7 | 18 | 39% | +| View | 6 | 20 | 30% | +| Automate | 0 | 3 | 0% | +| Table Design | 9 | 13 | 69% | +| Chart Design | 2 | 8 | 25% | +| Format | 0 | 3 | 0% | +| PivotTable Analyze | 0 | 4 | 0% | +| PivotTable Design | 0 | 4 | 0% | + +## File (8 / 11, 73%) + +Implemented: + +- `file.new` New (File) +- `file.open` Open (File) +- `file.save` Save (File) +- `file.saveAs` Save As (File) +- `file.close` Close (File) +- `file.exportCsv` CSV (Export) +- `file.exportHtml` Web Page (Export) +- `file.properties` Properties (Info) + +Missing: + +- `file.print` Print (File) +- `file.exportPdf` Export to PDF (Export) +- `file.options` Options (Options) + +## Home (91 / 94, 97%) + +Implemented: + +- `edit.paste` Paste (Clipboard) +- `edit.pasteSpecial` Paste Special (Clipboard) +- `edit.cut` Cut (Clipboard) +- `edit.copy` Copy (Clipboard) +- `edit.formatPainter` Format Painter (Clipboard) +- `home.fontName` Font (Font) +- `home.fontSize` Font Size (Font) +- `home.increaseFontSize` Increase Font Size (Font) +- `home.decreaseFontSize` Decrease Font Size (Font) +- `home.bold` Bold (Font) +- `home.italic` Italic (Font) +- `home.underline` Underline (Font) +- `home.doubleUnderline` Double Underline (Font) +- `home.strikethrough` Strikethrough (Font) +- `home.superscript` Superscript (Font) +- `home.subscript` Subscript (Font) +- `home.borders` Borders (Font) +- `home.fillColor` Fill Color (Font) +- `home.fontColor` Font Color (Font) +- `home.alignTop` Top Align (Alignment) +- `home.alignMiddle` Middle Align (Alignment) +- `home.alignBottom` Bottom Align (Alignment) +- `home.orientation` Orientation (Alignment) +- `home.alignLeft` Align Left (Alignment) +- `home.alignCenter` Center (Alignment) +- `home.alignRight` Align Right (Alignment) +- `home.decreaseIndent` Decrease Indent (Alignment) +- `home.increaseIndent` Increase Indent (Alignment) +- `home.wrapText` Wrap Text (Alignment) +- `home.mergeCenter` Merge & Center (Alignment) +- `home.mergeAcross` Merge Across (Alignment) +- `home.mergeCells` Merge Cells (Alignment) +- `home.unmergeCells` Unmerge Cells (Alignment) +- `home.numberFormat` Number Format (Number) +- `home.accounting` Accounting Number Format (Number) +- `home.percent` Percent Style (Number) +- `home.comma` Comma Style (Number) +- `home.increaseDecimal` Increase Decimal (Number) +- `home.decreaseDecimal` Decrease Decimal (Number) +- `home.conditionalFormat` Conditional Formatting (Styles) +- `home.clearRules` Clear Rules (Styles) +- `home.manageRules` Manage Rules (Styles) +- `home.formatAsTable` Format as Table (Styles) +- `home.cellStyle` Cell Styles (Styles) +- `home.insertCells` Insert Cells (Cells) +- `home.insertRows` Insert Sheet Rows (Cells) +- `home.insertColumns` Insert Sheet Columns (Cells) +- `home.insertSheet` Insert Sheet (Cells) +- `home.deleteCells` Delete Cells (Cells) +- `home.deleteRows` Delete Sheet Rows (Cells) +- `home.deleteColumns` Delete Sheet Columns (Cells) +- `home.deleteSheet` Delete Sheet (Cells) +- `home.rowHeight` Row Height (Cells) +- `home.autofitRowHeight` AutoFit Row Height (Cells) +- `home.columnWidth` Column Width (Cells) +- `home.autofitColumnWidth` AutoFit Column Width (Cells) +- `home.defaultWidth` Default Width (Cells) +- `home.hideRows` Hide Rows (Cells) +- `home.hideColumns` Hide Columns (Cells) +- `home.unhideRows` Unhide Rows (Cells) +- `home.unhideColumns` Unhide Columns (Cells) +- `sheet.hide` Hide Sheet (Cells) +- `sheet.unhide` Unhide Sheet (Cells) +- `sheet.rename` Rename Sheet (Cells) +- `sheet.move` Move or Copy Sheet (Cells) +- `sheet.tabColor` Tab Color (Cells) +- `review.protectSheet` Protect Sheet (Cells) +- `home.lockCell` Lock Cell (Cells) +- `home.formatCells` Format Cells (Cells) +- `formulas.autoSum` AutoSum (Editing) +- `edit.fillDown` Fill Down (Editing) +- `edit.fillRight` Fill Right (Editing) +- `edit.fillUp` Fill Up (Editing) +- `edit.fillLeft` Fill Left (Editing) +- `edit.fillSeries` Series (Editing) +- `edit.flashFill` Flash Fill (Editing) +- `edit.clearAll` Clear All (Editing) +- `edit.clearFormats` Clear Formats (Editing) +- `edit.clearContents` Clear Contents (Editing) +- `edit.clearComments` Clear Comments (Editing) +- `edit.clearHyperlinks` Clear Hyperlinks (Editing) +- `data.sortAscending` Sort A to Z (Editing) +- `data.sortDescending` Sort Z to A (Editing) +- `data.sort` Custom Sort (Editing) +- `data.filter` Filter (Editing) +- `data.clearFilter` Clear Filter (Editing) +- `data.reapply` Reapply (Editing) +- `edit.find` Find (Editing) +- `edit.replace` Replace (Editing) +- `edit.goTo` Go To (Editing) +- `edit.goToSpecial` Go To Special (Editing) + +Missing: + +- `home.drawBorder` Draw Border (Font) +- `home.newCellStyle` New Cell Style (Styles) +- `home.addIns` Add-ins (Add-ins) + +## Insert (11 / 25, 44%) + +Implemented: + +- `insert.table` Table (Tables) +- `insert.picture` Pictures (Illustrations) +- `insert.shape` Shapes (Illustrations) +- `insert.recommendedCharts` Recommended Charts (Charts) +- `insert.chart` Charts (Charts) +- `insert.sparkline` Sparklines (Sparklines) +- `insert.link` Link (Links) +- `review.newComment` Comment (Comments) +- `insert.textBox` Text Box (Text) +- `pageLayout.headerFooter` Header & Footer (Text) +- `insert.symbol` Symbol (Symbols) + +Missing: + +- `insert.pivotTable` PivotTable (Tables) +- `insert.recommendedPivotTables` Recommended PivotTables (Tables) +- `insert.icons` Icons (Illustrations) +- `insert.smartArt` SmartArt (Illustrations) +- `insert.screenshot` Screenshot (Illustrations) +- `insert.pivotChart` PivotChart (Charts) +- `insert.map` Maps (Charts) +- `insert.slicer` Slicer (Filters) +- `insert.timeline` Timeline (Filters) +- `insert.wordArt` WordArt (Text) +- `insert.signatureLine` Signature Line (Text) +- `insert.object` Object (Text) +- `insert.equation` Equation (Symbols) +- `insert.checkbox` Checkbox (Cell Controls) + +## Draw (0 / 4, 0%) + +Missing: + +- `draw.pen` Pen (Tools) +- `draw.eraser` Eraser (Tools) +- `draw.lasso` Lasso Select (Tools) +- `draw.inkToShape` Ink to Shape (Convert) + +## Page Layout (12 / 23, 52%) + +Implemented: + +- `pageLayout.theme` Themes (Themes) +- `pageLayout.margins` Margins (Page Setup) +- `pageLayout.orientation` Orientation (Page Setup) +- `pageLayout.size` Size (Page Setup) +- `pageLayout.printArea` Print Area (Page Setup) +- `pageLayout.breaks` Breaks (Page Setup) +- `pageLayout.printTitles` Print Titles (Page Setup) +- `pageLayout.scaleToFit` Scale to Fit (Scale to Fit) +- `view.gridlines` View Gridlines (Sheet Options) +- `pageLayout.printGridlines` Print Gridlines (Sheet Options) +- `view.headings` View Headings (Sheet Options) +- `pageLayout.printHeadings` Print Headings (Sheet Options) + +Missing: + +- `pageLayout.themeColors` Colors (Themes) +- `pageLayout.themeFonts` Fonts (Themes) +- `pageLayout.themeEffects` Effects (Themes) +- `pageLayout.background` Background (Page Setup) +- `pageLayout.pageSetup` Page Setup (Page Setup) +- `arrange.bringForward` Bring Forward (Arrange) +- `arrange.sendBackward` Send Backward (Arrange) +- `arrange.selectionPane` Selection Pane (Arrange) +- `arrange.align` Align (Arrange) +- `arrange.group` Group (Arrange) +- `arrange.rotate` Rotate (Arrange) + +## Formulas (13 / 23, 57%) + +Implemented: + +- `formulas.insertFunction` Insert Function (Function Library) +- `formulas.nameManager` Name Manager (Defined Names) +- `formulas.defineName` Define Name (Defined Names) +- `formulas.createFromSelection` Create from Selection (Defined Names) +- `formulas.tracePrecedents` Trace Precedents (Formula Auditing) +- `formulas.traceDependents` Trace Dependents (Formula Auditing) +- `formulas.removeArrows` Remove Arrows (Formula Auditing) +- `formulas.showFormulas` Show Formulas (Formula Auditing) +- `formulas.errorChecking` Error Checking (Formula Auditing) +- `formulas.evaluateFormula` Evaluate Formula (Formula Auditing) +- `formulas.calculationOptions` Calculation Options (Calculation) +- `formulas.calculateNow` Calculate Now (Calculation) +- `formulas.calculateSheet` Calculate Sheet (Calculation) + +Missing: + +- `formulas.recentlyUsed` Recently Used (Function Library) +- `formulas.financial` Financial (Function Library) +- `formulas.logical` Logical (Function Library) +- `formulas.text` Text (Function Library) +- `formulas.dateTime` Date & Time (Function Library) +- `formulas.lookupReference` Lookup & Reference (Function Library) +- `formulas.mathTrig` Math & Trig (Function Library) +- `formulas.moreFunctions` More Functions (Function Library) +- `formulas.useInFormula` Use in Formula (Defined Names) +- `formulas.watchWindow` Watch Window (Formula Auditing) + +## Data (10 / 28, 36%) + +Implemented: + +- `data.textToColumns` Text to Columns (Data Tools) +- `data.flashFill` Flash Fill (Data Tools) +- `data.removeDuplicates` Remove Duplicates (Data Tools) +- `data.validation` Data Validation (Data Tools) +- `data.circleInvalid` Circle Invalid Data (Data Tools) +- `data.group` Group (Outline) +- `data.ungroup` Ungroup (Outline) +- `data.subtotal` Subtotal (Outline) +- `data.showDetail` Show Detail (Outline) +- `data.hideDetail` Hide Detail (Outline) + +Missing: + +- `data.getData` Get Data (Get & Transform Data) +- `data.fromTextCsv` From Text/CSV (Get & Transform Data) +- `data.fromWeb` From Web (Get & Transform Data) +- `data.fromTableRange` From Table/Range (Get & Transform Data) +- `data.refreshAll` Refresh All (Queries & Connections) +- `data.queriesConnections` Queries & Connections (Queries & Connections) +- `data.stocks` Stocks (Data Types) +- `data.geography` Geography (Data Types) +- `data.advancedFilter` Advanced (Sort & Filter) +- `data.consolidate` Consolidate (Data Tools) +- `data.relationships` Relationships (Data Tools) +- `data.scenarioManager` Scenario Manager (Forecast) +- `data.goalSeek` Goal Seek (Forecast) +- `data.dataTable` Data Table (Forecast) +- `data.forecastSheet` Forecast Sheet (Forecast) +- `data.analyzeData` Analyze Data (Analysis) +- `data.solver` Solver (Analysis) +- `data.analysisToolPak` Data Analysis (Analysis) + +## Review (7 / 18, 39%) + +Implemented: + +- `review.workbookStatistics` Workbook Statistics (Proofing) +- `review.checkAccessibility` Check Accessibility (Accessibility) +- `review.deleteComment` Delete Comment (Comments) +- `review.newNote` New Note (Notes) +- `review.showNote` Show/Hide Note (Notes) +- `review.unprotectSheet` Unprotect Sheet (Protect) +- `review.protectWorkbook` Protect Workbook (Protect) + +Missing: + +- `review.spelling` Spelling (Proofing) +- `review.thesaurus` Thesaurus (Proofing) +- `review.smartLookup` Smart Lookup (Insights) +- `review.translate` Translate (Language) +- `review.previousComment` Previous Comment (Comments) +- `review.nextComment` Next Comment (Comments) +- `review.showComments` Show Comments (Comments) +- `review.showAllNotes` Show All Notes (Notes) +- `review.convertToComments` Convert to Comments (Notes) +- `review.allowEditRanges` Allow Edit Ranges (Protect) +- `review.showChanges` Show Changes (Changes) + +## View (6 / 20, 30%) + +Implemented: + +- `view.zoom` Zoom (Zoom) +- `view.zoomToSelection` Zoom to Selection (Zoom) +- `view.freezePanes` Freeze Panes (Window) +- `view.freezeTopRow` Freeze Top Row (Window) +- `view.freezeFirstColumn` Freeze First Column (Window) +- `view.unfreezePanes` Unfreeze Panes (Window) + +Missing: + +- `view.normal` Normal (Workbook Views) +- `view.pageBreakPreview` Page Break Preview (Workbook Views) +- `view.pageLayout` Page Layout (Workbook Views) +- `view.customViews` Custom Views (Workbook Views) +- `view.ruler` Ruler (Show) +- `view.formulaBar` Formula Bar (Show) +- `view.zoom100` 100% (Zoom) +- `view.newWindow` New Window (Window) +- `view.arrangeAll` Arrange All (Window) +- `view.split` Split (Window) +- `view.hideWindow` Hide (Window) +- `view.unhideWindow` Unhide (Window) +- `view.switchWindows` Switch Windows (Window) +- `view.macros` Macros (Macros) + +## Automate (0 / 3, 0%) + +Missing: + +- `automate.newScript` New Script (Scripting Tools) +- `automate.allScripts` All Scripts (Scripting Tools) +- `automate.recordActions` Record Actions (Scripting Tools) + +## Table Design (9 / 13, 69%) + +Implemented: + +- `table.rename` Table Name (Properties) +- `table.convertToRange` Convert to Range (Tools) +- `table.headerRow` Header Row (Table Style Options) +- `table.totalRow` Total Row (Table Style Options) +- `table.bandedRows` Banded Rows (Table Style Options) +- `table.firstColumn` First Column (Table Style Options) +- `table.lastColumn` Last Column (Table Style Options) +- `table.bandedColumns` Banded Columns (Table Style Options) +- `table.style` Table Styles (Table Styles) + +Missing: + +- `table.resize` Resize Table (Properties) +- `table.summarizePivot` Summarize with PivotTable (Tools) +- `table.removeDuplicates` Remove Duplicates (Tools) +- `table.insertSlicer` Insert Slicer (Tools) + +## Chart Design (2 / 8, 25%) + +Implemented: + +- `chart.set` Chart Styles (Chart Styles) +- `chart.switchRowColumn` Switch Row/Column (Data) + +Missing: + +- `chart.addElement` Add Chart Element (Chart Layouts) +- `chart.quickLayout` Quick Layout (Chart Layouts) +- `chart.changeColors` Change Colors (Chart Styles) +- `chart.selectData` Select Data (Data) +- `chart.changeType` Change Chart Type (Type) +- `chart.move` Move Chart (Location) + +## Format (0 / 3, 0%) + +Missing: + +- `chart.formatSelection` Format Selection (Current Selection) +- `chart.shapeFill` Shape Fill (Shape Styles) +- `chart.shapeOutline` Shape Outline (Shape Styles) + +## PivotTable Analyze (0 / 4, 0%) + +Missing: + +- `pivot.fieldList` Field List (Show) +- `pivot.refresh` Refresh (Data) +- `pivot.changeSource` Change Data Source (Data) +- `pivot.calculatedField` Fields, Items & Sets (Calculations) + +## PivotTable Design (0 / 4, 0%) + +Missing: + +- `pivot.grandTotals` Grand Totals (Layout) +- `pivot.subtotals` Subtotals (Layout) +- `pivot.reportLayout` Report Layout (Layout) +- `pivot.style` PivotTable Styles (PivotTable Styles)