diff --git a/README.md b/README.md
index 219ec06e..456697d1 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,11 @@
+
+ Stage a hunk
+
+
+
Navigate through hunks
@@ -72,6 +77,13 @@
+## Supported Neovim versions:
+- Neovim > 0.5
+
+## Supported Opperating System:
+- linux-gnu*
+- Darwin
+
## Prerequisites
- [Git](https://git-scm.com/)
- [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter)
@@ -374,8 +386,9 @@ vim.api.nvim_set_keymap('n', 'gq', ':VGit hunks_quickfix_list', {
| setup | Sets up the plugin for success |
| toggle_buffer_hunks | Shows hunk signs on buffers/Hides hunk signs on buffers |
| toggle_buffer_blames | Enables blames feature on buffers /Disables blames feature on buffers |
-| hunk_preview | Opens a VGit view of a hunk, if cursor is on a line with a git change |
-| hunk_reset | Removes the hunk from the buffer |
+| hunk_preview | Opens a VGit view of a hunk, if cursor is on a hunk |
+| hunk_reset | Removes the hunk from the buffer, if cursor is on a hunk |
+| hunk_stage | Stages a hunk, if cursor is on a hunk |
| hunk_down | Navigate downward through a hunk |
| hunk_up | Navigate upwards through a hunk |
| buffer_preview | Shows the current differences in lines in the current buffer |
@@ -397,5 +410,4 @@ vim.api.nvim_set_keymap('n', 'gq', ':VGit hunks_quickfix_list', {
## Similar Git Plugins
- [vim-fugitive](https://github.com/tpope/vim-fugitive)
- [vim-gitgutter](https://github.com/airblade/vim-gitgutter)
-- [gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim)
-- [neogit](https://github.com/TimUntersberger/neogit)
+- [vim-signify](https://github.com/mhinz/vim-signify)
diff --git a/lua/vgit/algorithms.lua b/lua/vgit/algorithms.lua
deleted file mode 100644
index a45d3b46..00000000
--- a/lua/vgit/algorithms.lua
+++ /dev/null
@@ -1,175 +0,0 @@
-local M = {}
-
-local function create_hunk(hunks, start, finish, diff, type)
- if type == 'remove' then
- if start < 0 then
- start = 0
- end
- hunks[#hunks + 1] = {
- start = start,
- finish = start,
- type = type,
- diff = diff,
- }
- elseif type == 'change' then
- local hunk = {
- start = start + 1,
- finish = finish,
- type = type,
- diff = diff,
- }
- hunks[#hunks + 1] = hunk
- if hunk.start < 1 then
- hunk.start = 1
- hunk.finish = 1
- end
- else
- hunks[#hunks + 1] = {
- start = start,
- finish = finish - 1,
- type = type,
- diff = diff,
- }
- end
-end
-
-M.myers_difference = function(a_lines, b_lines)
- local steps = { [1] = { x = 0, history = {} } }
- local a_len = #a_lines
- local b_len = #b_lines
- local max = a_len + b_len + 1
- for d = 0, max do
- for k = -d, d, 2 do
- local x, history
- local go_down = (k == -d or (k ~= d and steps[k - 1].x < steps[k + 1].x))
- if go_down then
- local step = steps[k + 1]
- x = step.x
- history = step.history
- else
- local step = steps[k - 1]
- x = step.x + 1
- history = step.history
- end
- local temp_history = history
- history = {}
- for i = 1, #temp_history do
- history[i] = temp_history[i]
- end
- local y = x - k
- if 1 <= y and y <= b_len and go_down then
- history[#history + 1] = { 1, b_lines[y] }
- elseif 1 <= x and x <= a_len then
- history[#history + 1] = { -1, a_lines[x] }
- end
- while x < a_len and y < b_len and a_lines[x + 1] == b_lines[y + 1] do
- x = x + 1
- y = y + 1
- history[#history + 1] = { 0, a_lines[x] }
- end
- if x >= a_len and y >= b_len then
- return history
- else
- steps[k] = { x = x, history = history }
- end
- end
- end
-end
-
-M.hunks = function(a_lines, b_lines)
- local diffs = M.myers_difference(a_lines, b_lines)
- local hunks = {}
- local processing_hunk = false
- local predicted_type = nil
- local temp_diff = {}
- local start = 0
- local temp_added_lines = 0
- local temp_removed_lines = 0
- local r_lines = {}
- for line_number = 1, #diffs do
- local diff = diffs[line_number]
- local type = diff[1]
- local line = diff[2]
- if not processing_hunk then
- if type == 1 then
- predicted_type = 'add'
- processing_hunk = true
- temp_diff[#temp_diff + 1] = string.format('+%s', line)
- start = line_number
- temp_added_lines = temp_added_lines + 1
- elseif type == -1 then
- predicted_type = 'undecided'
- processing_hunk = true
- temp_diff[#temp_diff + 1] = string.format('-%s', line)
- r_lines[#r_lines + 1] = line_number
- start = line_number
- end
- else
- if type == 1 then
- temp_diff[#temp_diff + 1] = string.format('+%s', line)
- if predicted_type == 'undecided' then
- predicted_type = 'change'
- end
- temp_added_lines = temp_added_lines + 1
- elseif type == -1 then
- temp_diff[#temp_diff + 1] = string.format('-%s', line)
- r_lines[#r_lines + 1] = line_number
- temp_removed_lines = temp_removed_lines + 1
- else
- local removed_lines = 0
- if predicted_type == 'undecided' then
- predicted_type = 'remove'
- for i = 1, #r_lines do
- local lnum = r_lines[i]
- if line_number > lnum then
- removed_lines = removed_lines + 1
- end
- end
- removed_lines = removed_lines - temp_removed_lines
- elseif predicted_type == 'change' then
- for i = 1, #r_lines do
- local lnum = r_lines[i]
- if line_number > lnum then
- removed_lines = removed_lines + 1
- end
- end
- removed_lines = removed_lines - temp_removed_lines
- else
- removed_lines = #r_lines
- end
- if #temp_diff ~= 0 then
- create_hunk(
- hunks,
- start - removed_lines,
- start - removed_lines + temp_added_lines,
- temp_diff,
- predicted_type
- )
- end
- temp_added_lines = 0
- temp_removed_lines = 0
- processing_hunk = false
- temp_diff = {}
- predicted_type = nil
- start = 0
- end
- end
- end
- if #temp_diff ~= 0 then
- local removed_lines = #r_lines
- if predicted_type == 'undecided' then
- predicted_type = 'remove'
- removed_lines = removed_lines - temp_removed_lines
- end
- create_hunk(
- hunks,
- start - removed_lines,
- start - removed_lines + temp_added_lines,
- temp_diff,
- predicted_type
- )
- end
- return hunks
-end
-
-return M
diff --git a/lua/vgit/defer.lua b/lua/vgit/defer.lua
index cf2b9a65..e4a7b70c 100644
--- a/lua/vgit/defer.lua
+++ b/lua/vgit/defer.lua
@@ -16,7 +16,7 @@ M.throttle_leading = function(fn, ms)
end
end
-function M.debounce_trailing(fn, ms)
+M.debounce_trailing = function(fn, ms)
local timer = vim.loop.new_timer()
return function(...)
local argv = {...}
diff --git a/lua/vgit/fs.lua b/lua/vgit/fs.lua
index ea3dae23..858ce560 100644
--- a/lua/vgit/fs.lua
+++ b/lua/vgit/fs.lua
@@ -5,7 +5,7 @@ local vim = vim
local M = {}
-M.relative_path = function(filepath)
+M.relative_filename = function(filepath)
assert(type(filepath) == 'string', 'type error :: expected string')
local cwd = vim.loop.cwd()
if not cwd or not filepath then return filepath end
@@ -19,13 +19,26 @@ M.relative_path = function(filepath)
return filepath
end
+M.short_filename = function(filepath)
+ assert(type(filepath) == 'string', 'type error :: expected string')
+ local filename = ''
+ for i = #filepath, 1, -1 do
+ local letter = filepath:sub(i, i)
+ if letter == '/' then
+ break
+ end
+ filename = letter .. filename
+ end
+ return filename
+end
+
M.project_relative_filename = function(filepath, project_files)
assert(type(filepath) == 'string', 'type error :: expected string')
assert(vim.tbl_islist(project_files), 'type error :: expected table of type list')
- table.sort(project_files)
if filepath == '' then
- return filepath
+ return nil
end
+ table.sort(project_files)
for i = #filepath, 1, -1 do
local letter = filepath:sub(i, i)
local new_project_files = {}
@@ -39,7 +52,19 @@ M.project_relative_filename = function(filepath, project_files)
end
project_files = new_project_files
end
- return project_files[1]
+ local project_filename = project_files[1]
+ if project_filename then
+ local short_filename = M.short_filename(filepath)
+ local project_short_filename = M.short_filename(project_filename)
+ return (short_filename == project_short_filename and project_filename) or nil
+ end
+ return nil
+end
+
+M.filename = function(buf)
+ assert(type(buf) == 'number', 'type error :: expected number')
+ local filepath = vim.api.nvim_buf_get_name(buf)
+ return M.relative_filename(filepath)
end
M.filetype = function(buf)
@@ -49,12 +74,6 @@ end
M.detect_filetype = pfiletype.detect
-M.filename = function(buf)
- assert(type(buf) == 'number', 'type error :: expected number')
- local filepath = vim.api.nvim_buf_get_name(buf)
- return M.relative_path(filepath)
-end
-
M.tmpname = function()
local length = 6
local res = ''
diff --git a/lua/vgit/git.lua b/lua/vgit/git.lua
index 751efc2e..406cd2ad 100644
--- a/lua/vgit/git.lua
+++ b/lua/vgit/git.lua
@@ -1,3 +1,4 @@
+local fs = require('vgit.fs')
local Job = require('plenary.job')
local State = require('vgit.State')
local a = require('plenary.async')
@@ -111,6 +112,7 @@ end
M.create_hunk = function(header)
local previous, current = parse_hunk_header(header)
local hunk = {
+ header = header,
start = current[1],
finish = current[1] + current[2] - 1,
type = nil,
@@ -128,6 +130,20 @@ M.create_hunk = function(header)
return hunk
end
+M.create_patch = function(filename, hunk)
+ local patch = {
+ string.format('diff --git a/%s b/%s', filename, filename),
+ 'index 000000..000000',
+ string.format('--- a/%s', filename),
+ string.format('+++ a/%s', filename),
+ hunk.header,
+ }
+ for i = 1, #hunk.diff do
+ patch[#patch + 1] = hunk.diff[i]
+ end
+ return patch
+end
+
M.create_blame = function(info)
local function split_by_whitespace(str)
return vim.split(str, ' ')
@@ -516,13 +532,40 @@ M.show = wrap(function(filename, commit_hash, callback)
job:start()
end, 3)
+M.stage_hunk = wrap(function(filename, hunk, callback)
+ local patch = M.create_patch(filename, hunk)
+ local patch_filename = fs.tmpname()
+ fs.write_file(patch_filename, patch)
+ local err = {}
+ local job = Job:new({
+ command = 'git',
+ args = {
+ 'apply',
+ '--cached',
+ '--unidiff-zero',
+ patch_filename,
+ },
+ on_stderr = function(_, data, _)
+ err[#err + 1] = data
+ end,
+ on_exit = function()
+ fs.remove_file(patch_filename)
+ if #err ~= 0 then
+ return callback(err)
+ end
+ callback(nil)
+ end,
+ })
+ job:start()
+end, 3)
+
M.reset = wrap(function(filename, callback)
local err = {}
local job = Job:new({
command = 'git',
args = {
'checkout',
- 'HEAD',
+ M.get_diff_base(),
'--',
filename,
},
diff --git a/lua/vgit/init.lua b/lua/vgit/init.lua
index 6bfa4af7..f098e41d 100644
--- a/lua/vgit/init.lua
+++ b/lua/vgit/init.lua
@@ -208,8 +208,7 @@ M._blame_line = debounce_trailing(void(function(buf)
if not state:get('disabled')
and buffer.is_valid(buf)
and bstate:contains(buf) then
- local is_buf_modified = vim.api.nvim_buf_get_option(buf, 'modified')
- if not is_buf_modified then
+ if not vim.api.nvim_buf_get_option(buf, 'modified') then
local win = vim.api.nvim_get_current_win()
local last_lnum_blamed = bstate:get(buf, 'last_lnum_blamed')
local lnum = vim.api.nvim_win_get_cursor(win)[1]
@@ -758,6 +757,39 @@ M.show_blame = throttle_leading(void(function(buf)
end
end), state:get('action_delay_ms'))
+M.hunk_stage = throttle_leading(void(function(buf, win)
+ buf = buf or buffer.current()
+ if not state:get('disabled')
+ and buffer.is_valid(buf)
+ and bstate:contains(buf)
+ and not vim.api.nvim_buf_get_option(buf, 'modified') then
+ win = win or vim.api.nvim_get_current_win()
+ local lnum = vim.api.nvim_win_get_cursor(win)[1]
+ local selected_hunk = nil
+ local hunks = bstate:get(buf, 'hunks')
+ for i = 1, #hunks do
+ local hunk = hunks[i]
+ if lnum == 1 and hunk.start == 0 and hunk.finish == 0 then
+ selected_hunk = hunk
+ break
+ end
+ if lnum >= hunk.start and lnum <= hunk.finish then
+ selected_hunk = hunk
+ break
+ end
+ end
+ if selected_hunk then
+ local err = git.stage_hunk(bstate:get(buf, 'project_relative_filename'), selected_hunk)
+ scheduler()
+ if not err then
+ M._buf_update(buf)
+ else
+ logger.debug(err, 'init.lua/hunk_stage')
+ end
+ end
+ end
+end), state:get('action_delay_ms'))
+
M.enabled = function()
return not state:get('disabled')
end
diff --git a/tests/unit/algorithms_spec.lua b/tests/unit/algorithms_spec.lua
deleted file mode 100644
index 5ea6738b..00000000
--- a/tests/unit/algorithms_spec.lua
+++ /dev/null
@@ -1,54 +0,0 @@
-local algorithms = require('vgit.algorithms')
-
-local it = it
-local describe = describe
-local eq = assert.are.same
-
-describe('algorithms:', function()
-
- describe('myers_difference', function()
-
- it('should compute the difference between two list tables', function()
- eq(algorithms.myers_difference({ 'a', 'b', 'c', 'd' }, { 'j', 'l', 'c', 'n' }), {
- { -1, 'a' },
- { -1, 'b' },
- { 1, 'j' },
- { 1, 'l' },
- { 0, 'c' },
- { -1, 'd' },
- { 1, 'n' },
- })
- end)
-
- end)
-
- describe('hunks', function()
-
- it('should compute the hunks of difference between the two different lines', function()
- eq(algorithms.hunks({ 'a', 'b', 'c', 'd' }, { 'j', 'l', 'c', 'n' }), {
- {
- diff = {
- '-a',
- '-b',
- '+j',
- '+l',
- },
- finish = 2,
- start = 1,
- type = 'change',
- },
- {
- diff = {
- '-d',
- '+n',
- },
- finish = 4,
- start = 4,
- type = 'change',
- }
- })
- end)
-
- end)
-
-end)
diff --git a/tests/unit/fs_spec.lua b/tests/unit/fs_spec.lua
index 2001bde4..7cc598a4 100644
--- a/tests/unit/fs_spec.lua
+++ b/tests/unit/fs_spec.lua
@@ -49,41 +49,72 @@ describe('fs:', function()
end)
- describe('relative_path', function()
+ describe('relative_filename', function()
it('should throw error on invalid argument types', function()
assert.has_error(function()
- fs.relative_path(true)
+ fs.relative_filename(true)
end)
assert.has_error(function()
- fs.relative_path({})
+ fs.relative_filename({})
end)
assert.has_error(function()
- fs.relative_path(1)
+ fs.relative_filename(1)
end)
assert.has_error(function()
- fs.relative_path(nil)
+ fs.relative_filename(nil)
end)
assert.has_error(function()
- fs.relative_path(function() end)
+ fs.relative_filename(function() end)
end)
end)
it('should convert an absolute path to a relative path', function()
local current = vim.loop.cwd()
local path = current .. '/lua/vgit/init.lua'
- local filepath = fs.relative_path(path)
+ local filepath = fs.relative_filename(path)
eq(filepath, 'lua/vgit/init.lua')
end)
it('should return the unchanged path if it is not absolute', function()
local path = 'lua/vgit/init.lua'
- local filepath = fs.relative_path(path)
+ local filepath = fs.relative_filename(path)
eq(filepath, 'lua/vgit/init.lua')
end)
end)
+ describe('short_filename', function()
+
+ it('should throw error on invalid argument types', function()
+ assert.has_error(function()
+ fs.relative_filename(true)
+ end)
+ assert.has_error(function()
+ fs.relative_filename({})
+ end)
+ assert.has_error(function()
+ fs.relative_filename(1)
+ end)
+ assert.has_error(function()
+ fs.relative_filename(nil)
+ end)
+ assert.has_error(function()
+ fs.relative_filename(function() end)
+ end)
+ end)
+
+ it('should take a long path and give you the filename', function()
+ eq(fs.short_filename('lua/vgit/init.lua'), 'init.lua')
+ eq(fs.short_filename('/init.lua'), 'init.lua')
+ eq(fs.short_filename('a/b/c/d/init.lua'), 'init.lua')
+ eq(fs.short_filename('init.lua'), 'init.lua')
+ eq(fs.short_filename(''), '')
+ eq(fs.short_filename('init/.lua'), '.lua')
+ end)
+
+ end)
+
describe('filetype', function()
it('should throw error on invalid argument types', function()