A fast terminal (TUI) viewer and editor for Microsoft Word .docx and Markdown — right where you live, in the terminal.
Docxy opens real .docx files — text, tables, lists, styles, even images —
renders them faithfully in a character grid, and lets you edit and save them
losslessly. It reads and writes Markdown too, converts between the two, and
exports to PDF. No Office, no browser, no network: it's a single static
binary on top of a small, dependency-free OOXML engine.
Docxy deliberately doesn't reproduce Word's pixel-perfect layout — it renders a faithful, readable view of the document in a character grid, with a familiar ribbon, mouse support, and an optional Vim mode.
- Stay in the terminal. Read and edit Word documents over SSH, in tmux, or from your editor's shell — no GUI required.
- Lossless by design. Anything docxy doesn't model (bookmarks, fields, content controls, section properties) is preserved byte-for-faithful on save.
- Zero-dependency core. The
docxcorecrate is purestd— its own ZIP/DEFLATE, XML parser, renderer, and PDF writer — so it's auditable and trivially embeddable. - One file does it all.
.docx⇄.mdconversion and.docx → .pdfexport are built in, scriptable, and headless.
cargo install docxy # or grab a prebuilt binary (see Install)
docxy report.docx # open a Word document
docxy notes.md # open / edit Markdown
docxy # launch the welcome screen (new file or open)Want to see it immediately? Generate the showcase document and open it:
cargo run -p docxcore --example gen_sample # writes assets/sample.docx
docxy assets/sample.docx- View & edit paragraphs and runs (bold / italic / underline / strike / color / highlight / sub- & superscript) and tables, including merged cells — navigate and type directly into cells.
- Styles resolved from
styles.xml; lists numbered fromnumbering.xml; headings, indents, alignment, tab stops, and horizontal rules. - Bidirectional DOCX layout in the terminal editor for Hebrew, Arabic, and
mixed LTR/RTL text: paragraph
w:bidi, runw:rtl, style-inherited direction, and Unicode bidi controls render in visual order while editing, copy/export, and save offsets remain logical. - Lossless save — unmodeled parts are preserved exactly.
- Find & replace, full clipboard (syncs with the OS clipboard), selection + formatting, word navigation, and show-invisibles.
- Headers & footers, multi-section page layout, and print/page view.
- Tracked-change review for insertions, deletions, and run/paragraph/table/ row/cell/section property changes. Review previous/next, accept or reject the current change, or confirm accept/reject all; each action is undoable and survives save/reload. Unsupported move/custom records stay lossless.
- DOCX protection honored across TUI and automation edits: read-only, comments-only, and formatting-only restrictions are enforced; unsupported forms-only and tracked-changes-only editing fails closed. Recommendation-only write protection remains editable with a warning; password-backed write protection fails closed as read-only until password verification is supported.
- Watermark previews in page view: applied text watermarks render as non-interactive overlays, while picture or unsupported watermarks show a preview-unavailable label. Overlay text never enters copy/export output or saved OOXML.
- Open and edit
.mdfiles directly; Save As to a.mdor.docxname converts between the two. - View ▸ Markdown toggles a
.mdfile between the rendered document and its raw source. - Headings, bold/italic/strike, inline
code, fenced code blocks, blockquotes, links, bullet/ordered lists, thematic rules, and pipe tables all map across — and round-trip through.docxvia real Word styles. - Scientific formulas:
$…$inline and$$…$$display math (LaTeX) convert to and from native Word equations (OMML) — fractions, roots,\sum/\intwith limits, Greek, scripts,\left…\right, and named functions. - Mermaid diagrams: a
```mermaidblock becomes a native Word drawing (DrawingML shapes + connectors, laid out automatically); the Mermaid source is embedded so Word → Markdown restores the exact block. Flowcharts are laid out fully; other diagram types are best-effort.
- Raster (PNG / JPEG / GIF / BMP / TIFF) rendered as real pixels via kitty / iTerm2 / Sixel graphics.
- Legacy WMF/EMF vector images rasterized through the OS (Windows).
- Floating, frame-anchored images projected to their real page positions.
- Welcome screen on launch with no file: create a
.docx/.mdor open one — keyboard- and mouse-driven. - Mouse everywhere: click to move, click a link to open, wheel/drag to scroll/select, and a fully clickable ribbon and File menu.
- Safe clickable links — only
http(s), shown for confirmation, opened without a shell. - Vim mode (
--vim): motions, operators, visual mode,/search,:w/:q. - PDF export, including headless.
docxy <file.docx|.md> # open a Word or Markdown file
docxy # welcome screen (new .docx/.md, or open)
docxy <file> --vim # open with Vim keybindings
# Headless conversion / export (no UI):
docxy in.docx --pdf out.pdf # export to PDF
docxy in.docx --md out.md # convert Word → Markdown
docxy in.md --docx out.docx # convert Markdown → Word
docxy in.docx --html in.docx.html # editable HTML (see below)docxy sample.docx --html sample.docx.html writes one self-contained HTML
file that opens offline in any browser as a docxy editor, with the desktop
suite's title bar, ribbon, Backstage and page view, in light or dark. It holds
the original .docx untouched, plus the docxwasm engine and the page. A
Content-Security-Policy of default-src 'none' means the page makes no network
requests at all.
- In the browser, Save writes the page back with your edits. Chromium saves
in place; other browsers download
sample.docx.html. Download sample.docx under File › Save As gives the Word file. - In docxy (terminal or desktop suite),
sample.docx.htmlopens like a.docx. Save keeps it editable HTML, and Save As.docxwrites plain Word.docxy sample.docx.html --docx out.docxreturns the embedded Word file byte for byte when nobody edited it. - If
sample.docxnext to the page has changed since the export, docxy says so. It never overwrites that file.
Making new pages embeds the engine, which the html-export feature compiles in.
Release binaries have it. From source, run
cargo build -p docxy --features html-export, which needs
rustup target add wasm32-unknown-unknown. Opening and re-saving an existing
page works in any build. Tables, headers and footers, and comments are edited
in docxy for now: the browser shows those ribbon commands dimmed.
| Keys | Action |
|---|---|
| type · Enter · Backspace · Delete | edit text |
| arrows · Home/End · PgUp/PgDn | move (Ctrl-←/→ by word) |
| Shift + move | select (Esc clears) |
| Ctrl-B / Ctrl-I / Ctrl-U | bold / italic / underline (over selection) |
| Ctrl-L / Ctrl-E / Ctrl-R | align left / center / right |
| Ctrl-A · Ctrl-C · Ctrl-X · Ctrl-V | select all · copy · cut · paste |
| Ctrl-F | find / replace (Tab toggles replace, Ctrl-A replaces all) |
| Ctrl-S · Ctrl-Z · Ctrl-Y | save · undo · redo |
| Ctrl-Q / Esc | quit |
| F2 · F3 · F4 | page view · show marks · table borders |
| F6 · F7 | edit header · edit footer (Esc returns) |
| F8 · F9 | insert landscape · portrait section at cursor |
| Alt-Shift-←/→ | previous / next tracked change |
| Alt-Shift-A/R | accept / reject current tracked change |
| mouse | click to move · click a link to open · wheel/drag to scroll/select |
The workspace now ships a sibling app: xlsxy, a terminal editor for
Microsoft Excel .xlsx workbooks built on gridcore, a dependency-free
SpreadsheetML engine with a real recalculation engine — a dependency
graph over your formulas, ~170 Excel functions, Excel-faithful semantics
(error values, coercions, the 1900 leap-year quirk), whole-column
references, defined names, structured table references, 3D sheet spans,
INDIRECT/OFFSET, XLOOKUP and the *IFS family, the full
number-format runtime, dynamic arrays (FILTER/SORT/UNIQUE/
SEQUENCE spill into neighboring cells, A1# spill references, @,
LET, #SPILL! blocking and recovery), LAMBDA (custom functions via
defined names, MAP/REDUCE/SCAN/BYROW/BYCOL/MAKEARRAY,
elementwise lifting like ABS(A1:A3)), pivot-table refresh and editing (a
columnar group-by/aggregate engine recomputes pivots from current data —
F9 in the TUI, automatic under --recalc; Ctrl-P edits a pivot's
fields — or creates a new pivot from the selected data range), a data model
(gridcore::model: multiple tables with relationships, Excel-formula
measures plus DAX-style row-context iterators like
SUMX(Sales,[@Qty]*[@Price]), filter context through star schemas, CSV
sources — xlsxy data.csv imports directly; Ctrl-M manages the model in
the TUI and materializes reports, with definitions persisted in the file), and the
same lossless
round-trip guarantee: anything it doesn't model (pivots, conditional
formatting…) is preserved byte-for-byte. The one deliberate exception is a chart
you edit — repoint its range, switch which way round it reads that range
(Excel's Switch Row/Column), rename or reorder its series, recolour it, in the
desktop suite's Chart panel — which is regenerated from the model, and a drawing
you move or delete, whose anchor is rewritten in place. Only column, bar, line
and pie charts can be regenerated; a scatter, a doughnut or anything stacked or
combined is kept verbatim and can't be repointed. Charts and drawings you leave
alone still round-trip verbatim.
Inserting a chart from a selection writes a live chart: each series is bound
to its own cells, so Excel updates it when the data changes. Formulas it can't
evaluate yet keep Excel's cached results and are saved untouched.
xlsxy book.xlsx # open a workbook (grid, formula bar, tabs)
xlsxy in.xlsx --recalc out.xlsx # headless: recalculate everything, save
xlsxy in.xlsx --csv out.csv # headless: export the first sheet as CSV
xlsxy corpus/xlsx/*.xlsx --verify # conformance scoreboard: recalc + diff
# against cached values (461/461 = 100%
# on the LibreOffice-oracle corpus)Type to replace, F2 to edit, = starts a formula; copy/paste and
fill-down translate relative references like Excel; insert/delete rows and
columns rewrites every affected formula workbook-wide — and every chart
reference with them; find, Save As, and
sheet add/rename/delete round out the basics; range selections show
Sum/Average/Count in the status bar. Try it: cargo run -p gridcore --example gen_sample_xlsx && xlsxy assets/sample.xlsx. The design and roadmap (conformance scoreboard,
dynamic arrays, pivot engine) live in SPREADSHEET.md.
Completing the trilogy (doc→docx, xls→xlsx, mpp→yppx), the workspace
ships yppxy, a terminal editor for project schedules built on
projcore, a dependency-free scheduling engine. It reads Microsoft Project's
open MSPDI XML (what Project produces via Save As → XML), schedules it with
a real Critical Path Method engine — forward/backward passes over
working-time calendars, computing early/late start & finish, total/free slack,
and the critical path — and saves to a native .yppx package (an OPC
ZIP container, the project analog of .docx/.xlsx). The TUI is a task outline
beside a live terminal Gantt chart that reschedules on every edit; critical
tasks show in amber, summaries roll up over their children, milestones as
diamonds. Dependencies (FS/SS/FF/SF with lag/lead) and hard constraints
(SNET/FNET/MSO/MFO…) are modeled; a schedule exports to a Markdown/Mermaid
Gantt block that renders anywhere docxy's diagrams do.
yppxy plan.xml # open MSPDI XML (or a .yppx package)
yppxy plan.yppx --gantt-md out.md # headless: export a Markdown Gantt chart
yppxy plan.xml --save out.yppx # headless: convert to the native packageLike docxy and xlsxy, yppxy has the same ribbon — with Microsoft Project's
tabs, groups and command names (File · Task · Resource · Report · Project ·
View, e.g. Project › Schedule › Set Baseline; F9 to engage) — the same File
backstage (Alt-F: New / Open / Info / Save / Save As / Export / Exit with a
folder browser and live preview), a start screen, a light/dark theme toggle
(the ◐ Theme button at the right of the tab strip, or T), and mouse support. Try it:
yppxy corpus/mspdi/10-summary.xml.
| Keys | Action |
|---|---|
| ↑ ↓ · j / k · g / G | move the selection (top / bottom) |
| ← → · h / l | scroll the Gantt timeline |
| n · Insert | add a task below (1 day? unless the plan's NewTasksEstimated is off; inserted into a finish-to-start chain, it is linked in when the plan's Autolink is on, the default) |
| N | insert a blank row above (typing into it makes a task) |
| x · Delete | delete the task (a summary asks first, then takes its subtasks) |
| Tab · Shift-Tab | indent / outdent (auto-forms summary tasks) |
| Enter · F2 | rename the task |
| d | set duration (3d / 4h / 2w) |
| p | add a predecessor by task ID |
| c | set a date constraint (SNET 2026-03-05, MSO …, none) |
| a | assign a resource to the task (created on first use; Name[25%] sets units, Cement[5 tons] a material quantity; empty clears) |
| b | set the baseline (planned-vs-current variance in the header) |
| L | toggle resource leveling (delay bars to fit resource capacity) |
| m | switch the task between Manually Scheduled (📌, pinned at its current dates) and Auto Scheduled |
| M | switch the plan's mode for new tasks (the status line's New Tasks: …; click it too) |
| T | toggle the light / dark theme |
| Ctrl-F · F3 | find task by name · repeat |
| Ctrl-Z · Ctrl-Y | undo · redo |
| F9 · Alt-F | engage the ribbon · open the File menu |
| Ctrl-S · Ctrl-E | save · export a Markdown Gantt |
| Ctrl-Q · q | quit (q warns on unsaved changes) |
| mouse | click a tab/button, click a task row, wheel to scroll/pan |
Launch with --vim for a modal mode (:w/:q/:wq/:q!, u undo, /
search). The light/dark theme persists between sessions.
A separate crate, mppread, reads the OLE2 Compound File container of
legacy binary .mpp files. It decodes OLE property-set metadata and validates
the counted task tables in supported MPP9 and current Project files before
importing task names, dates, outline levels, and predecessor links. Unsupported
layouts return an import error. Nonzero link lag remains unvalidated against a
real-file oracle.
The design, the CPM engine, resource leveling, and the format landscape are written up in PROJECT.md.
The workspace also ships lookxy, a terminal client for Outlook /
Exchange Online mail built on mailcore, a headless engine over Microsoft
Graph: OAuth2 authorization-code + PKCE sign-in (a system-browser
round trip via a loopback redirect — no password ever touches lookxy
itself), a SQLite local store with FTS5 full-text search, and a
background sync thread that keeps folders and messages current (per-folder
delta sync, an outbox of pending mutations with retry/back-off, and
automatic token refresh) — all fully testable against an in-process fake
Graph server, no network or secrets required. The TUI is a three-pane
Outlook-like layout — folders | message list | reading pane — with triage
keys (mark read/unread, flag, delete, move), attachment save/open, and
full-text search, all optimistic-local-write-then-sync so the UI never
blocks on the network.
lookxy # sign in once (opens your browser), then read and triage mailclient_id/backfill_days/refresh_secs are configurable via
%APPDATA%\lookxy\config.json or LOOKXY_* environment variables. Sign-in,
keybindings, storage locations, configuration, and the security/privacy
model (DPAPI-encrypted token cache, plaintext local mail store like
Outlook's own OST) are written up in LOOKXY.md.
Because docxcore and gridcore are pure std with no third-party crates,
they compile straight to WebAssembly — so both engines (parse → render →
edit → lossless save) run inside a VS Code editor tab. The
offxy-vscode extension opens a .docx or .xlsx on the same
faithful character-grid rendering the terminal apps use, at the editor's own
font and size and honoring your color theme — no ribbon, just the keyboard and
command palette, like editing code. Each format is a binary custom editor
(offxy.docxEditor / offxy.gridEditor) with native dirty state, undo/redo,
Save/Save As, and hot-exit backups, and because it edits the real OOXML model
(not HTML or an intermediate form), it keeps the document/workbook's structure
intact on save — the lossless round-trip that the crowded field of HTML-based
.docx/.xlsx extensions lacks. The Excel editor adds a virtualized grid,
formula bar, and full gridcore recalculation on edit.
The engines are two small .wasm builds — docxwasm and
gridwasm, each a hand-written C-ABI bridge, no wasm-bindgen.
The architecture — the wasm ABIs, the host ↔ webview split, and how VS Code's
edit events stay in lockstep with each engine's own undo stack — is written up
in VSCODE.md.
Bidi note: terminal docxy injects Unicode bidi projection for DOCX lines. The
wasm-backed Offxy editors currently use docxcore's identity render path, so
Hebrew, Arabic, and mixed-direction DOCX text remains logical-order there until
a wasm projector is added.
The offxy-jetbrains plugin brings the Word and Excel
editors to IntelliJ-platform IDEs (2024.2+) with no webview at all: the
same docxwasm.wasm/gridwasm.wasm artifacts run on the JVM via
Chicory (a pure-Java wasm runtime — no native code,
no per-platform builds). A .docx renders in a real IntelliJ editor
over a live, editable Document — typing is native-editor fast on any
document size, the engine follows and reconciles asynchronously, structure
is guarded, platform find/undo/save just work. An .xlsx opens in a
virtualized native grid over gridwasm's windowed viewport protocol —
formulas with live recalculation, formatting, structural edits, sheets, TSV
clipboard, one-transaction-one-undo. Every open tab advertises on the same
agent control surface the terminal apps use, so
Claude Code and Junie can read and edit it live. See
offxy-jetbrains/README.md.
Bidi note: the JetBrains plugin uses the same wasm DOCX bridge as Offxy for VS Code, so DOCX bidi projection is not yet applied in IDE editor tabs.
cargo install docxy # the document editor
cargo install xlsxy # the spreadsheet editor
cargo install yppxy # the project scheduler
cargo install lookxy # the mail clientOr grab prebuilt binaries (Linux / Windows / macOS) from the latest release — both apps ship with every release, each checksummed, cosign-signed, and carrying a build-provenance attestation.
Real image pixels need a graphics-capable terminal:
- WezTerm (kitty + Sixel + iTerm2) — best.
- Windows Terminal ≥ 1.22 (Sixel).
- Most other terminals fall back to a labeled placeholder box.
WMF/EMF vector images are rasterized via the OS GDI on Windows; on other platforms they show as boxes.
git clone https://github.com/yeroo/docxy
cd docxy
cargo build --release
cargo testThe workspace has twenty-one crates; these are the ones a reader of this
README will meet (CONTRIBUTING.md lists the rest):
opccore— pure,std-only OPC container plumbing (ZIP read/write, DEFLATE, XML pull parser) shared by every engine.docxcore— the WordprocessingML engine (document model, rendering, and the from-scratch PDF writer). No third-party dependencies. Embedders constructingdocxcore::render::RenderOptionsdirectly should setbidi: Nonefor identity visual order or provide anRc<dyn BidiProjector>when the host supplies Unicode bidi projection.gridcore— the SpreadsheetML engine (workbook model, formula parser/evaluator, dependency-graph recalculation, lossless xlsx I/O).projcore— the project-scheduling engine (task/calendar model, MSPDI read/write, Critical Path Method scheduler, Markdown/Mermaid Gantt export, native.yppxOPC package).std-only, on top ofopccore.mppread—std-only reader for the OLE2 Compound File container of legacy binary.mpp/.doc/.xlsfiles (MS-CFB).htmlbundle—std-only packer for editable HTML (*.docx.html): wraps a package, the wasm engine and the web UI (htmlbundle/web/) into one file and unwraps it again. Browser tests live inwebapp/(npm ci && npm test, Playwright).docxy— the document TUI (ratatui), Unicode bidi projection (unicode-bidi), clipboard (arboard), and image rendering (ratatui-image).xlsxy— the spreadsheet TUI (ratatui + arboard).yppxy— the project-scheduler TUI with a live terminal Gantt chart (ratatui).mailcore— the headless mail engine: OAuth2 auth-code+PKCE, a Microsoft Graph REST client, SQLite storage with FTS5 full-text search, and a background sync thread (no UI dependency;ureq/rustls for HTTP, bundledrusqlitefor storage, DPAPI on Windows for the token cache).lookxy— the mail TUI (ratatui) overmailcore: folder tree, message list, reading pane, and triage keys.
cargo run -p docxcore --example gen_sample [out.docx] # build the showcase doc
cargo run -p docxcore --example dump_doc -- assets/sample.docx # inspect a .docx
cargo run -p gridcore --example gen_sample_xlsx # build the showcase workbook
cargo run -p projcore --example gantt_md -- corpus/mspdi/10-summary.xml # Gantt → Markdown
cargo run -p projcore --example convert -- in.xml out.yppx # MSPDI ⇄ .yppx
cargo run -p mppread --example streams -- some.mpp # list a .mpp's streams
cargo run -p mppread --example tasknames -- some.mpp # decode a .mpp's tasks + datesMIT © yeroo
