Skip to content

Compare two or three files side by side - #489

Closed
cerede2000 wants to merge 2 commits into
nxzai:mainfrom
cerede2000:p3-60
Closed

cerede2000 wants to merge 2 commits into
nxzai:mainfrom
cerede2000:p3-60

Conversation

@cerede2000

@cerede2000 cerede2000 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

This sits on top of the tabbed browsing branch (#488), so its diff against main
shrinks to its own once that one lands.

Closes #487

Two or three files compared side by side, as WinMerge does. Choose two or three text
files in a listing, right-click, and Compare. They open at an address of their
own
— so a comparison lives in a tab, can be linked to, and is somewhere you leave and
come back to rather than a dialog you have to finish. Nothing about it is modal.

image

What a reader gets

The alignment. Every line of every file, aligned: two versions of a line on one row,
a gap where a side has nothing. Within a changed pair the part that actually differs is
marked, which is what makes one character different in a long path readable.

Walking them. The differences are counted and numbered; F8 and F7 walk them,
alt with the arrows does the same for a keyboard without those keys, and they come
round again at the end — somebody on the last difference pressing "next" means "start
again", and a button that does nothing is a button they press twice to be sure.

Taking one side over the other, in either direction, a whole run at once. What is
written back keeps the line endings it had: a comparison that rewrote those would turn a
one-line change into a change on every line of the file.

Three files align on the middle one, which is the arrangement that answers "who
changed what" when two people have both edited a common version. Copies go between
neighbours, since left and right are not next to each other.

Finding your way down a long file. A scrollbar the comparison draws for itself — the
platform's is drawn over the content and fades out when nothing is moving, measured
rather than assumed — with the thumb grabbed where it is grabbed and a track that jumps.
Beside it, a map of the whole file with the part on screen drawn over it: out of the way
until the pointer nears the right-hand edge, pinned from the bar for whoever wants it
there, and coloured red where a line is only on the left, green where it is only
on the right, amber where both have one and they differ.

Search and replace in one file, the other, or all at once; steps through what it
finds and replaces one or all — never into a side that is an earlier version.

Editing by hand, because not every fix is one side or the other. Double-click a line
and type: Enter breaks the line at the caret, which is also how a line is added;
backspace at the start joins it to the line above, which is how one is removed;
delete at the end pulls the next one up; the arrows walk from line to line; a
paste of several lines becomes several lines.

And the rest. The two sides can be put the other way round, address and tab name with
them. A long file folds down to the differences and says how much it folded, always
yours to unfold. In a tab, the tab is named after the files it compares, and coming back
reads neither file again — the lines, the difference you were on and anything taken
across and not yet saved are all still there. A comparison with unsaved lines says so
before you close its tab or the window.

A file against its own past. From the Versions panel, Compare with the file now
puts an earlier version beside the file as it stands, the older one on the left, which
is the order that reads as "what happened since". That side is read-only: there is
nothing to write a version back to.

What holds it together

The lines are the state, the alignment is derived from them. Taking a block across is
then a three-line operation — replace those lines on that side — and every row, every
difference, every count and the position in the row of them is recomputed from that.

The alignment is written rather than borrowed. A patch says what changed; a
comparison has to put line 40 of one file beside the line it corresponds to in the
other, with a gap where there is nothing to put. Myers with a trace and a backtrack,
bounded so a pair of files with nothing in common cannot take the window with them.

One answer about what a line is. The panes and the map render the same five-valued
answer. They used to decide separately, and disagreed the moment a difference contained
a line from each side — a rewritten section is exactly that, and the map painted amber
over what the panes showed green.

The screen is a page, so what it holds lives outside it. A comparison is handed to
its tab and comes back with it, because the lines taken across and not yet saved exist
nowhere else.

Configuration

Everything the text editor can open is comparable. COMPARE_FILE_EXTENSIONS adds
whatever else an installation calls text — a local question, somebody's .ino,
somebody else's .tf. Comparison itself needs no flag.

Checked

Built and booted on main with main's lockfile, every client call matched to a route,
and the suites run in that tree: 3 278 frontend, 4 048 backend, plus the browser
journeys for what only a browser can answer — the scrollbar and the map, the colours,
walking the differences, taking a side across and reading the file back off disk.

@cerede2000 cerede2000 changed the title [44/44] Compare two or three files side by side Compare two or three files side by side Sep 29, 2026
@cerede2000
cerede2000 force-pushed the p3-60 branch 4 times, most recently from c01ab91 to 3b4c8b5 Compare October 1, 2026 20:40
Benjy added 2 commits October 3, 2026 12:15
Off by default, and turned on in Settings → Preferences → Browse in tabs.

A tab is an address rather than a folder, so the strip holds anything the
application can be at: a folder, a document, the text editor, a terminal,
the trash, the settings. Each tab keeps what it was on — its listing, what
was selected in it, the rename it was in the middle of, the place it was
scrolled to. The clipboard is deliberately not per tab: copying in one and
pasting in another is the point of having two.

One rule opens anything behind: the platform modifier held with the gesture
that opens the thing — the double click on a row, the single click on a
favourite, a volume or a place on the home page, and Enter on whatever the
keyboard is on. On a row that modifier with a single click still adds to the
selection, which is worth more than a tab. The middle button says the same
thing without a modifier, anywhere.

What a tab holds stays alive while another is in front. A document is not
opened again when you come back to it; the text editor comes back where you
left it without reading the file again — what you typed and had not saved,
the selection, and the line you were reading, put back before the editor is
shown so the file does not appear at the top and jump. A folder tab comes
back the same way, and is refreshed quietly behind what you were already
looking at.

A tab opened behind is got ready before you reach it: the folder listed, the
document opened in its viewer, the text file read, the shell started. That
is Settings → Preferences → Prepare tabs opened in the background, and it is
capped at three prepared editors at once. While a tab is still working it
shows a spinner where its icon goes, as a browser does — for a document,
until it is really in the editor rather than until the panel exists.

Back and Forward belong to the tab, not to the window: each keeps its own
trail, so going back in one never takes you where another was looking, and
the buttons grey out when that tab has nowhere to go.

The rest of what the strip does: tabs are dragged along the row, duplicated,
kept (pinned to the front as their icon alone, with no cross and immune to
"close them all"), closed one at a time or all at once, and closed by double
click for whoever turns that on. Files and folders dragged out of a listing
can be dropped onto a tab, which moves them into the folder that tab is on —
or copies them with the modifier held. The strip never scrolls: tabs share
the room and stop at a number an administrator sets under Settings → Tabs.

A terminal is a place now. What a terminal *is* came out of the drawer that
showed it, so a page at an address of its own can show one too and there can
be as many shells as there are tabs; the drawer opens per tab, and a new tab
only on the gesture that asks for one.

Closing a tab is the one gesture that destroys what a tab was holding, so a
screen with something to lose can leave a question for its own tab. Asking
it meant the application had to be able to ask at all: `window.confirm`
answers by stopping the world, which a dialog inside the page cannot, so the
question is the application's own dialog and everything from the guard to
the cross on the tab waits for the answer. The browser's boxes are gone from
the rest of the window with it — twenty-three of them, `confirm`, `prompt`
and `alert`, replaced by that dialog and by the notifications this
application already had.

The browser journey comes with it, and with the bench it needs. Most of what
tabs do is what a browser does to a page that is not on screen, and that is
not a question a unit test can ask: a page unmounted the moment another tab
comes forward, a menu clipped away by the strip's own overflow, a shell that
dies when the layout holding it does. Each of those passed its unit suite and
was broken in a browser. So `frontend/e2e/app` holds one journey through the
whole application — set up, sign in, upload, share, delete and restore, read a
version, edit, browse in tabs — run by a second Playwright project against the
real server entry point serving the real build, in a throwaway install the
journey also reads from disk to check what actually landed there. It runs
under `npm run test:e2e` beside the specs already here, and takes about half a
minute.

Two readers in one folder, and the two things they were sharing that they
should not have been.

Where a reader is in a folder has always been remembered against the folder.
With tabs that is no longer enough, because two tabs can be in one folder at
once and be in different places in it — so a tab's place is kept against the
tab as well. It was, and it was then read as a number: `tabPlace(key) ||
get(folder)`. A tab sitting at the top answers nought, nought is false, and
the fallback is the folder's memory that every tab on it shares. Two tabs on
one folder, the first scrolled to the bottom and left there, the second never
moved from the top: the second came back down beside the first. The top is an
answer, so the question is now whether this tab has a place here at all.

The permission to be put back was shared the same way. It is granted for a
folder when somebody walks back up into it — and consumed by whichever tab is
drawn on that folder next, which need not be the tab that was promised it.
One tab walking up out of a subfolder, or coming back out of a file, moved
another reader. The permission names its tab now, and the rule that grants it
moved out of the router's guard into `router/restorePermission.js` so that it
can be asked directly, the way the other rules there are: it had no test, and
what had no test is what went wrong.

The Tabs page sits with the other preferences in the settings list rather
than at the end of it.
Choose two or three text files in a listing, right-click, and Compare. They
open at an address of their own, so a comparison lives in a tab, can be
linked to, and is somewhere you leave and come back to rather than a dialog
to finish.

Every line of both files is there and aligned: two versions of a line sit on
one row, and a gap shows where a side has nothing. Within a changed pair the
part that actually differs is marked, which is what makes one character
different in a long path readable. The alignment is written here rather than
taken from a library because the answer has to be aligned and not merely
listed: a patch says what changed, a comparison has to put line 40 of one
file beside the line it corresponds to in the other.

The differences are counted and numbered, and F8 and F7 walk them — alt with
the arrows does the same, for a keyboard without those keys — coming round
again at the end rather than stopping. Each difference can be taken across in
either direction, a whole run at once, and the file written back keeps the
line endings it had: a comparison that rewrote those would turn a one-line
change into a change on every line.

Three files align on the middle one, which is the arrangement that answers
"who changed what" when two people have both edited a common version. Copies
there go between neighbours, since left and right are not next to each other.

Along the right: a bar the comparison draws for itself, because this
platform draws its own over the content and fades it out when nothing is
moving, and beside it a map of the differences — out of the way until the
pointer nears the edge, pinned from the bar for whoever wants it there. The
map is coloured the way the panes are and from the same single answer about
what a line is: red where a line is only on the left, green where it is only
on the right, amber where both have one and they differ.

Search and replace looks in one file, another, or all of them at once, steps
through what it finds, and replaces one or all — never into a side that is an
earlier version. A line can be edited by hand: double-click it and type, with
Enter breaking the line, backspace at the start joining it to the one above,
the arrows walking from line to line, and a paste of several lines becoming
several lines.

The two sides can be put the other way round, address and tab name with them.
A long file folds down to the differences with a little around them and says
how much it folded, and that is always yours to unfold.

In a tab, the tab is named after the files it compares, so three comparisons
open are three tabs you can tell apart — and coming back to one reads neither
file again: the lines, the difference you were on and anything taken across
and not yet saved are all still there. A comparison with unsaved lines says so
before you close its tab or the window.

Also from the Versions panel: Compare with the file now puts an earlier
version beside the file as it stands, the older one on the left, which is the
order that reads as "what happened since". That side is read-only — there is
nothing to write a version back to.

Everything the text editor can open is comparable. COMPARE_FILE_EXTENSIONS
adds whatever else an installation calls text, because that is a local
question: somebody's .ino, somebody else's .tf.
@cerede2000

Copy link
Copy Markdown
Contributor Author

Closed by a branch rename on our side, not withdrawn. Same work, same branch, under a name without a batch number: #493 for the tabs, #494 for the comparison.

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.

Compare two or three files side by side

1 participant