Skip to content
Closed
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ has a `<Plug>(changeset-…)` map named after its words, and these default keys:
| `<C-g>bt` | `base tag` | Pick a tag to compare against |
| `<C-g>bc` | `base commit` | Pick a commit to compare against |
| `<C-g>br` | `base reset` | Compare against the base changeset guesses again |
| `<C-g>d` | `diff` | Turn the unified diff on or off |
| `<C-g>d` | `diff` | Turn the diff on or off |
| `<C-g>D` | `diff split` | Switch the diff between unified and side by side |
| `<C-g>r` | `refresh` | Rebuild the tree |
| `<C-g>nn` / `<C-g>np` | `next` / `prev` | Open the next / previous change |
| `<C-g>ns` / `<C-g>nS` | `next symbol` / `prev symbol` | Open the next / previous changed symbol |
Expand Down Expand Up @@ -197,7 +198,9 @@ Both need gitsigns.
base as the gutter. Closing the sidebar turns it off, or, as `unified_diff.keep` says, keeps
it on for the files you entered (opened from the sidebar, a step or the picker) or for all
of them. `<C-g>d` turns it off or on at any time, and `:Gitsigns diffthis unified=true`
closes one file's view until it next turns on.
closes one file's view until it next turns on. `<C-g>D` switches it side by side, the window
you are reading beside the file's base, or back; `unified_diff.view` picks the view a session
starts in.

## Configuration

Expand Down Expand Up @@ -249,6 +252,8 @@ require("changeset").setup({
unified_diff = {
-- Once the sidebar closes, keep the diff on "none", the "entered" files or "all".
keep = "none",
-- Draw the diff "unified" inline, or "split" side by side beside the window you read.
view = "unified",
},
})
```
Expand Down
64 changes: 58 additions & 6 deletions doc/agents/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,57 @@ stay directly under their line, the deleted lines after them. A parked block's r
counted from the head of the filler above the next line rather than its end, since the
deleted lines over the next line sit between the stack and that line.

### Side by side

The side-by-side diff is the same reconciler drawing another way. Each window still decides
whether a diff belongs there, through the same opening, closing, reach and viewed rules; it
only asks, too, which view it wants, and reopens when that changes, as it reopens on a base
that moved. A window opened in one view closes through that view's side.

Vim's diff mode belongs to the tabpage, not to a pair of windows, so two pairs in one
tabpage would merge into a four-way diff. So each tabpage has one pair, in the window being
read: the one the cursor last entered on a listed file, or the one the sidebar previews
into. Entering the base window, the sidebar or an unlisted file doesn't move it, nor does a
floating window, as the one `nvim_buf_call` borrows. Every other window keeps the unified
view, which needs no partner. A tabpage holding a diff of the user's own gets no pair, and
a window in diff mode changeset didn't pair counts as the user's own view, left alone as a
unified view of theirs is.

It doesn't run `gitsigns.diffthis()` either. Its no-argument call does diff the base
changeset set, but takes the current window late, as the unified `diffthis` does, splits a
conflicted file three ways, and makes a buffer that writes back to the index. The base
window holds the read-only revision buffer the unified view already uses, opened on the
file window's left so the pair reads base | file, as GitHub's split does. That buffer is
read-only only while the base is a commit: compared against the index, gitsigns makes it
write back there, so a file stays unified until its base is a commit, rather than put a
buffer that writes to the index in a window the user can enter.

A file new since the base stays unified: its base text is empty, which gitsigns writes as
one blank line, and Vim's diff can't show "every line added" against that, while the
unified view already does.

A preview swap parks the base window for the next listed file rather than closing it, so
holding `j` in the sidebar doesn't open and close a window per row. Turning the diff off,
or lowering its reach, closes every base window no view names. Closing a file window takes
its base window with it, so none is left diffing nothing; when that would empty the base
window's tabpage mid-close, it closes just after instead; closing the base window by hand
closes that file's diff as a hand-closed unified view does. A reload keeps the pair, since
`:e!` takes gitsigns' unified view with the buffer but not a window.

'diff' is window-local, and a window-local option comes back with the buffer it was set
for. So a window taking back a buffer changeset had diffed, with no base window beside it,
would sit in a one-sided diff; changeset turns that 'diff' off.

Switching views is not a hand-close. It redraws every window in the other view, the user
asking for the diff another way, not for less of it; counting it as closing each file's
view would leave every file without one.

A session restores a base window as an empty `gitsigns://` buffer, since gitsigns reads no
such buffer back. So on `SessionLoadPost` changeset closes each one, unless it is the last
window, and the reconciler opens a fresh pair. `:mksession` also saves a diffed window's
'foldmethod' `diff` but not its 'diff', so changeset resets a window restored with the one
and not the other, which would otherwise fold on nothing.

### The diff follows the sidebar

Opening the sidebar is an implicit request to read the branch, so it turns the diff on
Expand All @@ -1353,11 +1404,12 @@ diff on every file the cursor passed over. The set lasts the session, so a file
an hour ago keeps its diff after the next close. It records which files, not when, since
nothing reads a time.

Closing one of changeset's views by hand, with gitsigns' toggle or by switching to its split
diff, is about that file. Its views close in every window showing it, so none is left frozen
there with gitsigns' signs drawn beside it, and they stay closed until the diff next turns
on, by the sidebar or the toggle, each the user asking for the diff again; nothing writes it
down. Turning every file off for it would make one stray toggle cost the session's diff.
Closing one of changeset's views by hand, with gitsigns' toggle or by opening gitsigns' own
split diff, is about that file. Its views close in every window showing it, so none is left
frozen there with gitsigns' signs drawn beside it, and they stay closed until the diff next
turns on, by the sidebar or the toggle, each the user asking for the diff again; nothing
writes it down. Turning every file off for it would make one stray toggle cost the
session's diff.

A file marked viewed has no diff while its mark holds: the user has read it, so its buffer
goes back to how gitsigns alone draws it. The diff returns when the file's diff changes,
Expand All @@ -1368,7 +1420,7 @@ condition, apart from the reach and from a view closed by hand.

The tree is built the first time something asks for it — `:Changeset`, `:Changeset
refresh`, the picker (`:Changeset pick`, or `require("changeset.pick").pick()`),
`:Changeset diff` turning the unified diff on, or a restored session refilling the sidebar
`:Changeset diff` turning the diff on, or a restored session refilling the sidebar
— for the current buffer's repository: the fork point
is measured at once,
the diff and symbols in the background. Reading symbols loads each changed file the cache
Expand Down
58 changes: 44 additions & 14 deletions doc/changeset.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,22 @@ UNIFIED DIFF
:Changeset diff *:Changeset-diff*
<Plug>(changeset-diff) *<Plug>(changeset-diff)*
<C-g>d *changeset-<C-g>d*
Turn the unified diff off everywhere when it is on, else on for every
file the sidebar lists, as opening the sidebar does, with the sidebar
open or closed (|changeset-unified-diff|). It says which, in one line.
Before any tree was built, it first builds one for the current
buffer's repository, or warns "no merge base with the default branch"
and stays off. Without gitsigns, or one too old for unified views, it
warns that the unified diff needs gitsigns and stays off.
Turn the diff off everywhere when it is on, else on for every file
the sidebar lists, as opening the sidebar does, with the sidebar open
or closed, in the view it is in (|changeset-unified-diff|). It says
which, in one line. Before any tree was built, it first builds one
for the current buffer's repository, or warns "no merge base with the
default branch" and stays off. Without gitsigns, or one too old for
unified views, it warns that the diff needs gitsigns and stays off.

:Changeset diff split *:Changeset-diff-split*
<Plug>(changeset-diff-split) *<Plug>(changeset-diff-split)*
<C-g>D *changeset-<C-g>D*
Switch the diff between unified and side by side, for every file of
the branch. When the diff is off, turn it on side by side instead.
It says which view is on.
Like |:Changeset-diff|, it builds a tree when there is none, and warns
that the diff needs gitsigns without it.

STEPPING

Expand Down Expand Up @@ -415,6 +424,7 @@ Key Mode Runs ~
<C-g>bc Normal |:Changeset-base-commit|
<C-g>br Normal |:Changeset-base-reset|
<C-g>d Normal |:Changeset-diff|
<C-g>D Normal |:Changeset-diff-split|
<C-g>r Normal |:Changeset-refresh|
<C-g>nn Normal |:Changeset-next|
<C-g>np Normal |:Changeset-prev|
Expand Down Expand Up @@ -675,7 +685,7 @@ keymaps.toggle_viewed *changeset-option-keymaps.toggle_viewed*
- The word `viewed` sits left of its stat, in |ChangesetMeta|. A long
name is trimmed to make room.
- The file folds.
Its unified diff closes too (|changeset-unified-diff|).
Its diff closes too (|changeset-unified-diff|).

After marking, the cursor moves to the next file row below that isn't
viewed, skipping other copies of the file you marked, and previews it.
Expand Down Expand Up @@ -1156,6 +1166,21 @@ and needs gitsigns. changeset opens it in no other file, and closes it in a
file the sidebar stops listing, as when you switch branches, once the
sidebar has read the new diff.

*changeset-split-diff*
|:Changeset-diff-split| switches it side by side, or back, for the session;
`unified_diff.view` picks the view a session starts in
(|changeset-option-unified_diff.view|). Side by side, the window you are
reading shows its file's base in a window on its left, the two in
|diff-mode|. Diff mode belongs to the tab page, so only that window of each
tab page goes side by side, and the other windows there keep the unified
view. The window you are reading is the one your cursor last entered on a
file the sidebar lists, or the one the sidebar previews into, and the base
window moves with it. A file added or untracked since the base stays
unified, and so does a tab page holding a diff of your own. Closing the base
window yourself closes that file's diff, as closing its unified view does
(below). `unified_diff.keep` and viewed marks govern the side-by-side diff as
they govern the unified one. 'diffopt' is yours.

It is off when Neovim starts. Opening the sidebar, by any route, turns it on
for every file the sidebar lists, in every window, files you open later
included. Closing the sidebar, by any route, keeps it on only as far as
Expand Down Expand Up @@ -1256,7 +1281,7 @@ Pass |changeset.setup()| the options you change. These are the defaults: >lua
sign = true,
blocks = false,
},
unified_diff = { keep = "none" },
unified_diff = { keep = "none", view = "unified" },
})
<
Each `setup()` call starts from the defaults, not from the previous call. An
Expand Down Expand Up @@ -1298,10 +1323,15 @@ review_comment.blocks *changeset-option-review_comment.blocks*
as blocks of their whole text (|changeset-blocks|).

unified_diff.keep *changeset-option-unified_diff.keep*
`"none"`, `"entered"` or `"all"`. Default `"none"`. How far the unified
diff stays on once the sidebar closes: for no file, for the files you
entered, or for every file the sidebar lists (|changeset-unified-diff|).
Read each time the sidebar closes.
`"none"`, `"entered"` or `"all"`. Default `"none"`. How far the diff
stays on once the sidebar closes: for no file, for the files you
entered, or for every file the sidebar lists
(|changeset-unified-diff|). Read each time the sidebar closes.

unified_diff.view *changeset-option-unified_diff.view*
`"unified"` or `"split"`. Default `"unified"`. The view a session's
diff starts in: inline, or side by side (|changeset-split-diff|). Read
as the diff opens, until |:Changeset-diff-split| switches it.

==============================================================================
15. Highlight groups *changeset-highlights*
Expand Down Expand Up @@ -1551,7 +1581,7 @@ nothing, starts no language server and makes no network request.
cursor row and scroll too when it also contains `globals`.
- Kept in memory until Neovim exits: folds, which diff each viewed file
was last folded at, the agent last submitted to, how
far the unified diff reaches, the files you entered
far the diff reaches, the files you entered
(|changeset-entered-files|), and the files whose view you closed.

vim:tw=78:ts=8:noet:ft=help:norl:
11 changes: 9 additions & 2 deletions lua/changeset/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ local M = {}
---@field sign? boolean Put a comment bubble in the sign column on each review comment's first line; `false` leaves it to a statuscolumn that draws `require("changeset").bubble()`. Default true.
---@field blocks? boolean Start the session showing each review comment's whole text in a box under its last line, rather than its first line at the end of the line; `:Changeset comment toggle` switches. Default false.

---@alias changeset.DiffView "unified"|"split"

---@class changeset.Config.UnifiedDiff
---@field keep? changeset.DiffReach.Keep Which files keep the unified diff once the sidebar closes: none, those entered, or all. Default "none".
---@field keep? changeset.DiffReach.Keep Which files keep the diff once the sidebar closes: none, those entered, or all. Default "none".
---@field view? changeset.DiffView The view the diff starts each session in: inline, or side by side in the window being read. Default "unified".

---The options in force: every field set.
---@class changeset.Options : changeset.Config
Expand Down Expand Up @@ -90,6 +93,7 @@ local M = {}

---@class changeset.Options.UnifiedDiff : changeset.Config.UnifiedDiff
---@field keep changeset.DiffReach.Keep
---@field view changeset.DiffView

---@type changeset.Options
local DEFAULTS = {
Expand Down Expand Up @@ -118,7 +122,7 @@ local DEFAULTS = {
layout = { min_file_width = 80 },
review = { header = "", footer = "" },
review_comment = { save = { "<C-CR>", "<C-s>" }, sign = true, blocks = false },
unified_diff = { keep = "none" },
unified_diff = { keep = "none", view = "unified" },
}

local current = vim.deepcopy(DEFAULTS)
Expand Down Expand Up @@ -153,6 +157,9 @@ local function validate(options)
vim.validate("unified_diff.keep", options.unified_diff.keep, function(v)
return v == "none" or v == "entered" or v == "all"
end, '"none", "entered" or "all"')
vim.validate("unified_diff.view", options.unified_diff.view, function(v)
return v == "unified" or v == "split"
end, '"unified" or "split"')
end

---Splits `opts` into the options `defaults` has and the dotted paths of those it doesn't. A list is one option.
Expand Down
2 changes: 1 addition & 1 deletion lua/changeset/diff_reach.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---Which files the unified diff reaches: none, the files entered, or all of them, less those closed by hand.
---Which files the diff reaches: none, the files entered, or all of them, less those closed by hand.
---It decides; `changeset.unified_diff` opens and closes the views.

local M = {}
Expand Down
4 changes: 2 additions & 2 deletions lua/changeset/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,12 @@ end
---@param facts changeset.health.Facts
local function gitsigns(facts)
if facts.gitsigns and not facts.gitsigns_unified then
return finding("info", "`gitsigns` has no unified view: update it for the unified diff")
return finding("info", "`gitsigns` has no unified view: update it for the diff")
end
if facts.gitsigns then
return finding("ok", "`gitsigns` found")
end
return finding("info", "`gitsigns` not found: the gutter's branch base and the unified diff are unavailable")
return finding("info", "`gitsigns` not found: the gutter's branch base and the diff are unavailable")
end

---@param facts changeset.health.Facts
Expand Down
47 changes: 39 additions & 8 deletions lua/changeset/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -824,29 +824,60 @@ function M.open()
end
end

---Dismiss the sidebar, the unified diff kept on only as far as `unified_diff.keep` says. The tree stays, and keeps
---Dismiss the sidebar, the diff kept on only as far as `unified_diff.keep` says. The tree stays, and keeps
---refreshing.
function M.close()
release()
window.close()
unified_diff.limit(config.get().unified_diff.keep)
end

---Turn the unified diff off everywhere, or on for every file of the tree as the sidebar opening does, building the
---The active view's name, as messages say it.
---@return string
local function view_name()
return unified_diff.view() == "split" and "side-by-side diff" or "unified diff"
end

---Warn that the diff needs gitsigns when it is missing.
---@return boolean available
local function diff_available()
local available = unified_diff.available()
if not available then
vim.notify("Changeset: the diff needs gitsigns", vim.log.levels.WARN)
end
return available
end

---Turn the diff off everywhere, or on for every file of the tree as the sidebar opening does, building the
---current buffer's repository's tree when there is none, and say which; warn when gitsigns can't show it.
function M.diff()
if unified_diff.on() then
unified_diff.limit("none")
return vim.notify("Changeset: unified diff off")
return vim.notify(("Changeset: %s off"):format(view_name()))
end
if not unified_diff.available() then
return vim.notify("Changeset: the unified diff needs gitsigns", vim.log.levels.WARN)
end
if not (build.current() or built()) then
if not (diff_available() and (build.current() or built())) then
return
end
unified_diff.turn_on()
vim.notify("Changeset: unified diff on")
vim.notify(("Changeset: %s on"):format(view_name()))
end

---Switch the diff between unified and side by side while it is on, or turn it on side by side when it is off,
---building the current buffer's repository's tree when there is none, and say which view is on; warn when gitsigns
---can't show it.
function M.diff_split()
if not (diff_available() and (build.current() or built())) then
return
end
if unified_diff.on() then
unified_diff.switch()
else
if unified_diff.view() ~= "split" then
unified_diff.switch()
end
unified_diff.turn_on()
end
vim.notify(("Changeset: %s on"):format(view_name()))
end

---Fill `placeholder`, in the current tabpage, with the tree, or let go of it when there is no tree to fill it with.
Expand Down
Loading
Loading