From 6ca2f0ab6711a8f113ffd07759bf03ad72f10945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lan=20Cr=C3=ADstoffer?= Date: Thu, 12 Feb 2026 14:47:17 +0100 Subject: [PATCH 1/2] feat: better handling of server installation path The plugin should not assume its folder is writable. Instead, try to search for a writable folder by checking the data folder and the runtime paths. Also allows the user to give the path to the server's executable intead of always compiling it. All of this makes the plugin friendlier to restricted/read-only environments, like nix. --- doc/gitlab.nvim.txt | 7 +++-- flake.lock | 61 +++++++++++++++++++++++++++++++++++++++++++ flake.nix | 47 +++++++++++++++++++++++++++++++++ lua/gitlab/init.lua | 2 +- lua/gitlab/job.lua | 2 +- lua/gitlab/server.lua | 61 +++++++++++++++++++++++++++++++++++-------- lua/gitlab/state.lua | 5 +++- 7 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/doc/gitlab.nvim.txt b/doc/gitlab.nvim.txt index 2db9f633..4f61c03b 100644 --- a/doc/gitlab.nvim.txt +++ b/doc/gitlab.nvim.txt @@ -144,7 +144,10 @@ Here is the default setup function. All of these values are optional, and if you call this function with no values the defaults will be used: >lua require("gitlab").setup({ - port = nil, -- The port of the Go server, which runs in the background, if omitted or `nil` the port will be chosen automatically + server = { + binary = nil, -- The path to the server binary. If omitted or nil, the server will be built + port = nil, -- The port of the Go server, which runs in the background. If omitted or `nil` the port will be chosen automatically + }, log_path = vim.fn.stdpath("cache") .. "/gitlab.nvim.log", -- Log path for the Go server config_path = nil, -- Custom path for `.gitlab.nvim` file, please read the "Connecting to Gitlab" section debug = { @@ -678,7 +681,7 @@ by a |motion|. Either the operator or the motion can be preceded by a count, so that `3sj` is equivalent to `s3j`, and they both create a comment for the current line and three more lines downwards. Similarly, both `2s`|ap| and `s2`|ap| create a suggestion -for two "outer" paragraphs. +for two "outer" paragraphs. The operators force |linewise| visual selection, so they work correctly even if the motion itself works |characterwise| (e.g., |i(| for selecting the inner diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..8f8b8f30 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1770770419, + "narHash": "sha256-iKZMkr6Cm9JzWlRYW/VPoL0A9jVKtZYiU4zSrVeetIs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6c5e707c6b5339359a9a9e215c5e66d6d802fd7a", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..a414bbfd --- /dev/null +++ b/flake.nix @@ -0,0 +1,47 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, flake-utils, nixpkgs }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; + gitlab-nvim-server = pkgs.buildGoModule { + pname = "gitlab.nvim-server"; + version = "git"; + src = ./.; + vendorHash = "sha256-OLAKTdzqynBDHqWV5RzIpfc3xZDm6uYyLD4rxbh0DMg="; + postInstall = '' + cp -r ${./cmd/config} $out/bin/config + mv $out/bin/cmd $out/bin/gitlab.nvim + ''; + }; + gitlab-nvim = pkgs.vimUtils.buildVimPlugin { + name = "gitlab.nvim"; + src = ./.; + doCheck = false; + }; + in + rec { + formatter = pkgs.nixpkgs-fmt; + packages.gitlab-nvim-server = gitlab-nvim-server; + packages.gitlab-nvim = gitlab-nvim; + packages.default = packages.gitlab-nvim; + devShell = pkgs.mkShell { + packages = with pkgs; [ + git + go + go-tools + golangci-lint + luajitPackages.busted + luajitPackages.luacheck + luajitPackages.luarocks + neovim + stylua + ]; + }; + } + ); +} diff --git a/lua/gitlab/init.lua b/lua/gitlab/init.lua index 8c52c73b..b492c563 100644 --- a/lua/gitlab/init.lua +++ b/lua/gitlab/init.lua @@ -44,8 +44,8 @@ local function setup(args) return end - server.build() -- Builds the Go binary if it doesn't exist state.merge_settings(args) -- Merges user settings with default settings + server.build() -- Builds the Go binary if it doesn't exist state.set_global_keymaps() -- Sets keymaps that are not bound to a specific buffer require("gitlab.colors") -- Sets colors reviewer.init() diff --git a/lua/gitlab/job.lua b/lua/gitlab/job.lua index 128591be..380246ec 100644 --- a/lua/gitlab/job.lua +++ b/lua/gitlab/job.lua @@ -6,7 +6,7 @@ local M = {} M.run_job = function(endpoint, method, body, callback) local state = require("gitlab.state") - local args = { "-s", "-X", (method or "POST"), string.format("localhost:%s", state.settings.port) .. endpoint } + local args = { "-s", "-X", (method or "POST"), string.format("localhost:%s", state.settings.server.port) .. endpoint } if body ~= nil then local encoded_body = vim.json.encode(body) diff --git a/lua/gitlab/server.lua b/lua/gitlab/server.lua index d9ff6305..55f04de8 100644 --- a/lua/gitlab/server.lua +++ b/lua/gitlab/server.lua @@ -30,7 +30,7 @@ end -- Starts the Go server and call the callback provided M.start = function(callback) - local port = tonumber(state.settings.port) or 0 + local port = tonumber(state.settings.server.port) or 0 local parsed_port = nil local callback_called = false @@ -51,7 +51,7 @@ M.start = function(callback) settings = settings:gsub('"', '\\"') end - local command = string.format('"%s" "%s"', state.settings.bin, settings) + local command = string.format('"%s" "%s"', state.settings.server.binary, settings) local job_id = vim.fn.jobstart(command, { on_stdout = function(_, data) @@ -61,7 +61,7 @@ M.start = function(callback) port = line:match("Server started on port:%s+(%d+)") if port ~= nil then parsed_port = port - state.settings.port = port + state.settings.server.port = port break end end @@ -105,26 +105,59 @@ end -- Builds the Go binary with the current Git tag. M.build = function(override) local file_path = u.current_file_path() - local parent_dir = vim.fn.fnamemodify(file_path, ":h:h:h:h") + state.settings.root_path = vim.fn.fnamemodify(file_path, ":h:h:h:h") + + -- If the user provided a path to the server, don't build it. + if state.settings.server.binary ~= nil then + local binary_exists = vim.loop.fs_stat(state.settings.server.binary) + if binary_exists == nil then + u.notify( + string.format("The user-provided server path (%s) does not exist.", state.settings.server.binary), + vim.log.levels.ERROR + ) + end + return + end + + -- If the user did not provide a path, we build it and place it in either the data path, or the + -- first writable path we find in the runtime. + local datapath = vim.fn.stdpath("data") + local runtimepath = vim.api.nvim_list_runtime_paths() + table.insert(runtimepath, 1, datapath) + + local bin_folder + for _, path in ipairs(runtimepath) do + local ok, err = vim.loop.fs_access(path, "w") + if err == nil and ok ~= nil and ok then + bin_folder = path .. u.path_separator .. "gitlab.nvim" .. u.path_separator .. "bin" + if vim.fn.mkdir(bin_folder, "p") == 1 then + state.settings.server.binary = bin_folder .. u.path_separator .. "server" + break + end + end + end - local bin_name = u.is_windows() and "bin.exe" or "bin" - state.settings.root_path = parent_dir - state.settings.bin = parent_dir .. u.path_separator .. "cmd" .. u.path_separator .. bin_name + if state.settings.server.binary == nil then + u.notify("Could not find a writable folder in the runtime path to save the server to.", vim.log.levels.ERROR) + return + end if not override then - local binary_exists = vim.loop.fs_stat(state.settings.bin) + local binary_exists = vim.loop.fs_stat(state.settings.server.binary) if binary_exists ~= nil then return end end - local version_output = vim.system({ "git", "describe", "--tags", "--always" }, { cwd = parent_dir }):wait() + local version_output = vim + .system({ "git", "describe", "--tags", "--always" }, { cwd = state.settings.root_path }) + :wait() local version = version_output.code == 0 and vim.trim(version_output.stdout) or "unknown" local ldflags = string.format("-X main.Version=%s", version) local res = vim .system( - { "go", "build", "-ldflags", ldflags, "-o", bin_name }, + { "go", "build", "-buildvcs=false", "-ldflags", ldflags, "-o", state.settings.server.binary }, { cwd = state.settings.root_path .. u.path_separator .. "cmd" } ) :wait() @@ -133,6 +166,12 @@ M.build = function(override) u.notify(string.format("Failed to install with status code %d:\n%s", res.code, res.stderr), vim.log.levels.ERROR) return false end + + local Path = require("plenary.path") + local src = Path:new(state.settings.root_path .. u.path_separator .. "cmd" .. u.path_separator .. "config") + local dest = Path:new(bin_folder .. u.path_separator .. "config") + src:copy({ destination = dest, recursive = true, override = true }) + u.notify("Installed successfully!", vim.log.levels.INFO) return true end @@ -185,7 +224,7 @@ M.get_version = function(callback) local version_output = vim.system({ "git", "describe", "--tags", "--always" }, { cwd = parent_dir }):wait() local plugin_version = version_output.code == 0 and vim.trim(version_output.stdout) or "unknown" - local args = { "-s", "-X", "GET", string.format("localhost:%s/version", state.settings.port) } + local args = { "-s", "-X", "GET", string.format("localhost:%s/version", state.settings.server.port) } -- We call the "/version" endpoint here instead of through the regular jobs pattern because earlier versions of the plugin -- may not have it. We handle a 404 as an "unknown" version error. diff --git a/lua/gitlab/state.lua b/lua/gitlab/state.lua index d67e0c06..940b950a 100644 --- a/lua/gitlab/state.lua +++ b/lua/gitlab/state.lua @@ -45,7 +45,10 @@ end M.settings = { auth_provider = M.default_auth_provider, file_separator = u.path_separator, - port = nil, -- choose random port + server = { + binary = nil, + port = nil, + }, debug = { request = false, response = false, From bbcb640d3b1f9d65085f197fbd0324488bb93b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lan=20Cr=C3=ADstoffer?= Date: Fri, 13 Feb 2026 08:57:05 +0100 Subject: [PATCH 2/2] fix: set LC_TIME when testing, since the test expects it to be English. --- lua-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua-test.sh b/lua-test.sh index 6cf83dc9..300530c2 100755 --- a/lua-test.sh +++ b/lua-test.sh @@ -41,4 +41,4 @@ done # Run tests echo "Running tests with Neovim..." -nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@" +LC_TIME=en_US.UTF-8 nvim -u NONE -U NONE -N -i NONE -l tests/init.lua "$@"