Skip to content

Edit and save MAT-files - #88

Merged
ww-mw merged 7 commits into
mainfrom
vscode-mat-editing
Oct 21, 2026
Merged

ww-mw merged 7 commits into
mainfrom
vscode-mat-editing

Conversation

@ww-mw

@ww-mw ww-mw commented Oct 21, 2026

Copy link
Copy Markdown
Member

.mat files now open in an editable view and save, through data-explorer-core 1.44's MAT-file save. .slx, .mdl and .prj stay read-only.

What a user can do

  • Edit values, matrix elements, struct fields and cell members.
  • Rename, add and delete variables.
  • Edit a Simulink.Parameter's or Simulink.Signal's allow-listed properties.

All of these come with undo and redo, including undo past a save. Only edits core can write are offered. A file core cannot save opens read-only, with the reason in a banner.

How the save keeps the file

  • Untouched variables are not rewritten. Core writes every variable not edited as its own bytes, and checks the result against MATLAB's load rules before returning it. If core refuses the save, nothing is written, the document stays dirty, and core's message, naming the file, is shown.
  • The write is safe. It goes to a temp file beside the original, is flushed, and is renamed over the original, keeping its mode and owner.
    • In place, when a rename can't be used: for a hard-linked file, a folder that takes no temp file, an owner that can't be kept, or a rename the system refuses (OneDrive and antivirus on Windows, bind mounts, some network drives), the bytes are written in place. They are written over the old ones and then cut to length, so the file is never emptied first.
    • Symlinks: a symlink is written through and stays a link.
    • Other file systems: anything that is not a local file is written by VS Code's own write.
    • What a rename can't keep: extended attributes and ACLs, which is documented.
  • Outside changes are handled as in the binary dictionary editor. It reloads when clean and asks before overwriting when dirty, and it honours files.saveConflictResolution. That logic now lives in diskFollow.ts, shared by both editors with no behaviour change. A crash-recovery backup remembers the file version its edits were made over, so a restore asks before overwriting a file changed since.

Tests

  • 24 integration tests in matEdit.test.ts cover:
    • edits, saves and reopening, with untouched variables byte-identical;
    • undo and redo across a save;
    • rename, add and delete;
    • object properties, allowed and refused;
    • a save with no edit leaving the file byte-identical;
    • a refused save;
    • an outside change while dirty;
    • a gesture that fails partway, after which Undo lands on the right edit.
  • 22 unit tests for the safe write.
  • npm run verify passes (3598 tests), and npm run test:integration passes (311 passing, 1 pending, 0 failing).
  • A MATLAB R2027a read-back of files saved through the editor passed 26 of 26. It covered value edits of every class, renames, deletes, adds, a Parameter Value, a -v6 file and undo past a save.

Two reviews found no blocker. Their findings, mostly about the safe write and edge cases of the document's lifecycle, are fixed in the last four commits.

ww-mw added 7 commits October 21, 2026 14:58
The watcher and its settle, the reload of a clean document, the
save-time conflict check and the Overwrite / Revert prompt were private
methods of BinarySlddEditorProvider. They are now diskFollow.ts, over a
FollowedDocument interface the binary dictionary document implements,
so a second writable byte-backed editor can answer an outside write and
a save over one by the same rules. What each format reads off the disk
and how it replaces its document stay with its provider, handed in as
read and apply. No behaviour changes.
A .mat now opens in its own writable editor (dataExplorer.matView, the
default for *.mat) instead of the read-only view, which hands on a .mat
that lands in it. Values, renames, Add (a variable named var, holding
0, with its name editor open), Add Child and Delete all go through
core's session, whose gate offers no edit a save would refuse or write
as something else; a refused edit records nothing and leaves the file
clean. Undo and redo are core's: each gesture pushes one VS Code edit
that replays the steps core recorded for it.

A save is core's serializeSource: every variable nobody edited is the
bytes it was read from, -v6 stays uncompressed, and a save core refuses
writes nothing, keeps the edits and says why, naming the file. The
bytes replace the file by a temporary file beside it, flushed and
renamed over it, keeping its mode, following a symbolic
link, writing in place where the file has another hard link, and
refusing a read-only file; any file system but the local disk is
written as VS Code writes it.

A save keeps the tree, so an edit can still be undone after the save
that wrote it, auto-save included. The Status column says what differs
from the file as last saved, kept beside VS Code's stack. Revert, hot-
exit backup and restore, and an outside change follow the binary
dictionary editor's rules (diskFollow.ts). A file core cannot save
opens read-only with the reason in the banner; a -v7.3 file shows why
it cannot be opened.

The table's setRows gains add: 'variable' and clipboard: false: the Add
button and its key add a variable at once, and Copy, Cut, Paste and row
drags are not offered, since a variable has no dictionary entry form.
MAT rows carry the menu's capability flags.

The integration suite opens copies of three MATLAB-written files from
core's test corpus (test-integration/fixtures/mat) and compares each
saved file with the original record by record.
…e a replace cannot be made

A save's fallbacks wrote with fs.writeFile, which truncates first, so a
full disk or a crash during a hard-linked file's save left it short.
The write in place now opens the file for update, writes the new bytes
over the old from the start, and only then cuts it to their length.

It is also the fallback wherever a replace by rename cannot be made and
VS Code's own save would still succeed: a hard link, a directory that
takes no new file, a rename the file system refuses (EPERM, EBUSY,
EXDEV, EACCES, ENOTSUP: OneDrive or a scanner on Windows, a bind-mounted
file, some network and FUSE mounts), and a temporary file that cannot be
given the original's mode, owner or group. A refused chmod no longer
fails the save, and a replace never changes who may read or write the
file. A link to nothing is written through, and stays a link.

The write answers the stamp the written file has, read off it before it
takes the name, so an outside write landing just after a save is not
taken for the save. A temporary file a crash leaves behind is removed by
the next save of that file; its name is random from crypto. The header
says what a replace drops (extended attributes, ACLs, Finder tags) and
what a write through another file system cannot promise.
…ackup against its own stamp

- A gesture that throws after core recorded some of its steps is still
  one edit VS Code knows of, so the next Undo undoes it and not the
  edit before it. The table is repainted whole, then the error shown.
- A hot-exit backup's id records the stamp of the file the edits were
  made over; a restore's first save asks before writing over a file
  another program changed since, instead of taking its current stamp.
- A revert reads the disk before it marks the document clean, and one
  that cannot read it puts the tree back to the bytes the file held as
  last read or saved, so a clean document never holds dropped edits.
- A backup core refuses keeps the last backup that could be written
  standing (each handle holds a reference, so the extension host's
  delete of the old handle does not take the file), and says so once.
  Measured: a backup of a 91 MB file with one edit serializes in 3 ms
  and writes in 15 ms; of a 100,000-variable file, 173 ms and 2 ms.
- A save's stamp is read off the written file. Save As onto the file
  itself, or onto another link to it, is a save.
- Every message shown of core's names the file, not the srcId, the
  parse-warning banner included.
- After a revert or a reload, the Property Inspector shows its node
  again from the new tree, or clears.
- Said in the code: a stale undo leaves the document dirty while it
  equals the disk, and a MAT-file opens in one panel, as a binary
  dictionary does.
…when its menu check throws

With `clipboard: false` (a MAT-file) rows are no longer draggable, and a
drag from another table draws no drop target over them. The row
builder's menu flags are computed defensively: a check that throws
offers nothing and the row still shows, where it used to fail the whole
payload. Rows are copied once instead of twice; the flags are stamped on
every MAT row, a read-only file's too, which the code now says.

Timed against a 19 MB file of eleven 183,000-cell variables and a file
of 100,000 variables (best of 5): expanding one cell takes 514 ms
without the flags, 567 ms with them; the 100,000-row payload 285 ms
without, 309 ms with. Copying once instead of twice is not measurable.

The read-only view's dead .mat branches (its Usage filler, two
comments) are gone: a .mat is handed on before they run.
The headers of diskConflict.ts and diskFollow.ts, and one comment in
the binary dictionary editor, named a ticket and its ruling; they now
say the behaviour. diskConflict.ts also names both writable byte-backed
views, and diskFollow.ts as its vscode half.
@ww-mw
ww-mw merged commit f4ab280 into main Oct 21, 2026
4 checks passed
@ww-mw
ww-mw deleted the vscode-mat-editing branch October 21, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant