Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 40 additions & 30 deletions doc/diffview.txt
Original file line number Diff line number Diff line change
Expand Up @@ -809,14 +809,15 @@ view.x.layout *diffview-config-view.x.layout*
{diff1_raw}
Auto-selected (not directly settable via `view.x.layout`).

Selected per-file by |diffview-config-view.single_pane_for_one_sided|
Selected per-file by |diffview-config-view.one_sided_layout|
when the entry's diff is one-sided (added, untracked, or
deleted). A single window with no diff highlighting and no
diff folding; the buffer reads like a normal file. For
additions and untracked files the window holds the working-
tree file directly (editable and `:w`-able); for deletions
it holds the pre-deletion content from the index or commit
(read-only since the working-tree file is gone).
it holds the pre-deletion content from `revs.a` (read-only
against a commit, editable against the index — `:w` then
writes back to the index via the usual STAGE-0 path).

┌──────────────┐
│ │
Expand Down Expand Up @@ -915,33 +916,42 @@ view.foldlevel *diffview-config-view.foldlevel*
value (e.g. `99`) to keep all folds open, which can help when another
plugin's ftplugin creates unrelated folds in diff buffers.

*diffview-config-view.single_pane_for_one_sided*
view.single_pane_for_one_sided
Type: `boolean`, Default: `false`

When true, files whose diff is one-sided open in a single non-diff
window instead of a Diff2 layout with an empty pane. Specifically:

• Status A or ? (added / untracked): the b-side is opened
directly. When b is `LOCAL` (the usual case in diff views)
the window holds the working-tree file as an editable,
`:w`-able buffer; when b is a commit rev (the usual case in
file-history views) it is read-only.
• Status D (deleted): the pre-deletion content from the index or
commit is shown in a single read-only window.

Applies to both diff views and file history views. Has no effect for
renamed, modified, or merge-conflict files (those keep their Diff2 or
merge layout). Also no effect when the configured layout is already a
single-window layout (`diff1_plain`, `diff1_inline`) or when file
history's `pin_local` mode owns the right-hand window.

The auto-selected layout is `diff1_raw`. It is excluded from
|diffview-config-view.cycle_layouts|: cycling with |g<C-x>| moves
through your configured Diff2 layouts. The cycled-to layout
replaces the entry's layout in place, so re-selecting the same
file simply reuses it; re-applying `diff1_raw` requires a refresh
that rebuilds the entry (see |diffview-actions-refresh_files|).
*diffview-config-view.one_sided_layout*
view.one_sided_layout
Type: `"default"|"raw"`, Default: `"default"`

Layout used for files whose diff is one-sided (status `A`/`?`/`D`).

• `"default"`: keep the configured base layout. A Diff2 will
leave one pane empty; a `diff1_plain` keeps its diff-mode
chrome (foldcolumn, foldmethod=diff) on a buffer with no
comparison partner.
• `"raw"`: substitute `diff1_raw`. A single non-diff window:

• Status A or ? (added / untracked): the b-side is opened
directly. When b is `LOCAL` (the usual case in diff views)
the window holds the working-tree file as an editable,
`:w`-able buffer; when b is a commit rev (the usual case in
file-history views) it is read-only.
• Status D (deleted): the pre-deletion content from `revs.a`
is shown in a single window — read-only when the source is
a commit; editable when it's the index, with `:w` writing
back via the usual STAGE-0 path (same workflow as other
index buffers in diffview).

Applies to both diff views and file history views, and to
`diff1_plain` and Diff2 base layouts. Has no effect on
`diff1_inline` (which already renders one-sided content coherently
as all-added or all-deleted virt_lines), on renamed/modified files
or merge conflicts, or when file history's `pin_local` mode owns
the right-hand window.

`diff1_raw` is excluded from |diffview-config-view.cycle_layouts|:
cycling with |g<C-x>| moves through your configured base layouts.
The cycled-to layout replaces the entry's layout in place, so
re-selecting the same file simply reuses it; re-applying
`diff1_raw` requires a refresh that rebuilds the entry (see
|diffview-actions-refresh_files|).

view.cycle_layouts *diffview-config-view.cycle_layouts*
Type: `table`, Default: (see defaults)
Expand Down
6 changes: 3 additions & 3 deletions doc/diffview_defaults.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ DEFAULT CONFIG *diffview.defaults*
pin_local = false, -- See |diffview-config-view.file_history.pin_local|
},
foldlevel = 0, -- See |diffview-config-view.foldlevel|
-- See |diffview-config-view.single_pane_for_one_sided|. When true,
-- See |diffview-config-view.one_sided_layout|. When set to "raw",
-- one-sided diffs (status A/?/D) open in a single non-diff window
-- instead of a Diff2 layout with an empty pane.
single_pane_for_one_sided = false,
-- (diff1_raw) instead of the configured Diff2 or diff1_plain layout.
one_sided_layout = "default",
-- Layouts to cycle through with `cycle_layout` action. Each view's
-- configured layout (e.g. view.default.layout) is automatically
-- appended to its cycle if missing, so cycling always returns to it.
Expand Down
35 changes: 26 additions & 9 deletions lua/diffview/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ end
---@alias DiffviewStandardLayout "diff1_plain"|"diff1_inline"|"diff2_horizontal"|"diff2_vertical"
---@alias DiffviewMergeLayout "diff1_plain"|"diff3_horizontal"|"diff3_vertical"|"diff3_mixed"|"diff4_mixed"
---@alias DiffviewInferredLayout -1
---@alias DiffviewOneSidedLayout "default"|"raw"

-- Targets consumed by action factories in `actions.lua` (referenced from keymaps).
---@alias DiffviewConflictTarget "ours"|"theirs"|"base"|"all"|"none"
Expand Down Expand Up @@ -289,7 +290,7 @@ M.defaults = {
---@field merge_tool DiffviewMergeViewTypeConfig
---@field file_history DiffviewStandardViewTypeConfig
---@field foldlevel integer
---@field single_pane_for_one_sided boolean
---@field one_sided_layout DiffviewOneSidedLayout
---@field cycle_layouts DiffviewCycleLayouts
---@field inline DiffviewInlineConfig

Expand All @@ -298,7 +299,7 @@ M.defaults = {
---@field merge_tool? DiffviewMergeViewTypeConfig.user Config for conflicted files in diff views during a merge or rebase.
---@field file_history? DiffviewStandardViewTypeConfig.user Config for changed files in file history views.
---@field foldlevel? integer See `|diffview-config-view.foldlevel|`.
---@field single_pane_for_one_sided? boolean When true, files whose diff is one-sided (status `A`/`?` or `D`) open in a single non-diff window instead of a Diff2 layout with an empty pane. `A`/`?` shows the b-side directly: editable working-tree buffer when b is `LOCAL` (the usual case in diff views), read-only when b is a commit rev (the usual case in file-history views). `D` shows the pre-deletion content from the index/commit in a read-only window. Applies to both diff views and file history views. Has no effect for renames, modifications, or merge conflicts. See `|diffview-config-view.single_pane_for_one_sided|`.
---@field one_sided_layout? DiffviewOneSidedLayout Layout used for files whose diff is one-sided (status `A`/`?` or `D`). `"default"` keeps the configured layout (a Diff2 leaves an empty pane; a `diff1_plain` keeps its diff-mode chrome). `"raw"` substitutes `diff1_raw`: a single non-diff window where `A`/`?` shows the b-side directly (editable working-tree buffer when b is `LOCAL`, read-only when b is a commit rev) and `D` shows the pre-deletion content from `revs.a` (read-only when that's a commit; editable when it's the index, with `:w` writing back via the usual STAGE-0 path). Applies to both diff views and file history views, and to `diff1_plain` and Diff2 base layouts. Has no effect on `diff1_inline` (which already renders one-sided content coherently), on renames, modifications, merge conflicts, or when file history's `pin_local` mode owns the right-hand window. See `|diffview-config-view.one_sided_layout|`.
---@field cycle_layouts? DiffviewCycleLayouts.user Layouts to cycle through with `cycle_layout`.
---@field inline? DiffviewInlineConfig.user Options that apply to the `diff1_inline` layout.
view = {
Expand Down Expand Up @@ -351,13 +352,16 @@ M.defaults = {
-- Initial 'foldlevel' for diff buffers. Default 0 collapses unchanged
-- regions; set to a high value (e.g. 99) to keep all folds open.
foldlevel = 0,
-- When true, files whose diff is one-sided (added, untracked, or
-- deleted) open in a single non-diff window. Working-tree additions
-- and untracked files open as editable buffers backed by the file on
-- disk; deletions show the pre-deletion content from the index or
-- commit in a single read-only window. Has no effect for renames,
-- modifications, or merge conflicts.
single_pane_for_one_sided = false,
-- Layout used for files whose diff is one-sided (added, untracked, or
-- deleted). `"default"` keeps the configured layout. `"raw"` substitutes
-- `diff1_raw`: a single non-diff window where additions and untracked
-- files open as editable buffers backed by the file on disk, and
-- deletions show the pre-deletion content from `revs.a` (read-only
-- against a commit; editable against the index, with `:w` writing
-- back via the usual STAGE-0 path). Applies to `diff1_plain` and
-- Diff2 base layouts; `diff1_inline` (which renders one-sided content
-- as all-added or all-deleted virt_lines) is left alone.
one_sided_layout = "default",

---@class DiffviewCycleLayouts
---@field default DiffviewStandardLayout[]
Expand Down Expand Up @@ -1254,6 +1258,19 @@ function M.setup(user_config)
end
end

local valid_one_sided_layouts = { "default", "raw" }
if view.one_sided_layout == nil then
view.one_sided_layout = M.defaults.view.one_sided_layout
elseif not vim.tbl_contains(valid_one_sided_layouts, view.one_sided_layout) then
utils.err(
("Invalid value '%s' for 'view.one_sided_layout'! Must be one of (%s)."):format(
view.one_sided_layout,
fmt_enum(valid_one_sided_layouts)
)
)
view.one_sided_layout = M.defaults.view.one_sided_layout
end
Comment thread
dlyongemallo marked this conversation as resolved.

-- Validate `view.inline`. A nil style (e.g. user passed `view.inline = {}`)
-- silently falls back to the default; only an explicit invalid value errors.
-- Reject non-table values (mirrors the `view.cycle_layouts` guard above).
Expand Down
28 changes: 20 additions & 8 deletions lua/diffview/scene/file_entry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local lazy = require("diffview.lazy")
local oop = require("diffview.oop")

local Diff1 = lazy.access("diffview.scene.layouts.diff_1", "Diff1") ---@type Diff1|LazyModule
local Diff1Inline = lazy.access("diffview.scene.layouts.diff_1_inline", "Diff1Inline") ---@type Diff1Inline|LazyModule
local Diff1Raw = lazy.access("diffview.scene.layouts.diff_1_raw", "Diff1Raw") ---@type Diff1Raw|LazyModule
local Diff2 = lazy.access("diffview.scene.layouts.diff_2", "Diff2") ---@type Diff2|LazyModule
local File = lazy.access("diffview.vcs.file", "File") ---@type vcs.File|LazyModule
Expand Down Expand Up @@ -299,16 +300,18 @@ local function class_descends_from(cls, target)
end

---Pick the effective layout class for an entry. Substitutes `Diff1Raw` for a
---Diff2 when `view.single_pane_for_one_sided` is on and the file's diff is
---one-sided (status `A`/`?`/`D`). Falls through (returns the input class)
---when the precondition isn't met, leaving every other layout path untouched.
---Bails out for pinned-b mode (the view owns the b-side) and for non-Diff2
---inputs (merge layouts, user-set `diff1_*` layouts, etc.).
---Diff1 or Diff2 base when `view.one_sided_layout` is `"raw"` and the file's
---diff is one-sided (status `A`/`?`/`D`). Falls through (returns the input
---class) when the precondition isn't met, leaving every other layout path
---untouched. Bails out for pinned-b mode (the view owns the b-side), for
---`diff1_inline` (which already renders one-sided content coherently as
---all-added or all-deleted virt_lines), for `diff1_raw` itself (no-op), and
---for merge layouts (Diff3/Diff4).
---@param default_class Layout (class)
---@param opt FileEntry.with_layout.Opt
---@return Layout (class)
local function select_layout_for_status(default_class, opt)
if not config.get_config().view.single_pane_for_one_sided then
if config.get_config().view.one_sided_layout ~= "raw" then
return default_class
end
if opt.pinned_b_file then
Expand All @@ -317,10 +320,19 @@ local function select_layout_for_status(default_class, opt)
if not vim.tbl_contains({ "A", "?", "D" }, opt.status) then
return default_class
end
if not class_descends_from(default_class, Diff2.__get()) then
if
class_descends_from(default_class, Diff1Inline.__get())
or class_descends_from(default_class, Diff1Raw.__get())
then
return default_class
end
return Diff1Raw.__get()
if
class_descends_from(default_class, Diff1.__get())
or class_descends_from(default_class, Diff2.__get())
then
return Diff1Raw.__get()
end
return default_class
end

---@param layout_class Layout (class)
Expand Down
2 changes: 1 addition & 1 deletion lua/diffview/scene/layouts/diff_1_raw.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ local oop = require("diffview.oop")
local M = {}

---Single-window layout that shows the file as a plain (non-diff) buffer. Used
---by `view.single_pane_for_one_sided` when a file's diff would be one-sided
---by `view.one_sided_layout = "raw"` when a file's diff would be one-sided
---(status `A`/`?` or `D`). Window opts disabling `diff`, `scrollbind`, and
---diff folding are merged in by `StandardView` via the `diff1_raw` winopts
---key; the layout itself just wires the single `b` window.
Expand Down
Loading
Loading