Bugfixes, Etc. (#502)

* Fix: Jumping to renamed files (#484)

* fix: prevent "cursor position outside buffer" error

* fix: swap file_name and old_file_name in reviewer data

`old_file_name` is not set to the empty string for un-renamed files anymore, because then we can
remove the empty-line check in `comment_helpers.go` which was used to replace the empty string with
the current file name anyway.

* fix: add old_file_name to discussion root node data

* fix: also consider old_file_name when jumping to the reviewer

This fixes jumping to renamed files, however, may not work for comments that
were created on renamed files with the previous version of `gitlab.nvim` as
that version assigned the `file_name` and `old_file_name` incorrectly.

* refactor: don't shadow variable

* fix: check file_name or old_file_name based on which SHA comment belongs to

* Fix: Store reviewer data before creating comment popup (#476)

* Fix: Make publishing drafts more robust (#483)

* Fix: Swap file_name and old_file_name in reviewer data (#485)

* Feat: Enable toggling date format between relative and absolute (#491)

* Fix: Add opts to help popup (#492)

* Fix: Force start_line for jumping to diagnostic to be inside buffer (#494)

* fix: redefine colors after reloading colorscheme (#500)

* Fix: Use path instead of oldpath as fallback for unrenamed files (#496)

* Fix: Use file_name when old_file_name is not set (#495)

* fix(ci): fix lua tests (#501)

* Proxy Support (#499)

This is a #MINOR release.

---------

Co-authored-by: Jakub F. Bortlík <jakub.bortlik@proton.me>
Co-authored-by: Jonathan Duck <Duckbrain30@gmail.com>
This commit is contained in:
Harrison (Harry) Cramer
2025-06-24 20:53:51 -04:00
committed by GitHub
parent a260f648fe
commit e29909cd10
15 changed files with 107 additions and 66 deletions

View File

@@ -45,12 +45,15 @@ jobs:
with: with:
neovim: true neovim: true
version: ${{ matrix.nvim_version }} version: ${{ matrix.nvim_version }}
- name: Install luajit - uses: leafo/gh-actions-lua@v11
uses: leafo/gh-actions-lua@v10
with: with:
luaVersion: "luajit-openresty" luaVersion: "luajit-openresty"
- name: Install luarocks - uses: hishamhm/gh-actions-luarocks@master
uses: leafo/gh-actions-luarocks@v4 with:
luaRocksVersion: "3.12.0"
- name: build
run: |
luarocks install busted
- name: Run tests - name: Run tests
shell: bash shell: bash
run: | run: |

View File

@@ -5,10 +5,11 @@ endif
let expanders = '^\s*\%(' . g:gitlab_discussion_tree_expander_open . '\|' . g:gitlab_discussion_tree_expander_closed . '\)' let expanders = '^\s*\%(' . g:gitlab_discussion_tree_expander_open . '\|' . g:gitlab_discussion_tree_expander_closed . '\)'
let username = '@[a-zA-Z0-9.]\+' let username = '@[a-zA-Z0-9.]\+'
" Covers times like '14 days ago', 'just now', as well as 'October 3, 2024' " Covers times like '14 days ago', 'just now', as well as 'October 3, 2024', and '02/28/2025 at 00:50'
let time_ago = '\d\+ \w\+ ago' let time_ago = '\d\+ \w\+ ago'
let formatted_date = '\w\+ \{1,2}\d\{1,2}, \d\{4}' let formatted_date = '\w\+ \{1,2}\d\{1,2}, \d\{4}'
let date = '\%(' . time_ago . '\|' . formatted_date . '\|just now\)' let absolute_time = '\d\{2}/\d\{2}/\d\{4} at \d\{2}:\d\{2}'
let date = '\%(' . time_ago . '\|' . formatted_date . '\|' . absolute_time . '\|just now\)'
let published = date . ' \%(' . g:gitlab_discussion_tree_resolved . '\|' . g:gitlab_discussion_tree_unresolved . '\|' . g:gitlab_discussion_tree_unlinked . '\)\?' let published = date . ' \%(' . g:gitlab_discussion_tree_resolved . '\|' . g:gitlab_discussion_tree_unresolved . '\|' . g:gitlab_discussion_tree_unlinked . '\)\?'
let state = ' \%(' . published . '\|' . g:gitlab_discussion_tree_draft . '\)' let state = ' \%(' . published . '\|' . g:gitlab_discussion_tree_draft . '\)'

View File

@@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"github.com/harrisoncramer/gitlab.nvim/cmd/app/git" "github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
"github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/go-retryablehttp"
@@ -66,6 +67,14 @@ func NewClient() (*Client, error) {
}, },
} }
if proxy := pluginOptions.ConnectionSettings.Proxy; proxy != "" {
u, err := url.Parse(proxy)
if err != nil {
return nil, fmt.Errorf("parse proxy url: %w", err)
}
tr.Proxy = http.ProxyURL(u)
}
retryClient := retryablehttp.NewClient() retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr retryClient.HTTPClient.Transport = tr
gitlabOptions = append(gitlabOptions, gitlab.WithHTTPClient(retryClient.HTTPClient)) gitlabOptions = append(gitlabOptions, gitlab.WithHTTPClient(retryClient.HTTPClient))
@@ -99,11 +108,11 @@ func InitProjectSettings(c *Client, gitInfo git.GitData) (*ProjectInfo, error) {
project, _, err := c.GetProject(gitInfo.ProjectPath(), &opt) project, _, err := c.GetProject(gitInfo.ProjectPath(), &opt)
if err != nil { if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("Error getting project at %s", gitInfo.RemoteUrl), err) return nil, fmt.Errorf("error getting project at %s: %w", gitInfo.RemoteUrl, err)
} }
if project == nil { if project == nil {
return nil, fmt.Errorf(fmt.Sprintf("Could not find project at %s", gitInfo.RemoteUrl), err) return nil, fmt.Errorf("could not find project at %s", gitInfo.RemoteUrl)
} }
projectId := fmt.Sprint(project.ID) projectId := fmt.Sprint(project.ID)

View File

@@ -13,6 +13,7 @@ type PluginOptions struct {
} `json:"debug"` } `json:"debug"`
ChosenMrIID int `json:"chosen_mr_iid"` ChosenMrIID int `json:"chosen_mr_iid"`
ConnectionSettings struct { ConnectionSettings struct {
Proxy string `json:"proxy"`
Insecure bool `json:"insecure"` Insecure bool `json:"insecure"`
Remote string `json:"remote"` Remote string `json:"remote"`
} `json:"connection_settings"` } `json:"connection_settings"`

View File

@@ -161,6 +161,7 @@ you call this function with no values the defaults will be used:
}, },
}, },
connection_settings = { connection_settings = {
proxy = "", -- Configure a proxy URL to use when connecting to GitLab. Supports URL schemes: http, https, socks5
insecure = false, -- Like curl's --insecure option, ignore bad x509 certificates on connection insecure = false, -- Like curl's --insecure option, ignore bad x509 certificates on connection
remote = "origin", -- The default remote that your MRs target remote = "origin", -- The default remote that your MRs target
}, },
@@ -213,6 +214,7 @@ you call this function with no values the defaults will be used:
switch_view = "c", -- Toggle between the notes and discussions views switch_view = "c", -- Toggle between the notes and discussions views
toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name" toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name"
publish_draft = "P", -- Publish the currently focused note/comment publish_draft = "P", -- Publish the currently focused note/comment
toggle_date_format = "dt", -- Toggle between date formats: relative (e.g., "5 days ago", "just now", "October 13, 2024" for dates more than a month ago) and absolute (e.g., "03/01/2024 at 11:43")
toggle_draft_mode = "D", -- Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately) toggle_draft_mode = "D", -- Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately)
toggle_sort_method = "st", -- Toggle whether discussions are sorted by the "latest_reply", or by "original_comment", see `:h gitlab.nvim.toggle_sort_method` toggle_sort_method = "st", -- Toggle whether discussions are sorted by the "latest_reply", or by "original_comment", see `:h gitlab.nvim.toggle_sort_method`
toggle_node = "t", -- Open or close the discussion toggle_node = "t", -- Open or close the discussion
@@ -267,6 +269,7 @@ you call this function with no values the defaults will be used:
draft = "✎", -- Symbol to show next to draft comments/notes draft = "✎", -- Symbol to show next to draft comments/notes
tree_type = "simple", -- Type of discussion tree - "simple" means just list of discussions, "by_file_name" means file tree with discussions under file tree_type = "simple", -- Type of discussion tree - "simple" means just list of discussions, "by_file_name" means file tree with discussions under file
draft_mode = false, -- Whether comments are posted as drafts as part of a review draft_mode = false, -- Whether comments are posted as drafts as part of a review
relative_date = true, -- Whether to show relative time like "5 days ago" or absolute time like "03/01/2025 at 01:43"
winbar = nil, -- Custom function to return winbar title, should return a string. Provided with WinbarTable (defined in annotations.lua) winbar = nil, -- Custom function to return winbar title, should return a string. Provided with WinbarTable (defined in annotations.lua)
-- If using lualine, please add "gitlab" to disabled file types, otherwise you will not see the winbar. -- If using lualine, please add "gitlab" to disabled file types, otherwise you will not see the winbar.
}, },

View File

@@ -1,15 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Setup and run tests for lua part of gitlab.nvim. # Setup and run tests for lua part of gitlab.nvim.
# Requires `luarocks`, `git`, and `nvim` installed.
# #
# In order to run tests you need to have `luarocks` and `git` installed. This script will check if
# environment is already setup, if not it will initialize current directory with `luarocks`,
# install `busted` framework and download plugin dependencies.
#
#
set -e
LUA_VERSION="5.1" set -euo pipefail
PLUGINS_FOLDER="tests/plugins" PLUGINS_FOLDER="tests/plugins"
PLUGINS=( PLUGINS=(
"https://github.com/MunifTanjim/nui.nvim" "https://github.com/MunifTanjim/nui.nvim"
@@ -18,42 +14,31 @@ PLUGINS=(
) )
if ! command -v luarocks >/dev/null 2>&1; then if ! command -v luarocks >/dev/null 2>&1; then
echo "You need to have luarocks installed in order to run tests." echo "Error: luarocks not found. Please install LuaRocks." >&2
exit 1 exit 1
fi fi
if ! command -v git >/dev/null 2>&1; then if ! command -v git >/dev/null 2>&1; then
echo "You need to have git installed in order to run tests." echo "Error: git not found. Please install Git." >&2
exit 1 exit 1
fi fi
if ! luarocks --lua-version=$LUA_VERSION which busted > /dev/null 2>&1; then if ! command -v nvim >/dev/null 2>&1; then
echo "Installing busted." echo "Error: nvim not found. Please install Neovim." >&2
luarocks init exit 1
luarocks config --scope project lua_version "$LUA_VERSION"
luarocks install --lua-version="$LUA_VERSION" busted
fi fi
for arg in "$@"; do # Clone test plugin dependencies
if [[ $arg =~ "--coverage" ]] && ! luarocks --lua-version=$LUA_VERSION which luacov > /dev/null 2>&1; then mkdir -p "$PLUGINS_FOLDER"
luarocks install --lua-version="$LUA_VERSION" luacov
# lcov reporter for luacov - lcov format is supported by `nvim-coverage`
luarocks install --lua-version="$LUA_VERSION" luacov-reporter-lcov
fi
done
for plugin in "${PLUGINS[@]}"; do for plugin in "${PLUGINS[@]}"; do
plugin_name=${plugin##*/} plugin_name="${plugin##*/}"
plugin_folder="$PLUGINS_FOLDER/$plugin_name" plugin_folder="$PLUGINS_FOLDER/$plugin_name"
if [[ ! -d "$plugin_folder/.git" ]]; then
# Check if plugin was already downloaded echo "Cloning $plugin..."
if [[ -d "$plugin_folder/.git" ]]; then
# We could also try to pull here but I am not sure if that wouldn't slow down tests too much.
continue
fi
git clone --depth 1 "$plugin" "$plugin_folder" git clone --depth 1 "$plugin" "$plugin_folder"
fi
done done
# Run tests
echo "Running tests with Neovim..."
nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@" nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@"

View File

@@ -15,7 +15,9 @@ M.build_note_header = function(note)
if note.note then if note.note then
return "@" .. state.USER.username .. " " .. state.settings.discussion_tree.draft return "@" .. state.USER.username .. " " .. state.settings.discussion_tree.draft
end end
return "@" .. note.author.username .. " " .. u.time_since(note.created_at) local time = state.settings.discussion_tree.relative_date and u.time_since(note.created_at)
or u.format_to_local(note.created_at, vim.fn.strftime("%z"))
return "@" .. note.author.username .. " " .. time
end end
M.switch_can_edit_bufs = function(bool, ...) M.switch_can_edit_bufs = function(bool, ...)
@@ -240,7 +242,9 @@ M.get_line_numbers_for_range = function(old_line, new_line, start_line_code, end
return (old_line - range), old_line, false return (old_line - range), old_line, false
elseif new_line ~= nil then elseif new_line ~= nil then
local range = new_end_line - new_start_line local range = new_end_line - new_start_line
return (new_line - range), new_line, true -- Force start_line to be greater than 0
local start_line = (new_line - range > 0) and (new_line - range) or 1
return start_line, new_line, true
else else
u.notify("Error getting new or old line for range", vim.log.levels.ERROR) u.notify("Error getting new or old line for range", vim.log.levels.ERROR)
return 1, 1, false return 1, 1, false

View File

@@ -649,6 +649,16 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
}) })
end end
if keymaps.discussion_tree.toggle_date_format then
vim.keymap.set("n", keymaps.discussion_tree.toggle_date_format, function()
M.toggle_date_format()
end, {
buffer = bufnr,
desc = "Toggle date format",
nowait = keymaps.discussion_tree.toggle_date_format_nowait,
})
end
if keymaps.discussion_tree.toggle_resolved then if keymaps.discussion_tree.toggle_resolved then
vim.keymap.set("n", keymaps.discussion_tree.toggle_resolved, function() vim.keymap.set("n", keymaps.discussion_tree.toggle_resolved, function()
if M.is_current_node_note(tree) and not M.is_draft_note(tree) then if M.is_current_node_note(tree) and not M.is_draft_note(tree) then
@@ -725,7 +735,7 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
if keymaps.help then if keymaps.help then
vim.keymap.set("n", keymaps.help, function() vim.keymap.set("n", keymaps.help, function()
help.open() help.open({ discussion_tree = true })
end, { buffer = bufnr, desc = "Open help popup", nowait = keymaps.help_nowait }) end, { buffer = bufnr, desc = "Open help popup", nowait = keymaps.help_nowait })
end end
@@ -809,6 +819,13 @@ M.toggle_sort_method = function()
M.rebuild_view(false, true) M.rebuild_view(false, true)
end end
---Toggle between displaying relative time (e.g., "5 days ago") and absolute time (e.g., "04/10/2025 at 22:49")
M.toggle_date_format = function()
state.settings.discussion_tree.relative_date = not state.settings.discussion_tree.relative_date
M.rebuild_unlinked_discussion_tree()
M.rebuild_discussion_tree()
end
---Indicates whether the node under the cursor is a draft note or not ---Indicates whether the node under the cursor is a draft note or not
---@param tree NuiTree ---@param tree NuiTree
---@return boolean ---@return boolean

View File

@@ -155,6 +155,7 @@ M.build_root_draft_note = function(note)
id = note.id, id = note.id,
root_note_id = note.id, root_note_id = note.id,
file_name = (type(note.position) == "table" and note.position.new_path or nil), file_name = (type(note.position) == "table" and note.position.new_path or nil),
old_file_name = (type(note.position) == "table" and note.position.old_path or nil),
new_line = (type(note.position) == "table" and note.position.new_line or nil), new_line = (type(note.position) == "table" and note.position.new_line or nil),
old_line = (type(note.position) == "table" and note.position.old_line or nil), old_line = (type(note.position) == "table" and note.position.old_line or nil),
resolvable = false, resolvable = false,

View File

@@ -6,7 +6,12 @@ local state = require("gitlab.state")
local List = require("gitlab.utils.list") local List = require("gitlab.utils.list")
local Popup = require("nui.popup") local Popup = require("nui.popup")
M.open = function() ---@class HelpPopupOpts
---@field discussion_tree boolean|nil Whether help popup is for the discussion tree
--- @param opts HelpPopupOpts|nil Table with options for the help popup
M.open = function(opts)
local help_opts = opts or {}
local bufnr = vim.api.nvim_get_current_buf() local bufnr = vim.api.nvim_get_current_buf()
local keymaps = vim.api.nvim_buf_get_keymap(bufnr, "n") local keymaps = vim.api.nvim_buf_get_keymap(bufnr, "n")
local help_content_lines = List.new(keymaps):reduce(function(agg, keymap) local help_content_lines = List.new(keymaps):reduce(function(agg, keymap)
@@ -17,6 +22,7 @@ M.open = function()
return agg return agg
end, {}) end, {})
if help_opts.discussion_tree then
table.insert(help_content_lines, "") table.insert(help_content_lines, "")
table.insert( table.insert(
help_content_lines, help_content_lines,
@@ -27,16 +33,17 @@ M.open = function()
state.settings.discussion_tree.resolved state.settings.discussion_tree.resolved
) )
) )
end
local longest_line = u.get_longest_string(help_content_lines) local longest_line = u.get_longest_string(help_content_lines)
local opts = { "Help", state.settings.popup.help, longest_line + 3, #help_content_lines, 70 } local popup_opts = { "Help", state.settings.popup.help, longest_line + 3, #help_content_lines, 70 }
local help_popup = Popup(popup.create_popup_state(unpack(opts))) local help_popup = Popup(popup.create_popup_state(unpack(popup_opts)))
help_popup:on(event.BufLeave, function() help_popup:on(event.BufLeave, function()
help_popup:unmount() help_popup:unmount()
end) end)
popup.set_up_autocommands(help_popup, nil, vim.api.nvim_get_current_win(), opts) popup.set_up_autocommands(help_popup, nil, vim.api.nvim_get_current_win(), popup_opts)
help_popup:mount() help_popup:mount()

View File

@@ -18,7 +18,7 @@ local function get_colors_for_group(group)
local normal_bg = vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(group)), "bg") local normal_bg = vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(group)), "bg")
return { fg = normal_fg, bg = normal_bg } return { fg = normal_fg, bg = normal_bg }
end end
vim.api.nvim_create_autocmd("VimEnter", { vim.api.nvim_create_autocmd({ "VimEnter", "ColorScheme" }, {
callback = function() callback = function()
vim.api.nvim_set_hl(0, "GitlabUsername", get_colors_for_group(discussion.username)) vim.api.nvim_set_hl(0, "GitlabUsername", get_colors_for_group(discussion.username))
vim.api.nvim_set_hl(0, "GitlabMention", get_colors_for_group(discussion.mention)) vim.api.nvim_set_hl(0, "GitlabMention", get_colors_for_group(discussion.mention))

View File

@@ -122,6 +122,9 @@ M.place_diagnostics = function(bufnr)
u.notify("Could not find Diffview view", vim.log.levels.ERROR) u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return return
end end
if vim.api.nvim_buf_get_name(bufnr) == "diffview://null" then
return
end
local ok, err = pcall(function() local ok, err = pcall(function()
local file_discussions = List.new(M.placeable_discussions):filter(function(discussion_or_note) local file_discussions = List.new(M.placeable_discussions):filter(function(discussion_or_note)

View File

@@ -19,6 +19,7 @@ M.run_job = function(endpoint, method, body, callback, on_error_callback)
-- success message or error message and details from the Go server and run the on_error_callback -- success message or error message and details from the Go server and run the on_error_callback
-- (if supplied for the job). -- (if supplied for the job).
local stderr = {} local stderr = {}
Job:new({ Job:new({
command = "curl", command = "curl",
args = args, args = args,

View File

@@ -110,6 +110,9 @@ end
---@param line_number number Line number from the discussion node. ---@param line_number number Line number from the discussion node.
---@param new_buffer boolean If true, jump to the NEW SHA. ---@param new_buffer boolean If true, jump to the NEW SHA.
M.jump = function(file_name, old_file_name, line_number, new_buffer) M.jump = function(file_name, old_file_name, line_number, new_buffer)
-- Draft comments don't have `old_file_name` set
old_file_name = old_file_name or file_name
if M.tabnr == nil then if M.tabnr == nil then
u.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR) u.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR)
return return
@@ -240,7 +243,7 @@ end
---@return string|nil ---@return string|nil
M.get_current_file_oldpath = function() M.get_current_file_oldpath = function()
local file_data = M.get_current_file_data() local file_data = M.get_current_file_data()
return file_data and file_data.oldpath return file_data and file_data.oldpath or file_data.path
end end
---Tell whether current file is renamed or not ---Tell whether current file is renamed or not

View File

@@ -62,6 +62,7 @@ M.settings = {
}, },
}, },
connection_settings = { connection_settings = {
proxy = "",
insecure = false, insecure = false,
remote = "origin", remote = "origin",
}, },
@@ -115,6 +116,7 @@ M.settings = {
switch_view = "c", switch_view = "c",
toggle_tree_type = "i", toggle_tree_type = "i",
publish_draft = "P", publish_draft = "P",
toggle_date_format = "dt",
toggle_draft_mode = "D", toggle_draft_mode = "D",
toggle_sort_method = "st", toggle_sort_method = "st",
toggle_node = "t", toggle_node = "t",
@@ -169,6 +171,7 @@ M.settings = {
draft = "", draft = "",
tree_type = "simple", tree_type = "simple",
draft_mode = false, draft_mode = false,
relative_date = true,
}, },
emojis = { emojis = {
formatter = nil, formatter = nil,