detached bf8765a

fix(history): keep sidebar open on navigation

commit
bf8765ac687916051c2cf55cc33bd05151ba248e
author
brice · · 2026-06-13 06:16 UTC
parent
0109d89
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +63 -35
M docs/reference/keybindings.md +1 -1
@@ -96,7 +96,7 @@ These work outside modal modes:
9696
9797 - `Alt+F` is the only floating-pane shortcut enabled by default.
9898 - `Alt+F` toggles floating visibility and keeps floating pane state intact.
99-- `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate and close sidebar, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`.
99+- `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate while keeping the sidebar open, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`.
100100 - `Ctrl+W` closes the active pane; when the floating pane is active, it fully releases that floating session.
101101 - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`.
102102 - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
@@ -96,7 +96,7 @@ These work outside modal modes:
96 96
97 - `Alt+F` is the only floating-pane shortcut enabled by default.97 - `Alt+F` is the only floating-pane shortcut enabled by default.
98 - `Alt+F` toggles floating visibility and keeps floating pane state intact.98 - `Alt+F` toggles floating visibility and keeps floating pane state intact.
99-- `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate and close sidebar, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`.99+- `Ctrl+H` toggles the native GTK history sidebar. The sidebar shows browsing history grouped by day with search/filter, keyboard navigation (arrows, Home/End, Ctrl+arrows for day jumps), and activation modes (Enter to navigate while keeping the sidebar open, Ctrl+Enter to navigate while keeping the sidebar open, Shift+Enter to open in a new split). If the native sidebar is unavailable, the shortcut returns an error instead of falling back to `dumb://history`.
100 - `Ctrl+W` closes the active pane; when the floating pane is active, it fully releases that floating session.100 - `Ctrl+W` closes the active pane; when the floating pane is active, it fully releases that floating session.
101 - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`.101 - Any URL shortcut (for example `Alt+G`) must be defined explicitly in `workspace.floating_pane.profiles`.
102 - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).102 - Floating profile shortcuts support modifier combos with `ctrl`, `shift`, and `alt` (for example `ctrl+shift+y` or `ctrl+alt+m`).
M internal/ui/browser_window.go +6 -10
@@ -309,18 +309,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow)
309309 return component.HistorySidebarConfig{
310310 HistoryUC: historyUC,
311311 OnNavigate: func(navCtx context.Context, url string) error {
312- if err := a.navigateFromBrowserWindow(navCtx, bw, url); err != nil {
313- return err
314- }
315- cb := glib.SourceFunc(func(_ uintptr) bool {
316- a.hideAndRestoreFocusForBrowserWindow(bw)
317- return false
318- })
319- glib.IdleAdd(&cb, 0)
320- return nil
312+ return a.navigateHistorySidebarSelection(navCtx, bw, url)
321313 },
322314 OnNavigateKeepOpen: func(navCtx context.Context, url string) error {
323- return a.navigateFromBrowserWindow(navCtx, bw, url)
315+ return a.navigateHistorySidebarSelection(navCtx, bw, url)
324316 },
325317 OnOpenInNewPane: func(splitCtx context.Context, url string) error {
326318 if a.wsCoord == nil {
@@ -335,6 +327,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow)
335327 }
336328 }
337329
330+func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWindow, url string) error {
331+ return a.navigateFromBrowserWindow(ctx, bw, url)
332+}
333+
338334 // toggleHistorySidebar toggles sidebar visibility. An optional width config
339335 // can be provided and is applied when showing the sidebar.
340336 func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) {
@@ -309,18 +309,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow)
309 return component.HistorySidebarConfig{309 return component.HistorySidebarConfig{
310 HistoryUC: historyUC,310 HistoryUC: historyUC,
311 OnNavigate: func(navCtx context.Context, url string) error {311 OnNavigate: func(navCtx context.Context, url string) error {
312- if err := a.navigateFromBrowserWindow(navCtx, bw, url); err != nil {312+ return a.navigateHistorySidebarSelection(navCtx, bw, url)
313- return err
314- }
315- cb := glib.SourceFunc(func(_ uintptr) bool {
316- a.hideAndRestoreFocusForBrowserWindow(bw)
317- return false
318- })
319- glib.IdleAdd(&cb, 0)
320- return nil
321 },313 },
322 OnNavigateKeepOpen: func(navCtx context.Context, url string) error {314 OnNavigateKeepOpen: func(navCtx context.Context, url string) error {
323- return a.navigateFromBrowserWindow(navCtx, bw, url)315+ return a.navigateHistorySidebarSelection(navCtx, bw, url)
324 },316 },
325 OnOpenInNewPane: func(splitCtx context.Context, url string) error {317 OnOpenInNewPane: func(splitCtx context.Context, url string) error {
326 if a.wsCoord == nil {318 if a.wsCoord == nil {
@@ -335,6 +327,10 @@ func (a *App) buildHistorySidebarConfig(ctx context.Context, bw *browserWindow)
335 }327 }
336 }328 }
337 329
330+func (a *App) navigateHistorySidebarSelection(ctx context.Context, bw *browserWindow, url string) error {
331+ return a.navigateFromBrowserWindow(ctx, bw, url)
332+}
333+
338 // toggleHistorySidebar toggles sidebar visibility. An optional width config334 // toggleHistorySidebar toggles sidebar visibility. An optional width config
339 // can be provided and is applied when showing the sidebar.335 // can be provided and is applied when showing the sidebar.
340 func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) {336 func (bw *browserWindow) toggleHistorySidebar(widthCfg ...window.SidebarWidthConfig) {
M internal/ui/browser_window_test.go +48 -1
@@ -1411,7 +1411,13 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) {
14111411 bwTabs.Add(tab)
14121412 bwTabs.SetActive(tab.ID)
14131413
1414- bw := &browserWindow{id: "window-1", tabs: bwTabs}
1414+ bw := &browserWindow{
1415+ id: "window-1",
1416+ tabs: bwTabs,
1417+ mainWindow: &window.MainWindow{},
1418+ historySidebar: &component.HistorySidebar{},
1419+ sidebarVisible: true,
1420+ }
14151421
14161422 fakeWv := &fakeRecordingWebView{id: 1}
14171423 contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil)
@@ -1441,6 +1447,47 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) {
14411447 // Verify the navigation reached the correct webview.
14421448 assert.True(t, fakeWv.loadURICalled, "webview must receive navigation")
14431449 assert.Equal(t, navigateURL, fakeWv.loadURILastURI)
1450+ assert.True(t, bw.sidebarVisible, "default history navigation should keep the sidebar open")
1451+}
1452+
1453+func TestApp_NavigateHistorySidebarSelection_KeepsSidebarVisible(t *testing.T) {
1454+ ctx := context.Background()
1455+
1456+ paneID := entity.PaneID("pane-1")
1457+ tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(paneID))
1458+ bwTabs := entity.NewTabList()
1459+ bwTabs.Add(tab)
1460+ bwTabs.SetActive(tab.ID)
1461+
1462+ bw := &browserWindow{
1463+ id: "window-1",
1464+ tabs: bwTabs,
1465+ mainWindow: &window.MainWindow{},
1466+ historySidebar: &component.HistorySidebar{},
1467+ sidebarVisible: true,
1468+ }
1469+
1470+ fakeWv := &fakeRecordingWebView{id: 1}
1471+ contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil)
1472+ contentCoord.RegisterPopupWebView(paneID, fakeWv)
1473+ navCoord := coordinator.NewNavigationCoordinator(ctx, nil, contentCoord)
1474+
1475+ app := &App{
1476+ tabs: entity.NewTabList(),
1477+ browserWindows: map[string]*browserWindow{bw.id: bw},
1478+ contentCoord: contentCoord,
1479+ navCoord: navCoord,
1480+ workspaceViews: map[entity.TabID]*component.WorkspaceView{
1481+ tab.ID: {},
1482+ },
1483+ }
1484+ app.tabs.Add(tab)
1485+
1486+ err := app.navigateHistorySidebarSelection(ctx, bw, "https://open.com")
1487+ require.NoError(t, err)
1488+ assert.True(t, fakeWv.loadURICalled)
1489+ assert.Equal(t, "https://open.com", fakeWv.loadURILastURI)
1490+ assert.True(t, bw.sidebarVisible, "history selection navigation should not hide the sidebar")
14441491 }
14451492
14461493 // TestApp_HistorySidebarConfig_NavigateCallbackOwnership verifies that
@@ -1411,7 +1411,13 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) {
1411 bwTabs.Add(tab)1411 bwTabs.Add(tab)
1412 bwTabs.SetActive(tab.ID)1412 bwTabs.SetActive(tab.ID)
1413 1413
1414- bw := &browserWindow{id: "window-1", tabs: bwTabs}1414+ bw := &browserWindow{
1415+ id: "window-1",
1416+ tabs: bwTabs,
1417+ mainWindow: &window.MainWindow{},
1418+ historySidebar: &component.HistorySidebar{},
1419+ sidebarVisible: true,
1420+ }
1415 1421
1416 fakeWv := &fakeRecordingWebView{id: 1}1422 fakeWv := &fakeRecordingWebView{id: 1}
1417 contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil)1423 contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil)
@@ -1441,6 +1447,47 @@ func TestApp_HistorySidebarConfig_NavigateCallbackNavigates(t *testing.T) {
1441 // Verify the navigation reached the correct webview.1447 // Verify the navigation reached the correct webview.
1442 assert.True(t, fakeWv.loadURICalled, "webview must receive navigation")1448 assert.True(t, fakeWv.loadURICalled, "webview must receive navigation")
1443 assert.Equal(t, navigateURL, fakeWv.loadURILastURI)1449 assert.Equal(t, navigateURL, fakeWv.loadURILastURI)
1450+ assert.True(t, bw.sidebarVisible, "default history navigation should keep the sidebar open")
1451+}
1452+
1453+func TestApp_NavigateHistorySidebarSelection_KeepsSidebarVisible(t *testing.T) {
1454+ ctx := context.Background()
1455+
1456+ paneID := entity.PaneID("pane-1")
1457+ tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(paneID))
1458+ bwTabs := entity.NewTabList()
1459+ bwTabs.Add(tab)
1460+ bwTabs.SetActive(tab.ID)
1461+
1462+ bw := &browserWindow{
1463+ id: "window-1",
1464+ tabs: bwTabs,
1465+ mainWindow: &window.MainWindow{},
1466+ historySidebar: &component.HistorySidebar{},
1467+ sidebarVisible: true,
1468+ }
1469+
1470+ fakeWv := &fakeRecordingWebView{id: 1}
1471+ contentCoord := contentcoord.NewCoordinator(ctx, nil, nil, nil, nil, nil, nil, nil)
1472+ contentCoord.RegisterPopupWebView(paneID, fakeWv)
1473+ navCoord := coordinator.NewNavigationCoordinator(ctx, nil, contentCoord)
1474+
1475+ app := &App{
1476+ tabs: entity.NewTabList(),
1477+ browserWindows: map[string]*browserWindow{bw.id: bw},
1478+ contentCoord: contentCoord,
1479+ navCoord: navCoord,
1480+ workspaceViews: map[entity.TabID]*component.WorkspaceView{
1481+ tab.ID: {},
1482+ },
1483+ }
1484+ app.tabs.Add(tab)
1485+
1486+ err := app.navigateHistorySidebarSelection(ctx, bw, "https://open.com")
1487+ require.NoError(t, err)
1488+ assert.True(t, fakeWv.loadURICalled)
1489+ assert.Equal(t, "https://open.com", fakeWv.loadURILastURI)
1490+ assert.True(t, bw.sidebarVisible, "history selection navigation should not hide the sidebar")
1444 }1491 }
1445 1492
1446 // TestApp_HistorySidebarConfig_NavigateCallbackOwnership verifies that1493 // TestApp_HistorySidebarConfig_NavigateCallbackOwnership verifies that
M internal/ui/component/history_sidebar.go +8 -23
@@ -31,7 +31,8 @@ const (
3131 type HistorySidebarKeyboardAction int
3232
3333 const (
34- // SidebarActionCloseOnActivate is the default: navigate and close the sidebar.
34+ // SidebarActionCloseOnActivate is the default activation path.
35+ // The host currently keeps the sidebar visible for default activation.
3536 SidebarActionCloseOnActivate HistorySidebarKeyboardAction = iota
3637 // SidebarActionKeepOpenOnActivate navigates but leaves the sidebar visible.
3738 SidebarActionKeepOpenOnActivate
@@ -104,7 +105,7 @@ type HistorySidebarConfig struct {
104105 HistoryUC *usecase.SearchHistoryUseCase
105106
106107 // OnNavigate is called when the user activates a history entry.
107- // The default Enter / click behavior closes the sidebar after navigating.
108+ // The default Enter / click behavior keeps the sidebar open after navigating.
108109 OnNavigate func(ctx context.Context, url string) error
109110
110111 // OnOpenInNewPane is called when Shift+Enter activates a URL.
@@ -153,7 +154,8 @@ func NewHistorySidebar(ctx context.Context, cfg HistorySidebarConfig) *HistorySi
153154 log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open navigate failed")
154155 }
155156 } else if cfg.OnNavigate != nil {
156- // Fall back to default navigate (which may close the sidebar)
157+ // Fall back to the default navigate path when no dedicated
158+ // keep-open callback is configured.
157159 if err := cfg.OnNavigate(callCtx, url); err != nil {
158160 log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open fallback failed")
159161 }
@@ -1143,7 +1145,7 @@ func (hs *HistorySidebar) handleEnterKey(keyval uint, state gdk.ModifierType) bo
11431145 // Shift+Enter: navigate in new pane
11441146 action = SidebarActionNewPaneOnActivate
11451147 default:
1146- // Plain Enter: navigate and close sidebar (via onRowActivated or direct)
1148+ // Plain Enter: navigate using the default activation behavior.
11471149 action = SidebarActionCloseOnActivate
11481150 }
11491151
@@ -1578,29 +1580,12 @@ func (hs *HistorySidebar) navigateWithoutClosing(url string) {
15781580 if hs.onNavigateKeepOpen == nil || url == "" {
15791581 return
15801582 }
1581-
1582- // Wrap onURL so that the configured OnNavigate callback is called
1583- // but we do NOT trigger the auto-close path (which is in OnNavigate).
1584- // Since OnNavigate already controls closing, we call the raw onURL
1585- // closure which calls OnNavigate directly — the OnNavigate callback
1586- // in browser_window.go has the auto-hide logic. We override that by
1587- // scheduling the navigation on idle but NOT scheduling a hide.
1588- //
1589- // The cleanest approach: call the raw onURL (which calls OnNavigate)
1590- // and let the caller decide about sidebar visibility.
1591- // OnNavigate in the browser window already hides; for keep-open we
1592- // need a different path.
1593- //
1594- // Solution: call a deferred navigate that does NOT include the
1595- // auto-close. We use a dedicated idle callback that navigates
1596- // without scheduling hide.
15971583 hs.doNavigateWithoutClose(url)
15981584 }
15991585
16001586 // doNavigateWithoutClose schedules navigation without closing the sidebar.
1601-// Uses the dedicated OnNavigateKeepOpen path so the host never hides
1602-// the sidebar. When OnNavigateKeepOpen is not configured, falls back
1603-// to the normal onURL path (which may close the sidebar).
1587+// Uses the dedicated OnNavigateKeepOpen path so hosts can override the
1588+// default activation behavior when they need a distinct keep-open action.
16041589 func (hs *HistorySidebar) doNavigateWithoutClose(url string) {
16051590 navigateCb := glib.SourceFunc(func(uintptr) bool {
16061591 hs.onNavigateKeepOpen(hs.ctx, url)
@@ -31,7 +31,8 @@ const (
31 type HistorySidebarKeyboardAction int31 type HistorySidebarKeyboardAction int
32 32
33 const (33 const (
34- // SidebarActionCloseOnActivate is the default: navigate and close the sidebar.34+ // SidebarActionCloseOnActivate is the default activation path.
35+ // The host currently keeps the sidebar visible for default activation.
35 SidebarActionCloseOnActivate HistorySidebarKeyboardAction = iota36 SidebarActionCloseOnActivate HistorySidebarKeyboardAction = iota
36 // SidebarActionKeepOpenOnActivate navigates but leaves the sidebar visible.37 // SidebarActionKeepOpenOnActivate navigates but leaves the sidebar visible.
37 SidebarActionKeepOpenOnActivate38 SidebarActionKeepOpenOnActivate
@@ -104,7 +105,7 @@ type HistorySidebarConfig struct {
104 HistoryUC *usecase.SearchHistoryUseCase105 HistoryUC *usecase.SearchHistoryUseCase
105 106
106 // OnNavigate is called when the user activates a history entry.107 // OnNavigate is called when the user activates a history entry.
107- // The default Enter / click behavior closes the sidebar after navigating.108+ // The default Enter / click behavior keeps the sidebar open after navigating.
108 OnNavigate func(ctx context.Context, url string) error109 OnNavigate func(ctx context.Context, url string) error
109 110
110 // OnOpenInNewPane is called when Shift+Enter activates a URL.111 // OnOpenInNewPane is called when Shift+Enter activates a URL.
@@ -153,7 +154,8 @@ func NewHistorySidebar(ctx context.Context, cfg HistorySidebarConfig) *HistorySi
153 log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open navigate failed")154 log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open navigate failed")
154 }155 }
155 } else if cfg.OnNavigate != nil {156 } else if cfg.OnNavigate != nil {
156- // Fall back to default navigate (which may close the sidebar)157+ // Fall back to the default navigate path when no dedicated
158+ // keep-open callback is configured.
157 if err := cfg.OnNavigate(callCtx, url); err != nil {159 if err := cfg.OnNavigate(callCtx, url); err != nil {
158 log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open fallback failed")160 log.Error().Err(err).Str("url", url).Msg("history sidebar keep-open fallback failed")
159 }161 }
@@ -1143,7 +1145,7 @@ func (hs *HistorySidebar) handleEnterKey(keyval uint, state gdk.ModifierType) bo
1143 // Shift+Enter: navigate in new pane1145 // Shift+Enter: navigate in new pane
1144 action = SidebarActionNewPaneOnActivate1146 action = SidebarActionNewPaneOnActivate
1145 default:1147 default:
1146- // Plain Enter: navigate and close sidebar (via onRowActivated or direct)1148+ // Plain Enter: navigate using the default activation behavior.
1147 action = SidebarActionCloseOnActivate1149 action = SidebarActionCloseOnActivate
1148 }1150 }
1149 1151
@@ -1578,29 +1580,12 @@ func (hs *HistorySidebar) navigateWithoutClosing(url string) {
1578 if hs.onNavigateKeepOpen == nil || url == "" {1580 if hs.onNavigateKeepOpen == nil || url == "" {
1579 return1581 return
1580 }1582 }
1581-
1582- // Wrap onURL so that the configured OnNavigate callback is called
1583- // but we do NOT trigger the auto-close path (which is in OnNavigate).
1584- // Since OnNavigate already controls closing, we call the raw onURL
1585- // closure which calls OnNavigate directly — the OnNavigate callback
1586- // in browser_window.go has the auto-hide logic. We override that by
1587- // scheduling the navigation on idle but NOT scheduling a hide.
1588- //
1589- // The cleanest approach: call the raw onURL (which calls OnNavigate)
1590- // and let the caller decide about sidebar visibility.
1591- // OnNavigate in the browser window already hides; for keep-open we
1592- // need a different path.
1593- //
1594- // Solution: call a deferred navigate that does NOT include the
1595- // auto-close. We use a dedicated idle callback that navigates
1596- // without scheduling hide.
1597 hs.doNavigateWithoutClose(url)1583 hs.doNavigateWithoutClose(url)
1598 }1584 }
1599 1585
1600 // doNavigateWithoutClose schedules navigation without closing the sidebar.1586 // doNavigateWithoutClose schedules navigation without closing the sidebar.
1601-// Uses the dedicated OnNavigateKeepOpen path so the host never hides1587+// Uses the dedicated OnNavigateKeepOpen path so hosts can override the
1602-// the sidebar. When OnNavigateKeepOpen is not configured, falls back1588+// default activation behavior when they need a distinct keep-open action.
1603-// to the normal onURL path (which may close the sidebar).
1604 func (hs *HistorySidebar) doNavigateWithoutClose(url string) {1589 func (hs *HistorySidebar) doNavigateWithoutClose(url string) {
1605 navigateCb := glib.SourceFunc(func(uintptr) bool {1590 navigateCb := glib.SourceFunc(func(uintptr) bool {
1606 hs.onNavigateKeepOpen(hs.ctx, url)1591 hs.onNavigateKeepOpen(hs.ctx, url)

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