detached cf4e69e

refactor(ui): consolidate external pane routing

commit
cf4e69eb95aa8673ed12110156418ee7f026b2ed
author
brice · · 2026-08-13 03:59 UTC
committer
brice · · 2026-08-13 04:39 UTC
parent
41ddc34
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +29 -27
M internal/ui/browser_window.go +29 -27
@@ -616,15 +616,15 @@ func (a *App) openExternalURLOnMainThread(ctx context.Context, url string, cfg e
616616 return a.openFreshWindow(ctx, url)
617617 }
618618
619- if err := a.openExternalURLInFocusedWindow(ctx, url, externalLinks); err == nil {
619+ err := a.openExternalURLInFocusedWindow(ctx, url, externalLinks)
620+ if err == nil {
620621 return nil
621- } else {
622- logging.FromContext(ctx).Warn().
623- Err(err).
624- Str("url_host", logging.SafeURLHost(url)).
625- Str("behavior", string(externalLinks.Behavior)).
626- Msg("ui: external URL placement failed; opening fresh window")
627622 }
623+ logging.FromContext(ctx).Warn().
624+ Err(err).
625+ Str("url_host", logging.SafeURLHost(url)).
626+ Str("behavior", string(externalLinks.Behavior)).
627+ Msg("ui: external URL placement failed; opening fresh window")
628628 return a.openFreshWindow(ctx, url)
629629 }
630630
@@ -656,29 +656,15 @@ func (a *App) openExternalURLInFocusedWindow(ctx context.Context, url string, cf
656656 return fmt.Errorf("created tab did not become an active UI target")
657657 }
658658 case entity.ExternalLinkBehaviorSplit:
659- if a.wsCoord == nil {
660- return fmt.Errorf("workspace coordinator not available")
661- }
662- before := activeTab.Workspace.ActivePaneID
663- if err := a.wsCoord.SplitWithURL(ctx, externalLinkSplitDirection(cfg.Placement), url); err != nil {
664- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
665- return err
666- }
667- if err := verifyExternalPaneCreation(activeTab.Workspace, before, url); err != nil {
668- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
659+ if err := a.placeExternalURLInPane(ctx, activeTab.Workspace, url, func(ctx context.Context) error {
660+ return a.wsCoord.SplitWithURL(ctx, externalLinkSplitDirection(cfg.Placement), url)
661+ }); err != nil {
669662 return err
670663 }
671664 case entity.ExternalLinkBehaviorStacked:
672- if a.wsCoord == nil {
673- return fmt.Errorf("workspace coordinator not available")
674- }
675- before := activeTab.Workspace.ActivePaneID
676- if err := a.wsCoord.StackPaneWithURL(ctx, url); err != nil {
677- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
678- return err
679- }
680- if err := verifyExternalPaneCreation(activeTab.Workspace, before, url); err != nil {
681- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
665+ if err := a.placeExternalURLInPane(ctx, activeTab.Workspace, url, func(ctx context.Context) error {
666+ return a.wsCoord.StackPaneWithURL(ctx, url)
667+ }); err != nil {
682668 return err
683669 }
684670 default:
@@ -692,6 +678,22 @@ func (a *App) openExternalURLInFocusedWindow(ctx context.Context, url string, cf
692678 return nil
693679 }
694680
681+func (a *App) placeExternalURLInPane(ctx context.Context, ws *entity.Workspace, url string, place func(context.Context) error) error {
682+ if a.wsCoord == nil {
683+ return fmt.Errorf("workspace coordinator not available")
684+ }
685+ before := ws.ActivePaneID
686+ if err := place(ctx); err != nil {
687+ a.rollbackExternalPaneCreation(ctx, ws, before)
688+ return err
689+ }
690+ if err := verifyExternalPaneCreation(ws, before, url); err != nil {
691+ a.rollbackExternalPaneCreation(ctx, ws, before)
692+ return err
693+ }
694+ return nil
695+}
696+
695697 func (a *App) rollbackExternalPaneCreation(ctx context.Context, ws *entity.Workspace, previous entity.PaneID) {
696698 if a.panesUC == nil || ws == nil || ws.ActivePaneID == "" || ws.ActivePaneID == previous {
697699 return
@@ -616,15 +616,15 @@ func (a *App) openExternalURLOnMainThread(ctx context.Context, url string, cfg e
616 return a.openFreshWindow(ctx, url)616 return a.openFreshWindow(ctx, url)
617 }617 }
618 618
619- if err := a.openExternalURLInFocusedWindow(ctx, url, externalLinks); err == nil {619+ err := a.openExternalURLInFocusedWindow(ctx, url, externalLinks)
620+ if err == nil {
620 return nil621 return nil
621- } else {
622- logging.FromContext(ctx).Warn().
623- Err(err).
624- Str("url_host", logging.SafeURLHost(url)).
625- Str("behavior", string(externalLinks.Behavior)).
626- Msg("ui: external URL placement failed; opening fresh window")
627 }622 }
623+ logging.FromContext(ctx).Warn().
624+ Err(err).
625+ Str("url_host", logging.SafeURLHost(url)).
626+ Str("behavior", string(externalLinks.Behavior)).
627+ Msg("ui: external URL placement failed; opening fresh window")
628 return a.openFreshWindow(ctx, url)628 return a.openFreshWindow(ctx, url)
629 }629 }
630 630
@@ -656,29 +656,15 @@ func (a *App) openExternalURLInFocusedWindow(ctx context.Context, url string, cf
656 return fmt.Errorf("created tab did not become an active UI target")656 return fmt.Errorf("created tab did not become an active UI target")
657 }657 }
658 case entity.ExternalLinkBehaviorSplit:658 case entity.ExternalLinkBehaviorSplit:
659- if a.wsCoord == nil {659+ if err := a.placeExternalURLInPane(ctx, activeTab.Workspace, url, func(ctx context.Context) error {
660- return fmt.Errorf("workspace coordinator not available")660+ return a.wsCoord.SplitWithURL(ctx, externalLinkSplitDirection(cfg.Placement), url)
661- }661+ }); err != nil {
662- before := activeTab.Workspace.ActivePaneID
663- if err := a.wsCoord.SplitWithURL(ctx, externalLinkSplitDirection(cfg.Placement), url); err != nil {
664- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
665- return err
666- }
667- if err := verifyExternalPaneCreation(activeTab.Workspace, before, url); err != nil {
668- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
669 return err662 return err
670 }663 }
671 case entity.ExternalLinkBehaviorStacked:664 case entity.ExternalLinkBehaviorStacked:
672- if a.wsCoord == nil {665+ if err := a.placeExternalURLInPane(ctx, activeTab.Workspace, url, func(ctx context.Context) error {
673- return fmt.Errorf("workspace coordinator not available")666+ return a.wsCoord.StackPaneWithURL(ctx, url)
674- }667+ }); err != nil {
675- before := activeTab.Workspace.ActivePaneID
676- if err := a.wsCoord.StackPaneWithURL(ctx, url); err != nil {
677- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
678- return err
679- }
680- if err := verifyExternalPaneCreation(activeTab.Workspace, before, url); err != nil {
681- a.rollbackExternalPaneCreation(ctx, activeTab.Workspace, before)
682 return err668 return err
683 }669 }
684 default:670 default:
@@ -692,6 +678,22 @@ func (a *App) openExternalURLInFocusedWindow(ctx context.Context, url string, cf
692 return nil678 return nil
693 }679 }
694 680
681+func (a *App) placeExternalURLInPane(ctx context.Context, ws *entity.Workspace, url string, place func(context.Context) error) error {
682+ if a.wsCoord == nil {
683+ return fmt.Errorf("workspace coordinator not available")
684+ }
685+ before := ws.ActivePaneID
686+ if err := place(ctx); err != nil {
687+ a.rollbackExternalPaneCreation(ctx, ws, before)
688+ return err
689+ }
690+ if err := verifyExternalPaneCreation(ws, before, url); err != nil {
691+ a.rollbackExternalPaneCreation(ctx, ws, before)
692+ return err
693+ }
694+ return nil
695+}
696+
695 func (a *App) rollbackExternalPaneCreation(ctx context.Context, ws *entity.Workspace, previous entity.PaneID) {697 func (a *App) rollbackExternalPaneCreation(ctx context.Context, ws *entity.Workspace, previous entity.PaneID) {
696 if a.panesUC == nil || ws == nil || ws.ActivePaneID == "" || ws.ActivePaneID == previous {698 if a.panesUC == nil || ws == nil || ws.ActivePaneID == "" || ws.ActivePaneID == previous {
697 return699 return

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close