vcom/plans/mirror-and-cursor-memory.md

89 lines
3.8 KiB
Markdown
Raw Normal View History

# Feature Plan: Mirror Pane & Per-Directory Cursor Memory
## Feature 1: Mirror active pane directory to opposite pane
### Keybinding research
- **Midnight Commander (MC)**: `Alt-i` — makes the other panel equal to current directory
- **Total Commander**: `Ctrl-PgDn` opens directory under cursor in opposite panel
- **Far Manager**: `Ctrl-Left/Right` — opens in opposite panel
**Recommendation**: Bind to `p` (for "Pane"). `Ctrl-i` sends the same byte as `Tab` (ASCII 0x09) so it conflicts with Switch. `p` is free, accessible, and "Pane" reflects the meaning.
### How it works
1. Read active pane's current path (including remote mount state)
2. Apply that path to the passive (opposite) pane
3. If active is in a remote mount, also clone the remote mount stack to the passive pane
4. Reload both panes to reflect the change
### Code changes
#### [`internal/ui/keymap.go`](internal/ui/keymap.go)
- Add `Mirror` key binding field to `KeyMap` struct
- Bind to `"p"` with help text `"p"` / `"mirror pane"`
#### [`internal/ui/model.go`](internal/ui/model.go)
- Add handler `handleMirrorPane()`:
1. Get active pane path and remote mount state
2. Switch passive pane to match (copy remote stack if applicable)
3. Reload passive pane (using `reloadPane` or `reloadRemotePane`)
4. Set status: `"Mirrored: <path>"`
- Add key match case before the `default` switch
---
## Feature 2: Per-directory cursor position memory (session scope)
### Current state
- `enterSelected()` saves `pane.Path` to history for back-nav, then reloads target dir with `selected.Name` as preserve key — but `selected.Name` is the name of the directory being entered, not a cursor anchor
- `goParent()` pops history and reloads parent using the child directory name as preserve — cursor lands on the directory we came from
- **Missing**: When navigating back into a previously-visited directory, there's no saved cursor position for it
### Design
Add a `cursorMemory` field to `BrowserPane` — a `map[string]string` mapping **directory path → last selected entry display name**.
This integrates cleanly with the existing `SetEntries(entries, preserveKey)` / `FindSelected()` infrastructure.
### Flow
```
enterSelected() / enterRemoteDir():
1. Save to cursorMemory: pane.Path → selected entry's Name
2. Push history (existing)
3. Set path, reload (existing)
4. The reload already uses preserve=selected.Name for the new dir,
but cursorMemory will help when coming back later
reloadPane() / reloadRemotePane():
1. Try preserve key first (existing behavior)
2. If preserve is empty, check cursorMemory[pane.Path]
and use that as preserveKey instead
```
### Code changes
#### [`internal/ui/pane.go`](internal/ui/pane.go)
- Add field to `BrowserPane`: `CursorMemory map[string]string`
- Method `SaveCursor(path string, name string)` — stores cursor position
- Method `LoadCursor(path string) string` — retrieves saved cursor
#### [`internal/ui/model.go`](internal/ui/model.go)
- In `enterSelected()` (line ~1886): after `pane.PushHistory(pane.Path)`, add `pane.SaveCursor(pane.Path, selected.Name)`
- In `enterRemoteDir()` (line ~5726): same save logic
- In `goParent()`: save cursor before navigating away (already partially done via history)
- In `reloadPane()` (line ~1672): after existing preserve logic, if no preserve key and directory has cursor memory, use it
- In `reloadRemotePane()` (line ~5535): same fallback
---
## Files modified
| File | Changes |
|------|---------|
| `internal/ui/keymap.go` | Add `Mirror` field, binding `p` |
| `internal/ui/pane.go` | Add `CursorMemory` field + methods |
| `internal/ui/model.go` | Add `handleMirrorPane()`, save/restore cursor in navigation methods |
## Not changed
- `internal/fs/` — storage layer unaffected
- `internal/config/` — no config changes needed
- `internal/theme/` — no theme changes needed