Dual-panel file manager VS Code extension overlaid on a real terminal via pseudoterminal + node-pty.
It is designed to be as close replica as possible to the Far Manager. The Far Manager source code is located inside the "../FarManager" directory. Also there is a copy of Midnight Commander source code inside the "../mc" directory - both next to the project in the same parent directory.
IMPORTANT when implementing a new feature - carefully read the corresponding Far Manager code to get a complete understanding of:
- the feature logic
- edge cases and oddities
- visual representation
- hotkeys, key bindings, and mouse interactions and replicate the feature EXACTLY.
- when implementing a file system file-related feature (copy, move, deletion, etc) - think of all possible edge cases (low disk space, different filesystems, different drives for the source and target locations, symlinks, hardlinks, recursive soft- and hardlinks, file system errors, bad blocks, missing access permissions, incorrect file state and opned descriptiors, and much more). Read the Far Manager source code and Midnight commander for reference before thinking on how to implement these features. Ask all the necessary clarifying questions before implementation.
Your context window will be automatically compacted as it approaches its limit, allowing you to continue working indefinitely from where you left off. Therefore, do not stop tasks early due to token budget concerns. As you approach your token budget limit, save your current progress and state to memory before the context window refreshes. Always be as persistent and autonomous as possible and complete tasks fully, even if the end of your budget is approaching. Never stop and report unfinished tasks: complete everything before stopping work.
Every feature must go through the Feature Implementation Workflow (see Conventions below) — driven by the far-feature-spec skill.
npm install
npm run compile # tsc -p ./ (for debugging with F5)
npm run watch # tsc -watch -p ./ (development)
npm run bundle # esbuild production bundle (single minified file)
npm run bundle:watch # esbuild watch mode
npm run typecheck # tsc --noEmit (type checking only)
npm run package # bundle + vsce package (produces .vsix)
npm test # integration test suite (see Testing below)Integration tests live in src/test/ and run the real extension inside a
headless VS Code via @vscode/test-electron + Mocha.
- Each test is a directory
src/test/suite/nnn - description/containingtest.ts, an optionalfilesystem/fixture (panel1/,panel2/,settings.json— all optional), andscreenshots/reference files. Harness(src/test/suite/harness.ts) drives a live panel: it copies the fixture to a temp sandbox, opens the panel at a pinned 100x30 size, sends keyboard/mouse input, and captures the panel's render as a text grid.expectScreenshot(name)compares the panel against a reference file where?matches any character (volatile cells — paths, clock, dates, the shell command line — are auto-masked). Regenerate references withUPDATE_SCREENSHOTS=1 npm test.- Tests validate both the UI (screenshots) and filesystem side effects.
- Every new feature should get a
nnn - description/test directory. TEST_ONLY="<substring>" npm testruns only the test directories whose name matches — used to run a feature's own tests before the full suite.
Press F5 in VS Code to launch the Extension Development Host. Run "VSCommander: Open Terminal" from the command palette, then Ctrl+O to toggle the panel.
src/
extension.ts Orchestrator — Pseudoterminal, command registration, VS Code API. Delegates to components below.
shell.ts node-pty shell proxy — spawn, resize, data forwarding, kill
panel.ts Panel coordinator — layout, popup routing, pane navigation, render dispatch
draw.ts Low-level ANSI escape sequence primitives (cursor, box, color, alt screen)
timerManager.ts Generic BlinkTimer + PollTimer — replaces all timer patterns
copyMoveController.ts Copy/move orchestration — scan, progress, error dialog, navigate — no vscode
fileOps.ts File system operations (mkdir, copy, move, recursive copy) — no vscode
shellRouter.ts Shell input tracking, cd suppression, output buffer — no vscode
quickView.ts Quick view state machine + QuickViewHost interface — no vscode
commandLine.ts Command line row: render, cursor blink, spinner, shell input
fkeyBar.ts Function key bar: render, mouse hit-test
terminalArea.ts Terminal buffer rendering in hidden-pane area
cellQuery.ts Cell-at-coordinate query for popup shadows — pure function
pane.ts Single file-list pane: entries, cursor, selection, rendering
components/ Generic, window-agnostic UI components (see "Component
Architecture" below): formView, popup, composedPopup,
popupTable, inputControl, checkboxControl, dropdownControl,
buttonGroup, buttonGrid, comboBox, checkboxGrid,
maskedInput, optionList, textBlock, progressBar,
scrollIndicator, colorGrid
windows/<name>/ One directory per dialog window; index.ts defines the
window, specialized component files nest alongside it
Data flow: VS Code terminal ↔ Pseudoterminal (extension.ts) ↔ shell.ts (normal mode) OR panel.ts (panel mode). Toggle switches between alt screen buffer (panel) and main buffer (shell).
Dependency tree:
extension.ts (orchestrator)
├── timerManager.ts
├── copyMoveController.ts
│ └── fileOps.ts
├── shellRouter.ts
├── quickView.ts
├── directoryInfo.ts
├── shell.ts
└── panel.ts (coordinator)
├── commandLine.ts
├── fkeyBar.ts
├── terminalArea.ts
├── cellQuery.ts
├── pane.ts
└── windows/<name>/ (each built from components/)
No new file imports vscode (except extension.ts and directoryInfo.ts which already do).
This extension must work on Linux, FreeBSD, macOS, and Windows. Every change must respect:
- Paths: Always use
path.join()/path.resolve()/path.dirname()— never concatenate with/or\ - Home directory: Use
os.homedir()— neverprocess.env.HOME(unset on Windows) - Shell detection:
os.platform() === 'win32'→ PowerShell; otherwise$SHELLor/bin/bash - Terminal characters: ASCII and standard Unicode box-drawing only (U+2500 block). No emoji — they have inconsistent width across terminal emulators and platforms
- Line endings: All source files use LF. The terminal handles its own line endings
- ANSI sequences: Stick to xterm-256color — supported by Windows Terminal, conhost (Win10+), and all Unix terminals
- Strict mode enabled (
"strict": truein tsconfig) - Target ES2020, CommonJS modules
- Output to
out/directory - Imports:
import * as X from 'X'style (esModuleInterop enabled)
- No comments unless the logic is non-obvious
- No emoji in code or output strings
- Functions that produce ANSI output return
string— the caller writes to the emitter - Panel state is mutated in place, render functions read from it
- Keep
draw.tsas pure ANSI primitives with no business logic - Keep
panel.tsand all files it depends on with no knowledge of VS Code APIs - Keep
shell.tswith no knowledge of VS Code APIs - Only
extension.tsanddirectoryInfo.tsmay importvscode
All panel settings changes (theme, colors, display options) are IN MEMORY by default. Settings only persist to VS Code configuration when the user explicitly uses F9 > Options > Save settings. Never auto-persist settings changes.
- All drawing uses absolute cursor positioning (
moveTo(row, col)) - Panel uses alternate screen buffer (
\x1b[?1049h/\x1b[?1049l) to preserve shell history - Icon/text column widths must be tracked as display width (terminal cells), not JS string length
- Box borders use Unicode box-drawing:
BOX.topLeft(┌),BOX.horizontal(─), etc.
vscommander.open— opens a new VSCommander terminalvscommander.toggle— toggles panel overlay (bound toctrl+owhenterminalFocus)
USER.md at the project root is the user-facing guide.
docs/ at the project root is the directory containing grouped by functionality articles in depth with examples. This is the source for the built-in help menu. If the documentation is updated here it should be updated in the F1 (help) menu too.
Every feature change must update USER.md and create a new document or update an existing one in in docs/ — new keybindings, new commands, new panel behaviors, changed defaults. If you add it to the code, update the documentation.
README.md at the project root is the community-facing project description. Every major feature must be reflected in USER.md. The file language is concise, highlighting important and competitive features.
- After each iteration of code changes, run
npm run compileto check for TypeScript errors before moving on
Every window is assembled only from components — no in-place UI code, no ad-hoc one-off components.
- Strict hierarchy. A specialized component must be a derivative of a
generic component. Example: a
UserDropdownspecialized component derives from the genericDropdownInputcomponent, specializing it by filling its default options from/etc/passwd. Every component traces, by derivation, down to a generic base component. - No in-place or ad-hoc components. Every component is a named, reusable class in its own file — never defined inline inside a window.
- Generic components live in
src/components/. They know nothing about any specific window. - Windows live in
src/windows/<window-name>/: anindex.tsdefines the window; its window-specific (specialized) component files nest in the same directory. - Before implementing a feature, search
src/components/first — reuse an existing generic component, or derive a specialized one from it. - If a new feature needs new components, discuss the component hierarchy with the user before building it.
Every feature that replicates or extends Far Manager follows this workflow:
- Spec it. Invoke the
far-feature-specskill (.claude/skills/far-feature-spec/). It drives the real Far Manager, captures its behaviour, mines the Far source, and writesspecs/<feature>.mdwith reference captures. - Write the tests first. Implement the spec's test plan (its
section 14) as a
src/test/suite/nnn - <feature>/test directory — one reference screenshot per distinct visual state, plus filesystem assertions. See the Testing section. - Implement the feature per the spec and the architecture rules in "Adding New Features" below.
- Run the feature tests only:
TEST_ONLY="<feature>" npm test— iterate until they pass. - Run the entire suite:
npm test— confirm there are no regressions.
Rules:
- Far is the default source of truth — replicate it exactly unless an intentional improvement has been decided.
- Intentional improvements: VSCommander sometimes deliberately improves on Far Manager. When it does, the spec MUST describe the improvement and the reason, clearly marked as a deliberate deviation — never a silent divergence.
- If the existing implementation differs from the spec: do NOT silently pick one. Ask which behaviour to implement — replicate Far, keep the current VSCommander behaviour, or adopt a new improvement — before proceeding.
- Drawing primitives go in
draw.ts - Panel visual components go in their own files (
commandLine.ts,fkeyBar.ts,terminalArea.ts,cellQuery.ts) - Panel coordination (navigation, popup routing) goes in
panel.ts - File system operations go in
fileOps.ts - Shell/PTY concerns go in
shell.tsorshellRouter.ts - Timer patterns use
BlinkTimer/PollTimerfromtimerManager.ts - VS Code API integration goes in
extension.ts - Generic UI components go in
src/components/; a dialog window is asrc/windows/<name>/index.tsassembled from them — see "Component Architecture". A FormComponent (e.g.comboBox.ts,checkboxGrid.ts,maskedInput.ts) plugs into aFormViewviaaddComponent() - New files must not import vscode — only
extension.tsanddirectoryInfo.tsmay - Test on multiple platforms or at minimum verify no platform-specific APIs are used without guards
- Update
USER.mdwith any user-visible changes