Skip to content

feat(editor): implement multi-pane split layout#2416

Draft
bajrangCoder wants to merge 16 commits into
mainfrom
feat/multi-pane-view
Draft

feat(editor): implement multi-pane split layout#2416
bajrangCoder wants to merge 16 commits into
mainfrom
feat/multi-pane-view

Conversation

@bajrangCoder

@bajrangCoder bajrangCoder commented Jun 28, 2026

Copy link
Copy Markdown
Member

This PR introduces support for a Multi-Pane (Split-Screen) Editor Layout, allowing users to open multiple editor panes and view files/tabs side-by-side (similar to desktop code editors). Each pane acts as a self-contained editor workspace with its own tab bar, and tabs things. And it is not limited to editor tabs, it can work with any editor tabs such as terminal, custom, editor, media etc.

It adds few commands for this multi pane, which you can search in command palette with pane keyword.
And some keybinds:

  • Ctrl-\ : Split editor pane right
  • Ctrl-Shift-\ : Split editor pane down
  • Ctrl-Alt-W : Close active editor pane
  • Ctrl-Alt-Left : Focus editor pane to the left
  • Ctrl-Alt-Right : Focus editor pane to the right
  • Ctrl-Alt-Up : Focus editor pane above
  • Ctrl-Alt-Down : Focus editor pane below
  • Ctrl-Alt-\ : Move current tab to new pane
Screenshot 2026-06-28 at 11 03 22 PM image

@github-actions github-actions Bot added the enhancement New feature or request label Jun 28, 2026
@bajrangCoder bajrangCoder changed the title feat: add multi pane view feat(editor): implement multi-pane split layout Jun 28, 2026
@bajrangCoder bajrangCoder moved this from Backlog to In progress in The Code Board - Acode Jun 29, 2026
@bajrangCoder bajrangCoder marked this pull request as ready for review June 29, 2026 08:50
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a full multi-pane split-screen editor layout, allowing users to open multiple self-contained editor panes side-by-side, resize them via drag handles, drag tabs between panes, and navigate panes via keybindings and command palette. The implementation is large (~1500+ net lines) and touches the core editor lifecycle in editorManager.js, editorFile.js, the tab drag handler, and styles.

  • Pane engine (editorManager.js): adds a layout-tree, per-pane shell structs, a Proxy-based openFileList aggregating tabs from all panes, per-file docSyncTimers WeakMap (fixing the previously-flagged shared-timer race), and createEditorCompatibilityDescriptors factory that correctly closes over each pane's own editor (fixing the previously-flagged setTheme closure bug).
  • File lifecycle (editorFile.js): makeActive() iterates a blurredFileIds set for correct multi-pane deactivation; remove() routes via removeFileFromPane; pane-placeholder semantics prevent empty panes from appearing without a tab.
  • Drag-and-drop (editorFileTab.js): cross-pane tab transfer added to the existing FLIP reorder system; the internal animate() helper renamed to step() to avoid shadowing the imported animate from Motion.

Confidence Score: 3/5

The change is architecturally sound and addresses most previously-identified issues, but the double doc-sync listener on secondary panes and the async editor-creation window deserve review before merging.

The implementation fixes the docSync timer race, file-loaded pane-awareness gap, and setTheme closure bug from prior review rounds. However, createPaneEditor dispatches getDocSyncListener() via appendConfig AND applyFileToEditor embeds it again in every new EditorState via exts.push, so each keystroke in a secondary pane fires the sync callback twice and emits editor-state-changed twice. Additionally, createPane renders the new pane into the DOM before createPaneEditor resolves, leaving a brief window where the visible pane has editor=null and manager.activeFile=null.

src/lib/editorManager.js warrants the closest review, particularly createPaneEditor, createPane, switchFile, and the getDocSyncListener attachment sequence.

Important Files Changed

Filename Overview
src/lib/editorManager.js Core change: introduces multi-pane layout engine, per-file docSync timers, pane-aware file switching, and Proxy-based openFileList aggregation. Previously-flagged issues around file-loaded, docSync timer collisions, and setTheme closure appear fixed; secondary pane editors still receive getDocSyncListener via both appendConfig and EditorState exts (double-firing); pane is rendered into DOM before its editor is created.
src/handlers/editorFileTab.js Extends drag-and-drop tab reordering to support cross-pane tab transfers; renames internal animate() to step() to avoid collision with imported Motion library; migrates from WAAPI to Motion for reorder animations; adds allowPaneTransfer guard to prevent moving placeholder tabs.
src/lib/editorFile.js Adds paneId/pane/isPanePlaceholder fields; refactors makeActive() to use a blurredFileIds set for correct multi-pane deactivation; adds syncQuickToolsVisibility helper; guards remove() to avoid creating placeholders when suppressPanePlaceholder is set; setReadOnly now routes the dispatch to the correct pane editor.
src/lib/commands.js Adds all pane commands; updates next-file/prev-file to be pane-scoped; adds null guards on activeFile accesses; updates getTabsRelativeToFile to use pane-scoped file list.
src/cm/commandRegistry.js Registers 5 pane commands in the command palette; directional variants (splitPaneRight/Down, focusPaneLeft/Right/Up/Down) are missing and won't be discoverable in the palette.
src/lib/keyBindings.js Adds all pane-related keybindings; close-pane moved to Ctrl-Alt-W (resolves the previously-flagged Ctrl-Shift-W browser conflict); directional focus and move-tab bindings added correctly.
src/styles/codemirror.scss Adds all pane layout styles: editor-pane-root, editor-pane-split, editor-pane, resize handles, pane tab bars, and responsive breakpoint (<=720px) that hides inactive panes on small screens.
src/lib/openFile.js Threads paneId through to EditorFile construction and to existing-file activation; when opening an already-open file with a target paneId, moves the file to that pane rather than just making it active globally.
src/lib/fileList.js Single-line fix: guards activeFile?.loading access in initFileList to prevent crash when activeFile is null.
src/lib/showFileInfo.js Adds null guards on activeFile and type checks; isEditor now correctly validates that the queried URL matches the active file URI before reading it as editor content.
src/styles/page.scss Refines .open-file-list selectors to exclude .editor-pane-tabs so fullscreen and footer-height positioning rules don't accidentally apply to pane-local tab bars.
src/components/sidebar/index.js Sidebar resize now calls editor.requestMeasure() for CodeMirror EditorView instead of editor.resize(true); handles both Ace and CodeMirror APIs defensively.
src/main.js Adds null guard on activeFile; extends edit-menu toggler and file-menu logic to also hide for terminal tabs; defensive access of editorManager via optional chaining.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant Command as commands.js
    participant EM as editorManager
    participant Pane as New Pane
    participant EF as EditorFile

    User->>Command: split-pane-right
    Command->>EM: splitPaneRight()
    EM->>EM: canCreatePane(direction, sourcePane)
    EM->>EM: createPaneShell() pushes to panes[]
    EM->>EM: insertPaneIntoLayout(sourcePane, pane)
    EM->>EM: updatePaneLayoutState() pane visible editor null
    EM->>EM: animatePaneEntry(pane)
    EM->>EM: await createPaneEditor(pane)
    Note over EM,Pane: async gap pane in DOM pane.editor is null
    EM->>Pane: new EditorView(state, parent)
    EM->>Pane: appendConfig(getDocSyncListener())
    EM->>EM: setupEditor(pane)
    EM->>EM: updatePaneLayoutState() editor ready
    EM->>EF: createUntitledPaneFile(pane)
    EF->>EM: addFile(placeholderFile)
    EF->>EF: makeActive() switchFile(id, pane)
    EM->>EM: setActivePane(pane)
    EM->>Pane: applyFileToEditor(file)
    Note over Pane: exts.push(getDocSyncListener()) in new EditorState
    User->>Pane: drag tab from Pane A
    Pane->>EM: commitPaneTransfer() moveFileToPane(file, pane)
    EM->>EF: file.makeActive() in target pane
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant Command as commands.js
    participant EM as editorManager
    participant Pane as New Pane
    participant EF as EditorFile

    User->>Command: split-pane-right
    Command->>EM: splitPaneRight()
    EM->>EM: canCreatePane(direction, sourcePane)
    EM->>EM: createPaneShell() pushes to panes[]
    EM->>EM: insertPaneIntoLayout(sourcePane, pane)
    EM->>EM: updatePaneLayoutState() pane visible editor null
    EM->>EM: animatePaneEntry(pane)
    EM->>EM: await createPaneEditor(pane)
    Note over EM,Pane: async gap pane in DOM pane.editor is null
    EM->>Pane: new EditorView(state, parent)
    EM->>Pane: appendConfig(getDocSyncListener())
    EM->>EM: setupEditor(pane)
    EM->>EM: updatePaneLayoutState() editor ready
    EM->>EF: createUntitledPaneFile(pane)
    EF->>EM: addFile(placeholderFile)
    EF->>EF: makeActive() switchFile(id, pane)
    EM->>EM: setActivePane(pane)
    EM->>Pane: applyFileToEditor(file)
    Note over Pane: exts.push(getDocSyncListener()) in new EditorState
    User->>Pane: drag tab from Pane A
    Pane->>EM: commitPaneTransfer() moveFileToPane(file, pane)
    EM->>EF: file.makeActive() in target pane
Loading

Reviews (12): Last reviewed commit: "fixed bunch of issues introduced acciden..." | Re-trigger Greptile

Comment thread src/lib/editorFile.js Outdated
Comment thread src/lib/editorManager.js
Comment thread src/lib/keyBindings.js
@UnschooledGamer UnschooledGamer added the area:editor Feedback, improvements related to the editor (code editing, editor iteration, formatting, etc) label Jun 29, 2026
@bajrangCoder

This comment was marked as outdated.

@bajrangCoder

This comment was marked as outdated.

@bajrangCoder

This comment was marked as duplicate.

@bajrangCoder

This comment was marked as outdated.

Comment thread src/lib/editorManager.js
@bajrangCoder

This comment was marked as outdated.

@bajrangCoder

This comment was marked as outdated.

@bajrangCoder

This comment was marked as outdated.

@bajrangCoder

This comment was marked as outdated.

@bajrangCoder

This comment was marked as outdated.

@bajrangCoder

This comment was marked as outdated.

@bajrangCoder bajrangCoder marked this pull request as draft June 30, 2026 12:45
@bajrangCoder

This comment was marked as outdated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:editor Feedback, improvements related to the editor (code editing, editor iteration, formatting, etc) enhancement New feature or request

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants