Skip to content

session_diff: keymap config cannot bind file navigation or custom functions #510

Closed

Description

@wzj-zz

The keymap.session_diff scopes (list, preview, messages, help) are resolved in map_actions() (lua/opencode/ui/session_diff.lua) against a hardcoded table of 13 action names. This makes two common requests impossible through official configuration:

  1. Cycling files from inside the diff view. M.select(direction) already implements next/prev-file navigation (it backs the global diff_next/diff_prev editor keymaps), but it is not registered in the map_actions table, so there is no way to bind buffer-local keys like <Tab>/<S-Tab> for it inside the diff view. Global keys are not an option here - <Tab> must stay buffer-local, otherwise it hijacks <C-i>/the jump list in every buffer.

  2. Plain window-focus keys. Users coming from diffview/codediff are used to a key that moves focus from the file list to the diff pane (e.g. <M-e> -> 2wincmd l). This is a generic Vim operation, not plugin logic, so it can never be one of the 13 named actions.

User-side workarounds don't work either (compare codediff.nvim, which fires User CodeDiffOpen and sets dedicated filetypes like codediff-explorer): the session-diff view emits no User autocmd on open, the list buffer has filetype = '', and the preview buffers reuse the diffed file's real filetype - so a FileType autocmd cannot identify them safely.

Suggested fixes (any one unblocks the use cases):

  1. Register the existing navigation in map_actions:
    next_file = function() M.select(1) end,
    prev_file = function() M.select(-1) end,
  2. Allow function values as the rhs in keymap.session_diff entries (like diffview.nvim does), keeping the action-table lookup for strings.
  3. Optionally: set a dedicated filetype on the session-diff buffers and/or fire a User OpencodeSessionDiffOpen autocmd on open, so user configs can attach extra buffer-local mappings themselves.

Option 1 fixes file cycling; option 2 fixes everything generically.

Activity

  1. wzj-zz commented on Oct 3, 2026

    @wzj-zz
    ContributorAuthor

    Additional finding: the list window is missing wrap = false, causing j/k to need multiple presses per file entry.

    M.open() sets number, signcolumn, cursorline, winfixwidth, etc. on the list window, but not wrap = false - while the help float (session_diff.lua:225) and the turn float (session_diff.lua:242) both set it explicitly. So the list window looks like an oversight.

    The resulting chain:

    1. The list window is narrow (24-42 columns), and file entries ("status + icon + path + diffstats") easily exceed its width.
    2. With the user's global wrap = true, entries wrap onto multiple screen lines.
    3. With the common j/k -> gj/gk mapping (e.g. AstroNvim's default j -> v:count == 0 ? 'gj' : 'j', which moves by screen line without a count), passing one wrapped file entry takes several j/k presses.

    One-line fix: vim.wo[list_win].wrap = false.

  2. sudo-tee commented on Oct 6, 2026

    @sudo-tee
    Owner

    Thanks for the comments.

    For the keymaps, since there is a lot of them I might change them to the same mechanism as the panel, it would be more flexible this way.

    For the wrap you are right I will fix it.

    These are valuable comments as the diff view was added lately to replace the custom snapshots in v1 that are not available anymore, but it's still barebones it need more refining.

  3. sudo-tee commented on Oct 7, 2026

    @sudo-tee
    Owner

    @wzj-zz

    I did some improvements to the sdession diff from your comments

    #515

    can you let me know if it works for you ?

  4. wzj-zz commented on Oct 7, 2026

    @wzj-zz
    ContributorAuthor

    Tested #515 on Windows 11 / Neovim 0.11 — everything works:

    • wrap fix: one \j/\k\ press per file entry in the list now, even with global \wrap\ on
    • file cycling: <Tab>/<S-Tab>\ works from both the list and the diff panes
    • **\gf**: opens the working-tree file in a new tab, lands on the right post-change line (including from a deleted line on the before side)
    • custom callbacks: the new mechanism covers my real use case — I mirrored my codediff pane-focus keys with plain callbacks:

    \\lua
    keymap = {
    session_diff = {
    list = { [''] = { function() vim.cmd('2wincmd l') end, desc = 'Focus diff pane' } },
    preview = { ['e'] = { function() vim.cmd('99wincmd h') end, desc = 'Focus file list' } },
    },
    }
    \\

    • My pre-existing <M-q>\ close bindings in all scopes still work alongside the new defaults

    Also ran the full unit suite against this branch vs. main: identical failure sets (15 pre-existing Windows env failures), no regressions, and all the new session_diff/keymap tests pass.

    LGTM — thanks for the quick turnaround!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions