Skip to content

feat: add tab support - #28

Merged
kooler merged 7 commits into
kooler:mainfrom
gmist:tabs
Oct 2, 2026
Merged

kooler merged 7 commits into
kooler:mainfrom
gmist:tabs

Conversation

@gmist

@gmist gmist commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

The app had a single panel pair so working on two locations meant navigating away and losing state. Tabs give each workspace its own panels, focus, quickview, and server connections, and keep them alive while you work elsewhere.
scrshot-2026-09-26_21-25-55

  • Tabs — up to 10, each with its own panel pair, focus, quickview, and connections. A numbered tab bar on top labels them with abbreviated paths (home-relative, ssh locations kept intact).
  • Tab list (Ctrl-N) — floating table of open tabs: j/k move, n new tab from the row under the cursor, d close (list stays open for several closes), f live filter by number or path, 0-9 jump, Enter switch, Esc dismiss.
  • Per-tab state — quickview and focus survive tab switches; closing a tab releases only its own connections.
  • Shared connections — the same host open in several tabs shares one refcounted connection; closing one tab keeps it alive for the rest.

@kooler

kooler commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Epic feature @gmist !

A small feedback and it's good to go.

Release guard in internal/remote/conn.go

The new if c.refs <= 0 { return } check in Registry.Release turns a double release into a silent no-op. Every release in the PR and each one pairs with exactly one acquire, so nothing needs the guard today. My concern is that if a future change does release twice, this check would hide it, and the visible symptom would be a connection closing under a tab that still uses it. Could you please drop the guard so a refcount mistake stays visible? If you added it for a case I've missed, happy to hear it.

Unreachable "no ID" path for directory loads

A few pieces of the ID routing don't seem to be reachable:

  • quickview.Model.ID() has no callers; the preview routes its own results internally.
  • panel.HandleDirLoaded treats msg.ID == 0 as "addressed to every panel", but the only producer of DirLoadedMsg is LoadDir, which always sets an ID. The comment in app.go above the DirLoadedMsg case describes that case too.
  • TestDirLoadedWithoutAnIDReachesEveryTab constructs such a message by hand and is the only thing that exercises the branch.

Would you mind removing those three so the rule is simply "a load belongs to one panel"? It makes the routing easier to follow.

@gmist

gmist commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Double release check was a precaution in case of some hypothetical situations, but explicit behavior is better than implicit - "you are absolutely right". The other remarks seem to be corrected. Also corrected the mistake - now new tab there fully copy paths of the old tab in panels.

@kooler

kooler commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Thanks, looks good, merging in!

@kooler
kooler merged commit 99cdf63 into kooler:main Oct 2, 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