From c8a0267ba690b01af255d19c6089a307d43fc201 Mon Sep 17 00:00:00 2001 From: johnybx Date: Sat, 21 Oct 2023 17:39:37 +0200 Subject: [PATCH] Add option to choose random server port (#65) Updates the plugin to automatically choose a random port when started when no port is provided. This will allow multiple Neovim instances to open at the same time in different projects. --- README.md | 23 ++++++----- cmd/main.go | 55 ++++++++++++++++++++------ lua/gitlab/server.lua | 83 +++++++++++++++++++++++++++------------ lua/gitlab/state.lua | 12 +++--- lua/gitlab/utils/init.lua | 12 +----- 5 files changed, 119 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 7258144..2e47043 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ And a lot more! https://github.com/harrisoncramer/gitlab.nvim/assets/32515581/50f44eaf-5f99-4cb3-93e9-ed66ace0f675 - ## Requirements - Go >= v1.19 @@ -22,10 +21,10 @@ https://github.com/harrisoncramer/gitlab.nvim/assets/32515581/50f44eaf-5f99-4cb3 1. Install Go 2. Install reviewer: delta or diffview -2. Add configuration (see Installation section) -3. Checkout your feature branch: `git checkout feature-branch` -4. Open Neovim -5. Run `:lua require("gitlab").review()` to open the reviewer pane +3. Add configuration (see Installation section) +4. Checkout your feature branch: `git checkout feature-branch` +5. Open Neovim +6. Run `:lua require("gitlab").review()` to open the reviewer pane ## Installation @@ -85,7 +84,7 @@ Here is the default setup function. All of these values are optional, and if you ```lua require("gitlab").setup({ - port = 21036, -- The port of the Go server, which runs in the background + 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 reviewer = "delta", -- The reviewer type ("delta" or "diffview") attachment_dir = nil, -- The local directory for files (see the "summary" section) @@ -138,13 +137,13 @@ require("gitlab").setup({ ## Usage -First, check out the branch that you want to review locally. +First, check out the branch that you want to review locally. ``` git checkout feature-branch ``` -Then open Neovim. The `project_id` you specify in your configuration file must match the project_id of the Gitlab project your terminal is inside of. +Then open Neovim. The `project_id` you specify in your configuration file must match the project_id of the Gitlab project your terminal is inside of. ### Summary @@ -169,9 +168,9 @@ The reviewer is Delta by default, but you can configure the plugin to use Diffvi ### Discussions and Notes -Gitlab groups threads of comments together into "discussions." +Gitlab groups threads of comments together into "discussions." -To display all discussions for the current MR, use the `toggle_discussions` action, which will show the discussions in a split window. +To display all discussions for the current MR, use the `toggle_discussions` action, which will show the discussions in a split window. ```lua require("gitlab").toggle_discussions() @@ -189,7 +188,7 @@ require("gitlab").create_note() ### Uploading Files -To attach a file to an MR description, reply, comment, and so forth use the `settings.popup.perform_linewise_action` keybinding when the the popup is open. This will open a picker that will look in the directory you specify in the `settings.attachment_dir` folder (this must be an absolute path) for files. +To attach a file to an MR description, reply, comment, and so forth use the `settings.popup.perform_linewise_action` keybinding when the the popup is open. This will open a picker that will look in the directory you specify in the `settings.attachment_dir` folder (this must be an absolute path) for files. When you have picked the file, it will be added to the current buffer at the current line. @@ -277,7 +276,7 @@ This is the API call that is happening from within Neovim when you run the `summ If you are able to build and start the Go server and hit the endpoint successfully for the action you are trying to run (such as creating a comment or approving a merge request) then something is wrong with the Lua code. In that case, please file a bug report. -This Go server, in turn, writes logs to the log path that is configured in your setup function. These are written by default to `~/.cache/nvim/gitlab.nvim.log` and will be written each time the server reaeches out to Gitlab. +This Go server, in turn, writes logs to the log path that is configured in your setup function. These are written by default to `~/.cache/nvim/gitlab.nvim.log` and will be written each time the server reaeches out to Gitlab. If the Golang server is not starting up correctly, please check your `.gitlab.nvim` file and your setup function. You can, however, try running the Golang server independently of Neovim. For instance, to start it up for a certain project, navigate to your plugin directory, and build the binary (these are instructions for Lazy) and move that binary to your project. You can then try running the binary directly, or even with a debugger like Delve: diff --git a/cmd/main.go b/cmd/main.go index 041bce3..76a010f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "log" + "net" "net/http" "os" "os/exec" "strings" + "time" ) func main() { @@ -29,6 +31,7 @@ func main() { } m := http.NewServeMux() + m.Handle("/ping", http.HandlerFunc(PingHandler)) m.Handle("/mr/summary", withGitlabContext(http.HandlerFunc(SummaryHandler), c)) m.Handle("/mr/attachment", withGitlabContext(http.HandlerFunc(AttachmentHandler), c)) m.Handle("/mr/reviewer", withGitlabContext(http.HandlerFunc(ReviewersHandler), c)) @@ -44,19 +47,45 @@ func main() { m.Handle("/pipeline", withGitlabContext(http.HandlerFunc(PipelineHandler), c)) m.Handle("/job", withGitlabContext(http.HandlerFunc(JobHandler), c)) - port := fmt.Sprintf(":%s", os.Args[3]) - server := &http.Server{ - Addr: port, - Handler: m, + port := os.Args[3] + if port == "" { + // port was not specified + port = "0" + } + addr := fmt.Sprintf("localhost:%s", port) + listener, err := net.Listen("tcp", addr) + if err != nil { + log.Fatal(err) + fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err) + os.Exit(1) + } + listner_port := listener.Addr().(*net.TCPAddr).Port + + errCh := make(chan error) + go func() { + err := http.Serve(listener, m) + errCh <- err + }() + + go func() { + for i := 0; i < 10; i++ { + resp, err := http.Get("http://localhost:" + fmt.Sprintf("%d", listner_port) + "/ping") + if resp.StatusCode == 200 && err == nil { + /* This print is detected by the Lua code and used to fetch project information */ + fmt.Println("Server started on port: ", listner_port) + return + } + // Wait for healthcheck to pass - at most 1 sec. + time.Sleep(100 * time.Microsecond) + } + errCh <- err + }() + + if err := <-errCh; err != nil { + fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err) + os.Exit(1) } - done := make(chan bool) - go server.ListenAndServe() - - /* This print is detected by the Lua code and used to fetch project information */ - fmt.Println("Server started.") - - <-done } func withGitlabContext(next http.HandlerFunc, c Client) http.Handler { @@ -77,3 +106,7 @@ func getCurrentBranch() (res string, e error) { return strings.TrimSpace(string(output)), nil } +func PingHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintln(w, "pong") +} diff --git a/lua/gitlab/server.lua b/lua/gitlab/server.lua index 9e3e38b..836d90d 100644 --- a/lua/gitlab/server.lua +++ b/lua/gitlab/server.lua @@ -2,45 +2,75 @@ -- the Golang server. The Go server is responsible for making API calls -- to Gitlab and returning the data local state = require("gitlab.state") -local u = require("gitlab.utils") -local M = {} +local u = require("gitlab.utils") +local M = {} -- Starts the Go server and call the callback provided -M.start = function(callback) +M.start = function(callback) + local empty_port = "''" + local port = state.settings.port or empty_port + local parsed_port = nil + local callback_called = false local command = state.settings.bin - .. " " - .. state.settings.project_id - .. " " - .. state.settings.gitlab_url - .. " " - .. state.settings.port - .. " " - .. state.settings.auth_token - .. " " - .. state.settings.log_path + .. " " + .. state.settings.project_id + .. " " + .. state.settings.gitlab_url + .. " " + .. port + .. " " + .. state.settings.auth_token + .. " " + .. state.settings.log_path - vim.fn.jobstart(command, { - on_stdout = function(job_id) - if job_id <= 0 then - vim.notify("Could not start gitlab.nvim binary", vim.log.levels.ERROR) - else + local job_id = vim.fn.jobstart(command, { + on_stdout = function(_, data) + -- if port was not provided then we need to parse it from output of server + if parsed_port == nil then + for _, line in ipairs(data) do + port = line:match("Server started on port:%s+(%d+)") + if port ~= nil then + parsed_port = port + state.settings.port = port + break + end + end + end + + -- This assumes that first output of server will be parsable and port will be correctly set. + -- Make sure that this actually check if port was correctly parsed based on server output + -- because server outputs port only if it started successfully. + if parsed_port ~= nil and not callback_called then callback() + callback_called = true + elseif not callback_called then + vim.notify("Failed to parse server port", vim.log.levels.ERROR) end end, on_stderr = function(_, errors) - local err_msg = '' + local err_msg = "" for _, err in ipairs(errors) do if err ~= "" and err ~= nil then err_msg = err_msg .. err .. "\n" end end - if err_msg ~= '' then vim.notify(err_msg, vim.log.levels.ERROR) end - end + if err_msg ~= "" then + vim.notify(err_msg, vim.log.levels.ERROR) + end + end, + on_exit = function(job_id, exit_code, ...) + vim.notify( + "Golang gitlab server exited: job_id: " .. job_id .. ", exit_code: " .. exit_code, + vim.log.levels.ERROR + ) + end, }) + if job_id <= 0 then + vim.notify("Could not start gitlab.nvim binary", vim.log.levels.ERROR) + end end - -- Builds the Go binary M.build = function(override) local file_path = u.current_file_path() @@ -50,12 +80,13 @@ M.build = function(override) if not override then local binary_exists = vim.loop.fs_stat(state.settings.bin) - if binary_exists ~= nil then return end + if binary_exists ~= nil then + return + end end - local cmd = u.is_windows() and - 'cd %s\\cmd && go build -o bin.exe && move bin.exe ..\\' or - 'cd %s/cmd && go build -o bin && mv bin ../bin' + local cmd = u.is_windows() and "cd %s\\cmd && go build -o bin.exe && move bin.exe ..\\" + or "cd %s/cmd && go build -o bin && mv bin ../bin" local command = string.format(cmd, state.settings.bin_path) local null = u.is_windows() and " >NUL" or " > /dev/null" diff --git a/lua/gitlab/state.lua b/lua/gitlab/state.lua index 57432f8..2811ebc 100644 --- a/lua/gitlab/state.lua +++ b/lua/gitlab/state.lua @@ -7,11 +7,11 @@ local u = require("gitlab.utils") local M = {} -- These are the default settings for the plugin -M.settings = { - port = 21036, +M.settings = { + port = nil, -- choose random port log_path = (vim.fn.stdpath("cache") .. "/gitlab.nvim.log"), reviewer = "delta", - attachment_dir = '', + attachment_dir = "", popup = { exit = "", perform_action = "s", @@ -29,15 +29,15 @@ M.settings = { relative = "editor", position = "left", size = "20%", - resolved = '✓', - unresolved = '', + resolved = "✓", + unresolved = "", }, review_pane = { delta = { added_file = "", modified_file = "", removed_file = "", - } + }, }, pipeline = { created = "", diff --git a/lua/gitlab/utils/init.lua b/lua/gitlab/utils/init.lua index 822a3f5..5f36d87 100644 --- a/lua/gitlab/utils/init.lua +++ b/lua/gitlab/utils/init.lua @@ -126,20 +126,10 @@ M.create_popup_state = function(title, width, height) end M.merge = function(defaults, overrides) - local result = {} if type(defaults) == "table" and M.table_size(defaults) == 0 and type(overrides) == "table" then return overrides end - - for key, value in pairs(defaults) do - if type(value) == "table" then - result[key] = M.merge(value, overrides[key] or {}) - else - result[key] = overrides[key] or value - end - end - - return result + return vim.tbl_deep_extend("force", defaults, overrides) end M.join = function(tbl, separator)