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
11 changes: 7 additions & 4 deletions .github/workflows/lua.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,15 @@ jobs:
with:
neovim: true
version: ${{ matrix.nvim_version }}
- name: Install luajit
uses: leafo/gh-actions-lua@v10
- uses: leafo/gh-actions-lua@v11
with:
luaVersion: "luajit-openresty"
- name: Install luarocks
uses: leafo/gh-actions-luarocks@v4
- uses: hishamhm/gh-actions-luarocks@master
with:
luaRocksVersion: "3.12.0"
- name: build
run: |
luarocks install busted
- name: Run tests
shell: bash
run: |
Expand Down
5 changes: 3 additions & 2 deletions after/syntax/gitlab.vim
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ endif
let expanders = '^\s*\%(' . g:gitlab_discussion_tree_expander_open . '\|' . g:gitlab_discussion_tree_expander_closed . '\)'
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 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 state = ' \%(' . published . '\|' . g:gitlab_discussion_tree_draft . '\)'
Expand Down
15 changes: 12 additions & 3 deletions cmd/app/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"

"github.com/harrisoncramer/gitlab.nvim/cmd/app/git"
"github.com/hashicorp/go-retryablehttp"
Expand Down Expand Up @@ -66,10 +67,18 @@ 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.HTTPClient.Transport = tr
retryClient.RetryMax = 0
gitlabOptions = append(gitlabOptions, gitlab.WithHTTPClient(retryClient.HTTPClient))
gitlabOptions = append(gitlabOptions, gitlab.WithoutRetries())

client, err := gitlab.NewClient(pluginOptions.AuthToken, gitlabOptions...)

Expand Down Expand Up @@ -99,11 +108,11 @@ func InitProjectSettings(c *Client, gitInfo git.GitData) (*ProjectInfo, error) {
project, _, err := c.GetProject(gitInfo.ProjectPath(), &opt)

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 {
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)
Expand Down
8 changes: 7 additions & 1 deletion cmd/app/comment_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,19 @@ type RequestWithPosition interface {
func buildCommentPosition(commentWithPositionData RequestWithPosition) *gitlab.PositionOptions {
positionData := commentWithPositionData.GetPositionData()

// If the file has been renamed, then this is a relevant part of the payload
oldFileName := positionData.OldFileName
if oldFileName == "" {
oldFileName = positionData.FileName
}

opt := &gitlab.PositionOptions{
PositionType: &positionData.Type,
StartSHA: &positionData.StartCommitSHA,
HeadSHA: &positionData.HeadCommitSHA,
BaseSHA: &positionData.BaseCommitSHA,
NewPath: &positionData.FileName,
OldPath: &positionData.OldFileName,
OldPath: &oldFileName,
NewLine: positionData.NewLine,
OldLine: positionData.OldLine,
}
Expand Down
1 change: 1 addition & 0 deletions cmd/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type PluginOptions struct {
} `json:"debug"`
ChosenMrIID int `json:"chosen_mr_iid"`
ConnectionSettings struct {
Proxy string `json:"proxy"`
Insecure bool `json:"insecure"`
Remote string `json:"remote"`
} `json:"connection_settings"`
Expand Down
3 changes: 3 additions & 0 deletions doc/gitlab.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ you call this function with no values the defaults will be used:
},
},
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
remote = "origin", -- The default remote that your MRs target
},
Expand Down Expand Up @@ -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
toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name"
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_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
Expand Down Expand Up @@ -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
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
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)
-- If using lualine, please add "gitlab" to disabled file types, otherwise you will not see the winbar.
},
Expand Down
65 changes: 25 additions & 40 deletions lua-test.sh
Original file line number Diff line number Diff line change
@@ -1,59 +1,44 @@
#!/usr/bin/env bash
#
# Setup and run tests for lua part of gitlab.nvim.
#
# 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.
#
# Requires `luarocks`, `git`, and `nvim` installed.
#
set -e

LUA_VERSION="5.1"
set -euo pipefail

PLUGINS_FOLDER="tests/plugins"
PLUGINS=(
"https://github.com/MunifTanjim/nui.nvim"
"https://github.com/nvim-lua/plenary.nvim"
"https://github.com/sindrets/diffview.nvim"
"https://github.com/MunifTanjim/nui.nvim"
"https://github.com/nvim-lua/plenary.nvim"
"https://github.com/sindrets/diffview.nvim"
)

if ! command -v luarocks > /dev/null 2>&1; then
echo "You need to have luarocks installed in order to run tests."
exit 1
fi

if ! command -v git > /dev/null 2>&1; then
echo "You need to have git installed in order to run tests."
exit 1
if ! command -v luarocks >/dev/null 2>&1; then
echo "Error: luarocks not found. Please install LuaRocks." >&2
exit 1
fi

if ! luarocks --lua-version=$LUA_VERSION which busted > /dev/null 2>&1; then
echo "Installing busted."
luarocks init
luarocks config --scope project lua_version "$LUA_VERSION"
luarocks install --lua-version="$LUA_VERSION" busted
if ! command -v git >/dev/null 2>&1; then
echo "Error: git not found. Please install Git." >&2
exit 1
fi

for arg in "$@"; do
if [[ $arg =~ "--coverage" ]] && ! luarocks --lua-version=$LUA_VERSION which luacov > /dev/null 2>&1; then
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
if ! command -v nvim >/dev/null 2>&1; then
echo "Error: nvim not found. Please install Neovim." >&2
exit 1
fi
done

# Clone test plugin dependencies
mkdir -p "$PLUGINS_FOLDER"
for plugin in "${PLUGINS[@]}"; do
plugin_name=${plugin##*/}
plugin_folder="$PLUGINS_FOLDER/$plugin_name"

# Check if plugin was already downloaded
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

plugin_name="${plugin##*/}"
plugin_folder="$PLUGINS_FOLDER/$plugin_name"
if [[ ! -d "$plugin_folder/.git" ]]; then
echo "Cloning $plugin..."
git clone --depth 1 "$plugin" "$plugin_folder"

fi
done

nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@"
# Run tests
echo "Running tests with Neovim..."
nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@"
6 changes: 3 additions & 3 deletions lua/gitlab/actions/comment.lua
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ M.create_comment_layout = function(opts)
title = "Note"
user_settings = popup_settings.note
else
-- TODO: investigate why `old_file_name` is in fact the new name for renamed files!
local file_name = M.location.reviewer_data.old_file_name ~= "" and M.location.reviewer_data.old_file_name
or M.location.reviewer_data.file_name
local file_name = (M.location.reviewer_data.new_sha_focused or M.location.reviewer_data.old_file_name == "")
and M.location.reviewer_data.file_name
or M.location.reviewer_data.old_file_name
Comment on lines +156 to +158
Copy link

Copilot AI Jun 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This inline boolean expression is hard to read and maintain. Consider using a simple if/else block to clearly express which path should be used.

Suggested change
local file_name = (M.location.reviewer_data.new_sha_focused or M.location.reviewer_data.old_file_name == "")
and M.location.reviewer_data.file_name
or M.location.reviewer_data.old_file_name
local file_name
if M.location.reviewer_data.new_sha_focused or M.location.reviewer_data.old_file_name == "" then
file_name = M.location.reviewer_data.file_name
else
file_name = M.location.reviewer_data.old_file_name
end

Copilot uses AI. Check for mistakes.
title =
popup.create_title("Comment", file_name, M.location.visual_range.start_line, M.location.visual_range.end_line)
user_settings = popup_settings.comment
Expand Down
8 changes: 6 additions & 2 deletions lua/gitlab/actions/common.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ M.build_note_header = function(note)
if note.note then
return "@" .. state.USER.username .. " " .. state.settings.discussion_tree.draft
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

M.switch_can_edit_bufs = function(bool, ...)
Expand Down Expand Up @@ -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
elseif new_line ~= nil then
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
u.notify("Error getting new or old line for range", vim.log.levels.ERROR)
return 1, 1, false
Expand Down
19 changes: 18 additions & 1 deletion lua/gitlab/actions/discussions/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,16 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
})
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
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
Expand Down Expand Up @@ -725,7 +735,7 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)

if keymaps.help then
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

Expand Down Expand Up @@ -809,6 +819,13 @@ M.toggle_sort_method = function()
M.rebuild_view(false, true)
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
---@param tree NuiTree
---@return boolean
Expand Down
24 changes: 19 additions & 5 deletions lua/gitlab/actions/draft_notes/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
-- under lua/gitlab/actions/discussions/init.lua
local common = require("gitlab.actions.common")
local discussion_tree = require("gitlab.actions.discussions.tree")
local git = require("gitlab.git")
local job = require("gitlab.job")
local NuiTree = require("nui.tree")
local List = require("gitlab.utils.list")
Expand Down Expand Up @@ -85,12 +86,20 @@ end

---Publishes all draft notes and comments. Re-renders all discussion views.
M.confirm_publish_all_drafts = function()
if not git.check_current_branch_up_to_date_on_remote(vim.log.levels.ERROR) then
return
end
local body = { publish_all = true }
job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
u.notify(data.message, vim.log.levels.INFO)
state.DRAFT_NOTES = {}
local discussions = require("gitlab.actions.discussions")
discussions.rebuild_view(false, true)
require("gitlab.actions.discussions").rebuild_view(false, true)
end, function()
require("gitlab.actions.discussions").rebuild_view(false, true)
u.notify(
"Draft(s) may have been published despite the error. Check the discussion tree. Try publishing drafts individually.",
vim.log.levels.WARN
)
end)
end

Expand All @@ -99,6 +108,9 @@ end
---and re-render it.
---@param tree NuiTree
M.confirm_publish_draft = function(tree)
if not git.check_current_branch_up_to_date_on_remote(vim.log.levels.ERROR) then
return
end
local current_node = tree:get_node()
local note_node = common.get_note_node(tree, current_node)
local root_node = common.get_root_node(tree, current_node)
Expand All @@ -111,12 +123,13 @@ M.confirm_publish_draft = function(tree)
---@type integer
local note_id = note_node.is_root and root_node.id or note_node.id
local body = { note = note_id }
local unlinked = tree.bufnr == require("gitlab.actions.discussions").unlinked_bufnr
job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
u.notify(data.message, vim.log.levels.INFO)

local discussions = require("gitlab.actions.discussions")
local unlinked = tree.bufnr == discussions.unlinked_bufnr
M.rebuild_view(unlinked)
end, function()
M.rebuild_view(unlinked)
u.notify("Draft may have been published despite the error. Check the discussion tree.", vim.log.levels.WARN)
end)
end

Expand All @@ -142,6 +155,7 @@ M.build_root_draft_note = function(note)
id = note.id,
root_note_id = note.id,
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),
old_line = (type(note.position) == "table" and note.position.old_line or nil),
resolvable = false,
Expand Down
Loading
Loading