Skip to content

Feat/split view - #3

Merged
codecancu merged 7 commits into
mainfrom
feat/split-view
Jul 25, 2026
Merged

Feat/split view#3
codecancu merged 7 commits into
mainfrom
feat/split-view

Conversation

@codecancu

Copy link
Copy Markdown
Owner

What & why

Checklist

  • Signed off (DCO -s flag on all commits)
  • npm run lint passes
  • npm run typecheck passes
  • npm test passes
  • Added or updated tests for behavior changes
  • New source files include // SPDX-License-Identifier: GPL-3.0-or-later

claude and others added 7 commits July 9, 2026 13:45
Dropping one or more files onto the page opens each in a new tab, or — if a
tab already has that file open — activates the existing tab instead of
creating a duplicate. "Same file" is decided by FileSystemFileHandle identity
(isSameEntry) when the browser exposes a handle, falling back to file name.
Files open in the focused split pane; the editor is focused afterwards.

- FileActions.openDropped() + _findOpenDoc() + _readFile() reuse the existing
  size-guard / EOL / BOM / disk-baseline logic.
- App wires capture-phase dragover/drop guarded to file drags (stopPropagation)
  so file drops never reach CodeMirror's text-drop handling, while dragging
  selected text within the editor still works.
- CHANGELOG: note under 0.3.0 Added.

Tests: file-actions.test.ts (+4: new tab, activate by handle identity,
distinct entry same name, activate by name); drag-drop.spec.ts e2e (+2).
644 unit + 185 e2e pass; typecheck and lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a Notepad++-style Plugins system where plugin code is NEVER bundled into
editor.js — it loads on demand (dynamic import → separate webpack chunk) only
when the user enables it, keeping startup fast.

Plugin system (Phase A):
- plugin-api.ts: Plugin + PluginContext (store, focused view/controller,
  dockManager, namespaced storage, menu contribution).
- registry.ts: lazy enable/disable via dynamic-import loaders; enabled ids
  persisted and re-activated AFTER app-ready (never blocks first paint).
- New "Plugins" top-level menu (between Macro and Help) + Plugin Manager modal
  (Enable/Disable with a Loading… state).
- Wired through app.ts (menu render + refresh) and editor-page.ts (registry
  construction + post-appReady restore).

MarkdownViewer++ (Phase B):
- Docked live preview: CommonMark + GFM (tables, task lists, strikethrough)
  via marked, sanitized with DOMPurify, rendered in a shadow root so custom CSS
  is isolated. Auto-refresh (debounced) + synced scroll + Ctrl+Shift+M toggle.
- Options (custom CSS, persisted), Export HTML; PDF via browser Print.
- marked + dompurify live only in the plugin chunk (verified: 0 DOMPurify refs
  in editor.js; they're in the on-demand 214.js chunk).

Tests: registry (lazy-load, persistence, disable, failure, namespacing);
markdownToHtml (GFM); e2e markdown-viewer.spec.ts (enable→lazy-load→preview,
auto-refresh, real-browser XSS sanitization). Menu-count tests updated to 10.
653 unit + e2e pass; typecheck, lint, build clean.

Docs: docs/plugins/README.md, docs/plugins/markdown-viewer.md (user flows).

ComparePlus (Phase C) and CHANGELOG/perf finalize (Phase D) still to come.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A persisted dock layout restores the Markdown Preview panel on reload BEFORE
the lazily-loaded plugin re-registers its renderer, so the panel mounted as a
blank fallback with no content. On activate, close any such stale restored
instance so togglePreview() re-mounts it with the real renderer.

Adds an e2e regression test: enable → reload → preview still renders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports the browser-feasible subset of Notepad++ ComparePlus, lazy-loaded like
MarkdownViewer++ (jsdiff lives only in the plugin chunk, never in editor.js).

- diff-engine.ts (pure, tested): line diff with change-pairing + inline char
  diff, ignore whitespace/case/empty-lines, heuristic move detection, and a
  set-based "find unique lines" mode.
- Renders into a generic always-present overlay decoration slot
  (src/editor/plugin-overlay.ts) added to both editor panes — avoids appending
  a StateField to a freshly-created split pane (which doesn't render reliably).
  Decorations are painted after a layout frame so the new pane is sized.
- Uses the split view (new SplitApi on PluginContext / App.getSplitApi): left =
  previous tab, right = active tab. Modes: Compare, vs Clipboard, vs Last Save,
  Find Unique Lines. Next/Prev/First/Last diff navigation. Clear Results.
  Settings dialog for the ignore/move/char options.
- Git/SVN diff and Compare Selections are disabled (not feasible / not yet).

Also: CHANGELOG entry for the plugins feature; docs/plugins/compareplus.md
(user flows). Perf verified: editor.js has 0 refs to jsdiff/DOMPurify.

Tests: diff-engine unit tests (add/remove/change/char/ignore/moves/unique);
compare-plus.spec.ts e2e (split + diff highlights, Clear Results). 660 unit +
192 e2e pass; typecheck, lint, build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oll rebind

ComparePlus: the two panes now align line-by-line. The diff engine emits filler
"gaps" for lines present on only one side; those render as block-widget spacers
so matching lines stay horizontally aligned, and the panes scroll in lockstep.

MarkdownViewer++: the synced-scroll listener now rebinds to the focused editor
pane on tab/pane changes (previously bound once at mount), so scrolling follows
the active document. (Content already tracked the active tab — verified via
switch/close/reload/split e2e.)

Tests: engine gap tests; compare-plus alignment e2e (spacer present + equal pane
heights). 662 unit + 193 e2e pass; typecheck, lint, build clean. CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In split view:
- Dropping a file lands in the pane the cursor is over (not just the focused
  pane); if that file is already open in the other pane, its tab is moved across
  rather than duplicated.
- A tab can be dragged from one pane's strip onto the other to move it there.

- FileActions.openDropped now delegates placement to a callback so the app can
  target a specific pane; App.openDocInView moves/creates + shows a doc in a
  given pane (falls back to primary when the pane doesn't exist).
- The drop handler derives the target pane from the drop's element (guarded
  against non-Element targets like document, which previously threw).
- TabBar tabs are draggable and each strip is a drop target (custom
  application/x-notepad-tab payload), wired to App.openDocInView.

Tests: split-drag-drop.spec.ts e2e (drop into pane, move-across, tab drag).
662 unit + 196 e2e pass; typecheck, lint, build clean. CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Improve tab strip overflow handling by:

- Updating the overflow algorithm to always keep the active tab visible (never hidden in overflow), using a greedy left-to-right approach instead of checking if tabs extend past the cutoff
- Actually hiding overflowed tabs with display: none to prevent them from occupying layout space and pushing the >> button off-screen
- Resetting display state before each measurement to account for previously hidden tabs
- Fixing the shell's grid layout with minmax(0, 1fr) and min-width: 0 to clip content to viewport instead of expanding the grid

Adds regression tests for narrow viewport behavior and tab activation from overflow menu.
@codecancu
codecancu merged commit 10573a1 into main Jul 25, 2026
1 check passed
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.

2 participants