Skip to content

Commit c33cfce

Browse files
authored
feat: Stage changes by editing index buffers (#260)
1 parent 6e514ce commit c33cfce

File tree

7 files changed

+284
-89
lines changed

7 files changed

+284
-89
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ Additional commands for convenience:
115115
With a Diffview open and the default key bindings, you can cycle through changed
116116
files with `<tab>` and `<s-tab>` (see configuration to change the key bindings).
117117

118+
#### Staging
119+
120+
You can stage individual hunks by editing any buffer that represents the index
121+
(after running `:DiffviewOpen` with no `[git-rev]` the entries under "Changes"
122+
will have the index buffer on the left side, and the entries under "Staged
123+
changes" will have it on the right side). Once you write to an index buffer the
124+
index will be updated.
125+
118126
### `:[range]DiffviewFileHistory [paths] [options]`
119127

120128
Opens a new file history view that lists all commits that affected the given

doc/diffview.txt

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ COMMANDS *diffview-commands*
5555
:DiffviewOpen HEAD~2 -- lua/diffview plugin
5656
:DiffviewOpen d4a7b0d -uno
5757
<
58+
59+
*diffview-staging*
60+
You can stage individual hunks by editing any buffer that represents
61+
the index (after running `:DiffviewOpen` with no [git-rev] the entries
62+
under "Changes" will have the index buffer on the left side, and the
63+
entries under "Staged changes" will have it on the right side). Once
64+
you write to an index buffer the index will be updated.
65+
5866
*diffview-merge-tool*
5967
If you call `:DiffviewOpen` during a merge or a rebase, the view will
6068
list the conflicted files in their own section. When opening a
@@ -98,7 +106,7 @@ COMMANDS *diffview-commands*
98106
layouts. To configure a different default layout, see
99107
|diffview-config-view.x.layout|.
100108

101-
Options:~
109+
Options: ~
102110
-u[value], --untracked-files[={value}]
103111
Specify whether or not to show untracked files. If
104112
flag is given without value; defaults to `true`.
@@ -171,7 +179,7 @@ COMMANDS *diffview-commands*
171179
:'<,'>DiffviewFileHistory
172180
<
173181

174-
Options:~
182+
Options: ~
175183
--base={git-rev}
176184
Specify a base git rev from which the right side of
177185
the diff will be created. Use the special value
@@ -506,7 +514,7 @@ log_options *diffview-config-log_options*
506514
to define different default log options for history targeting singular
507515
files, and history targeting multiple paths, and/or directories.
508516

509-
Fields:~
517+
Fields: ~
510518
{single_file} (`LogOptions`)
511519
See |diffview.git.LogOptions|.
512520

@@ -535,34 +543,34 @@ hooks *diffview-config-hooks*
535543
Diffview. The hook events are also available as User autocommands. See
536544
|diffview-user-autocmds| for more details.
537545

538-
Available Events:~
546+
Available Events: ~
539547
{view_opened} (`fun(view: View)`)
540548
Emitted after a new view has been opened. It's called after
541549
initializing the layout in the new tabpage (all windows are
542550
ready).
543551

544-
Callback Parameters:~
552+
Callback Parameters: ~
545553
{view} (`View`)
546554
The `View` instance that was opened.
547555

548556
{view_closed} (`fun(view: View)`)
549557
Emitted after closing a view.
550558

551-
Callback Parameters:~
559+
Callback Parameters: ~
552560
{view} (`View`)
553561
The `View` instance that was closed.
554562

555563
{view_enter} (`fun(view: View)`)
556564
Emitted just after entering the tabpage of a view.
557565

558-
Callback Parameters:~
566+
Callback Parameters: ~
559567
{view} (`View`)
560568
The `View` instance that was entered.
561569

562570
{view_leave} (`fun(view: View)`)
563571
Emitted just before leaving the tabpage of a view.
564572

565-
Callback Parameters:~
573+
Callback Parameters: ~
566574
{view} (`View`)
567575
The `View` instance that's about to be left.
568576

@@ -577,7 +585,7 @@ hooks *diffview-config-hooks*
577585
that |:setlocal| will apply settings to the relevant buffer /
578586
window.
579587

580-
Callback Parameters:~
588+
Callback Parameters: ~
581589
{bufnr} (`integer`)
582590
The buffer number of the new buffer.
583591

@@ -589,7 +597,7 @@ hooks *diffview-config-hooks*
589597
that |:setlocal| will apply settings to the relevant buffer /
590598
window.
591599

592-
Callback Parameters:~
600+
Callback Parameters: ~
593601
{bufnr} (`integer`)
594602
The buffer number of the new buffer.
595603
{winid} (`integer`)
@@ -665,7 +673,7 @@ keymaps *diffview-config-keymaps*
665673
<
666674

667675
*diffview-actions*
668-
Actions~
676+
Actions ~
669677

670678
Actions are key-mappable functions. You can access an action through
671679
the config module: >
@@ -680,7 +688,7 @@ Actions~
680688
Following are the different contexts described alongside possible
681689
subjects, upon which actions called from their context, will act on.
682690

683-
Contexts:~
691+
Contexts: ~
684692
{diff_view}
685693
The windows containing the diff buffers in a Diffview.
686694

@@ -738,7 +746,7 @@ Actions~
738746
Any of the panels.
739747

740748
*diffview-available-actions*
741-
Available Actions~
749+
Available Actions ~
742750

743751
close *diffview-actions-close*
744752
Contexts: `view`, `panel`
@@ -978,7 +986,7 @@ view_windo({cmd}, [targets]) *diffview-actions-view_windo*
978986
|diffview-config-view.x.layout|.
979987

980988
*diffview-unused-actions*
981-
Unused actions~
989+
Unused actions ~
982990

983991
Not all actions are mapped by default. The unused actions offer variations on
984992
the functionality provided by other actions, or just different functionality
@@ -1037,7 +1045,7 @@ file panel.
10371045
|diffview-actions|
10381046

10391047
*diffview-file-inference*
1040-
File inference~
1048+
File inference ~
10411049

10421050
Actions that target a file will infer the target file according to a set of
10431051
simple rules:
@@ -1048,7 +1056,7 @@ simple rules:
10481056
the entry under the cursor.
10491057

10501058
*diffview-maps-view*
1051-
View maps~
1059+
View maps ~
10521060

10531061
These maps are available in the diff buffers while a Diffview is the current
10541062
tabpage.
@@ -1087,7 +1095,7 @@ gf Open the local version of the file in a new split in a
10871095
y Copy the commit hash of the entry under the cursor.
10881096

10891097
*diffview-maps-file-panel*
1090-
File panel maps~
1098+
File panel maps ~
10911099

10921100
These maps are available in the file panel buffer.
10931101

@@ -1134,7 +1142,7 @@ R Update the stats and entries in the file list.
11341142
<leader>e Bring focus to the file panel.
11351143

11361144
*diffview-maps-file-history-panel*
1137-
File history panel maps~
1145+
File history panel maps ~
11381146

11391147
These mappings are available in the file history panel buffer (the panel
11401148
listing the commits).
@@ -1174,7 +1182,7 @@ o Open the diff for the selected item.
11741182
<leader>e Bring focus to the file history panel.
11751183

11761184
*diffview-maps-file-history-option-panel*
1177-
File history option panel maps~
1185+
File history option panel maps ~
11781186

11791187
These mappings are available from the file history option panel. The option
11801188
panel will allow you to change the flags that will be passed to `git-log`. A

lua/diffview/scene/file_entry.lua

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ local File = lazy.access("diffview.vcs.file", "File") ---@type vcs.File|LazyModu
99
local RevType = lazy.access("diffview.vcs.rev", "RevType") ---@type RevType|LazyModule
1010
local utils = lazy.require("diffview.utils") ---@module "diffview.utils"
1111

12+
local api = vim.api
13+
1214
local M = {}
1315

1416
local fstat_cache = {}
@@ -168,8 +170,28 @@ function FileEntry:validate_stage_buffers(adapter, stat)
168170
if stat then
169171
if not cached_stat or cached_stat.mtime < stat.mtime.sec then
170172
for _, f in ipairs(self.layout:files()) do
171-
if f.rev.type == RevType.STAGE then
172-
f:dispose_buffer()
173+
if f.rev.type == RevType.STAGE and f:is_valid() then
174+
if f.rev.stage == 0 then
175+
local is_modified = vim.bo[f.bufnr].modified
176+
177+
if f.blob_hash then
178+
local new_hash = f.adapter:file_blob_hash(f.path)
179+
if new_hash and new_hash ~= f.blob_hash and is_modified then
180+
utils.warn((
181+
"A file was changed in the index since you started editing it!"
182+
.. " Be careful not to lose any staged changes when writing to this buffer: %s"
183+
):format(api.nvim_buf_get_name(f.bufnr)))
184+
end
185+
elseif not is_modified then
186+
-- Should be very rare that we don't have an index-buffer's blob
187+
-- hash. But in that case, we can't warn the user when a file
188+
-- changes in the index while they're editing its index buffer.
189+
f:dispose_buffer()
190+
end
191+
192+
else
193+
f:dispose_buffer()
194+
end
173195
end
174196
end
175197
end

lua/diffview/utils.lua

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ end
387387

388388
---@class utils.system_list.Opt
389389
---@field cwd string Working directory of the job.
390+
---@field writer Job|table|string Something that will write to the stdin of this job.
390391
---@field silent boolean Supress log output.
391392
---@field fail_on_empty boolean Return code 1 if stdout is empty and code is 0.
392393
---@field retry_on_empty integer Number of times to retry job if stdout is empty and code is 0. Implies `fail_on_empty`.
@@ -428,6 +429,7 @@ function M.system_list(cmd, cwd_or_opt)
428429
command = command,
429430
args = cmd,
430431
cwd = opt.cwd,
432+
writer = opt.writer,
431433
on_stderr = function(_, data)
432434
table.insert(stderr, data)
433435
end,
@@ -708,6 +710,44 @@ function M.tbl_fmap(t, func)
708710
return ret
709711
end
710712

713+
---Set a value in a table, creating all missing intermediate tables in the
714+
---table path.
715+
---@param t table
716+
---@param table_path string|string[] Either a `.` separated string of table keys, or a list.
717+
---@param value any
718+
function M.tbl_set(t, table_path, value)
719+
local keys = type(table_path) == "table"
720+
and table_path
721+
or vim.split(table_path, ".", { plain = true })
722+
723+
local cur = t
724+
725+
for i = 1, #keys - 1 do
726+
local k = keys[i]
727+
728+
if not cur[k] then
729+
cur[k] = {}
730+
end
731+
732+
cur = cur[k]
733+
end
734+
735+
cur[keys[#keys]] = value
736+
end
737+
738+
---Ensure that the table path is a table in `t`.
739+
---@param t table
740+
---@param table_path string|string[] Either a `.` separated string of table keys, or a list.
741+
function M.tbl_ensure(t, table_path)
742+
local keys = type(table_path) == "table"
743+
and table_path
744+
or vim.split(table_path, ".", { plain = true })
745+
746+
if not M.tbl_access(t, keys) then
747+
M.tbl_set(t, keys, {})
748+
end
749+
end
750+
711751
---Create a shallow copy of a portion of a vector. Negative numbers indexes
712752
---from the end.
713753
---@param t vector

lua/diffview/vcs/adapter.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ function VCSAdapter:head_rev()
9898
oop.abstract_stub()
9999
end
100100

101+
---Get the hash for a file's blob in a given rev.
102+
---@param path string
103+
---@param rev_arg string?
104+
---@return string?
105+
function VCSAdapter:file_blob_hash(path, rev_arg)
106+
oop.abstract_stub()
107+
end
108+
101109
---@return string[] # path to binary for VCS command
102110
function VCSAdapter:get_command()
103111
oop.abstract_stub()
@@ -284,6 +292,13 @@ function VCSAdapter:file_restore(path, kind, commit)
284292
oop.abstract_stub()
285293
end
286294

295+
---Update the index entry for a given file with the contents of an index buffer.
296+
---@param file vcs.File
297+
---@return boolean success
298+
function VCSAdapter:stage_index_file(file)
299+
oop.abstract_stub()
300+
end
301+
287302
---@diagnostic enable: unused-local, missing-return
288303

289304
---@param self VCSAdapter

0 commit comments

Comments
 (0)