diff --git a/.github/workflows/lua.yaml b/.github/workflows/lua.yaml index a899846..9ecfa48 100644 --- a/.github/workflows/lua.yaml +++ b/.github/workflows/lua.yaml @@ -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: | diff --git a/after/syntax/gitlab.vim b/after/syntax/gitlab.vim index d19182c..5922f29 100644 --- a/after/syntax/gitlab.vim +++ b/after/syntax/gitlab.vim @@ -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 . '\)' diff --git a/cmd/app/client.go b/cmd/app/client.go index d2caea9..c68beda 100644 --- a/cmd/app/client.go +++ b/cmd/app/client.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "github.com/harrisoncramer/gitlab.nvim/cmd/app/git" "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.HTTPClient.Transport = tr 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) 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) diff --git a/cmd/app/config.go b/cmd/app/config.go index dc40dfe..9d8bd0a 100644 --- a/cmd/app/config.go +++ b/cmd/app/config.go @@ -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"` diff --git a/doc/gitlab.nvim.txt b/doc/gitlab.nvim.txt index e1c38bc..19ae720 100644 --- a/doc/gitlab.nvim.txt +++ b/doc/gitlab.nvim.txt @@ -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 }, @@ -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 @@ -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. }, diff --git a/lua-test.sh b/lua-test.sh index 6351375..6cf83dc 100755 --- a/lua-test.sh +++ b/lua-test.sh @@ -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 +if ! command -v luarocks >/dev/null 2>&1; then + echo "Error: luarocks not found. Please install LuaRocks." >&2 + 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 git >/dev/null 2>&1; then + echo "Error: git not found. Please install Git." >&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 nvim >/dev/null 2>&1; then + echo "Error: nvim not found. Please install Neovim." >&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 -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 "$@" diff --git a/lua/gitlab/actions/common.lua b/lua/gitlab/actions/common.lua index 6ad224d..538d498 100644 --- a/lua/gitlab/actions/common.lua +++ b/lua/gitlab/actions/common.lua @@ -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, ...) @@ -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 diff --git a/lua/gitlab/actions/discussions/init.lua b/lua/gitlab/actions/discussions/init.lua index 59e381f..c22fa76 100644 --- a/lua/gitlab/actions/discussions/init.lua +++ b/lua/gitlab/actions/discussions/init.lua @@ -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 @@ -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 @@ -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 diff --git a/lua/gitlab/actions/draft_notes/init.lua b/lua/gitlab/actions/draft_notes/init.lua index 5348f19..1f0e0e1 100755 --- a/lua/gitlab/actions/draft_notes/init.lua +++ b/lua/gitlab/actions/draft_notes/init.lua @@ -155,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, diff --git a/lua/gitlab/actions/help.lua b/lua/gitlab/actions/help.lua index 991a162..3fed155 100644 --- a/lua/gitlab/actions/help.lua +++ b/lua/gitlab/actions/help.lua @@ -6,7 +6,12 @@ local state = require("gitlab.state") local List = require("gitlab.utils.list") 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 keymaps = vim.api.nvim_buf_get_keymap(bufnr, "n") local help_content_lines = List.new(keymaps):reduce(function(agg, keymap) @@ -17,26 +22,28 @@ M.open = function() return agg end, {}) - table.insert(help_content_lines, "") - table.insert( - help_content_lines, - string.format( - "%s = draft; %s = unlinked comment; %s = resolved", - state.settings.discussion_tree.draft, - state.settings.discussion_tree.unlinked, - state.settings.discussion_tree.resolved + if help_opts.discussion_tree then + table.insert(help_content_lines, "") + table.insert( + help_content_lines, + string.format( + "%s = draft; %s = unlinked comment; %s = resolved", + state.settings.discussion_tree.draft, + state.settings.discussion_tree.unlinked, + state.settings.discussion_tree.resolved + ) ) - ) + end 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 help_popup = Popup(popup.create_popup_state(unpack(opts))) + local popup_opts = { "Help", state.settings.popup.help, longest_line + 3, #help_content_lines, 70 } + local help_popup = Popup(popup.create_popup_state(unpack(popup_opts))) help_popup:on(event.BufLeave, function() help_popup:unmount() 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() diff --git a/lua/gitlab/colors.lua b/lua/gitlab/colors.lua index 54d557a..2d8bfc8 100644 --- a/lua/gitlab/colors.lua +++ b/lua/gitlab/colors.lua @@ -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") return { fg = normal_fg, bg = normal_bg } end -vim.api.nvim_create_autocmd("VimEnter", { +vim.api.nvim_create_autocmd({ "VimEnter", "ColorScheme" }, { callback = function() 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)) diff --git a/lua/gitlab/indicators/diagnostics.lua b/lua/gitlab/indicators/diagnostics.lua index e32e086..ccdd936 100644 --- a/lua/gitlab/indicators/diagnostics.lua +++ b/lua/gitlab/indicators/diagnostics.lua @@ -122,6 +122,9 @@ M.place_diagnostics = function(bufnr) u.notify("Could not find Diffview view", vim.log.levels.ERROR) return end + if vim.api.nvim_buf_get_name(bufnr) == "diffview://null" then + return + end local ok, err = pcall(function() local file_discussions = List.new(M.placeable_discussions):filter(function(discussion_or_note) diff --git a/lua/gitlab/job.lua b/lua/gitlab/job.lua index 3330821..bccd806 100644 --- a/lua/gitlab/job.lua +++ b/lua/gitlab/job.lua @@ -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 -- (if supplied for the job). local stderr = {} + Job:new({ command = "curl", args = args, diff --git a/lua/gitlab/reviewer/init.lua b/lua/gitlab/reviewer/init.lua index 8ddfd1f..46a6034 100644 --- a/lua/gitlab/reviewer/init.lua +++ b/lua/gitlab/reviewer/init.lua @@ -110,6 +110,9 @@ end ---@param line_number number Line number from the discussion node. ---@param new_buffer boolean If true, jump to the NEW SHA. 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 u.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR) return @@ -240,7 +243,7 @@ end ---@return string|nil M.get_current_file_oldpath = function() 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 ---Tell whether current file is renamed or not diff --git a/lua/gitlab/state.lua b/lua/gitlab/state.lua index 5262718..b7e2a46 100644 --- a/lua/gitlab/state.lua +++ b/lua/gitlab/state.lua @@ -62,6 +62,7 @@ M.settings = { }, }, connection_settings = { + proxy = "", insecure = false, remote = "origin", }, @@ -115,6 +116,7 @@ M.settings = { switch_view = "c", toggle_tree_type = "i", publish_draft = "P", + toggle_date_format = "dt", toggle_draft_mode = "D", toggle_sort_method = "st", toggle_node = "t", @@ -169,6 +171,7 @@ M.settings = { draft = "✎", tree_type = "simple", draft_mode = false, + relative_date = true, }, emojis = { formatter = nil,