@@ -0,0 +1,16 @@
+root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.{md,yml,yaml,toml,lua,vim}] +indent_size = 2 + +[*.go] +indent_style = tab +indent_size = 4
@@ -1,24 +0,0 @@
-name: Format and lint -on: [push, pull_request] - -jobs: - format: - name: stylua - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: JohnnyMorganz/stylua-action@1.0.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - version: 0.14.0 - args: --check . - - lint: - name: selene - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: NTBBloodbath/selene-action@v1.0.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --display-style=quiet .
@@ -0,0 +1,19 @@
+name: linters +on: [push, pull_request] + +jobs: + linters: + name: linters + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: JohnnyMorganz/stylua-action@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + args: --check . + + - uses: NTBBloodbath/selene-action@v1.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: .
@@ -0,0 +1,44 @@
+name: tests +on: [push, pull_request] + +jobs: + tests: + strategy: + matrix: + os: [ubuntu-latest] + nvim_version: + - nightly + - v0.7.0 + - v0.7.2 + - v0.8.0 + - v0.8.1 + - v0.8.2 + - v0.8.3 + - v0.9.0 + - v0.9.1 + - v0.9.2 + - v0.9.4 + - v0.9.5 + runs-on: ${{ matrix.os }} + steps: + - name: Install Task + uses: arduino/setup-task@v1 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v3 + + - name: Install Neovim + run: | + mkdir -p /tmp/nvim + wget -q https://github.com/neovim/neovim/releases/download/${{ matrix.nvim_version }}/nvim.appimage -O /tmp/nvim/nvim.appimage + cd /tmp/nvim + chmod a+x ./nvim.appimage + ./nvim.appimage --appimage-extract + echo "/tmp/nvim/squashfs-root/usr/bin/" >> $GITHUB_PATH + + - name: Run Tests + run: | + nvim --version + task test
@@ -0,0 +1,10 @@
+{ + "diagnostics.globals": [ + "describe", + "it", + "before_each", + "after_each", + "before_all", + "after_all" + ] +}
@@ -1,6 +1,14 @@
# Contributing to `gopher.nvim` -Thank you for looking to contributing +Thank you for taking the time to submit some code to gopher.nvim. It means a lot! + +### Task running + +In this codebase for running tasks is used [Taskfile](https://taskfile.dev). +You can install it with: +```bash +go install github.com/go-task/task/v3/cmd/task@latest +``` ### Styling and formatting@@ -8,6 +16,40 @@ Code is formatted by [stylua](https://github.com/JohnnyMorganz/StyLua) and linted using [selene](https://github.com/Kampfkarren/selene).
You can install these with: ```bash -cargo install stylua -cargo install selene +sudo pacman -S selene stylua +# or whatever is your package manager +# or way of installing pkgs +``` + +For formatting use this following commands, or setup your editor to integrate with selene/stylua: +```bash +task format +task format:check # will check if your code formatted +task lint +``` + +### Documentation + +Here we are using [mini.doc](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-doc.md) +for generating help files based on EmmyLua-like annotations in comments + +You can generate docs with: +```bash +task docgen +``` + +### Commit messages +We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it. + +### Testing + +For testing this plugins uses [plenary.nvim](https://github.com/nvim-lua/plenary.nvim). +All tests live in [/spec](https://github.com/olexsmir/gopher.nvim/tree/main/spec) dir. + +You can run tests with: +```bash +task test +# also there are some aliases for that +task tests +task spec ```
@@ -4,157 +4,230 @@ [](https://stand-with-ukraine.pp.ua)
Minimalistic plugin for Go development in Neovim written in Lua. -It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. +It's **NOT** an LSP tool, the main goal of this plugin is to add go tooling support in Neovim. -## Install +> If you want to use new and maybe undocumented, and unstable features you might use [develop](https://github.com/olexsmir/gopher.nvim/tree/develop) branch. -Pre-dependency: [go](https://github.com/golang/go) (tested on 1.17 and 1.18) +## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim)) + +Pre-dependency: + +- [Go](https://github.com/golang/go) +- `go` treesitter parser, install by `:TSInstall go` ```lua -use { +{ "olexsmir/gopher.nvim", - requires = { -- dependencies + ft = "go", + -- branch = "develop", -- if you want develop branch + -- keep in mind, it might break everything + dependencies = { "nvim-lua/plenary.nvim", "nvim-treesitter/nvim-treesitter", + "mfussenegger/nvim-dap", -- (optional) only if you use `gopher.dap` }, + -- (optional) will update plugin's deps on every update + build = function() + vim.cmd.GoInstallDeps() + end, + ---@type gopher.Config + opts = {}, } ``` -Also, run `TSInstall go` if `go` parser if isn't installed yet. +## Configuratoin -## Config +> [!IMPORTANT] +> +> If you need more info look `:h gopher.nvim` -By `.setup` function you can configure the plugin. - -Note: - -- `installer` does not install the tool in user set path +**Take a look at default options** ```lua require("gopher").setup { commands = { go = "go", gomodifytags = "gomodifytags", - gotests = "~/go/bin/gotests", -- also you can set custom command path + gotests = "gotests", impl = "impl", iferr = "iferr", + dlv = "dlv", + }, + gotests = { + -- gotests doesn't have template named "default" so this plugin uses "default" to set the default template + template = "default", + -- path to a directory containing custom test code templates + template_dir = nil, + -- switch table tests from using slice to map (with test name for the key) + -- works only with gotests installed from develop branch + named = false, + }, + gotag = { + transform = "snakecase", }, } ``` ## Features -1. Installation requires this go tool: +<!-- markdownlint-disable --> -```vim -:GoInstallDeps -``` +<details> + <summary> + <b>Install plugin's go deps</b> + </summary> -It will install next tools: + ```vim + :GoInstallDeps + ``` -- [gomodifytags](https://github.com/fatih/gomodifytags) -- [impl](https://github.com/josharian/impl) -- [gotests](https://github.com/cweill/gotests) -- [iferr](https://github.com/koron/iferr) + This will install the following tools: -2. Modify struct tags: - By default `json` tag will be added/removed, if not set: + - [gomodifytags](https://github.com/fatih/gomodifytags) + - [impl](https://github.com/josharian/impl) + - [gotests](https://github.com/cweill/gotests) + - [iferr](https://github.com/koron/iferr) + - [dlv](github.com/go-delve/delve/cmd/dlv) +</details> -```vim -:GoTagAdd json " For add json tag -:GoTagRm yaml " For remove yaml tag -``` +<details> + <summary> + <b>Add and remove tags for structs via <a href="https://github.com/fatih/gomodifytags">gomodifytags</a></b> + </summary> -3. Run `go mod` command: + By default `json` tag will be added/removed, if not set: -```vim -:GoMod tidy " Runs `go mod tidy` -:GoMod init asdf " Runs `go mod init asdf` -``` + ```vim + " add json tag + :GoTagAdd json -4. Run `go get` command + " remove yaml tag + :GoTagRm yaml + ``` -Link can have a `http` or `https` prefix. + ```lua + -- or you can use lua api + require("gopher").tags.add "xml" + require("gopher").tags.rm "proto" + ``` +</details> -You can provide more than one package url: +<details> + <summary> + <b>Generating tests via <a href="https://github.com/cweill/gotests">gotests</a></b> + </summary> -```vim -:GoGet github.com/gorilla/mux -``` + ```vim + " Generate one test for a specific function/method(one under cursor) + :GoTestAdd -5. Interface implementation + " Generate all tests for all functions/methods in the current file + :GoTestsAll -Command syntax: + " Generate tests for only exported functions/methods in the current file + :GoTestsExp + ``` -```vim -:GoImpl [receiver] [interface] + ```lua + -- or you can use lua api + require("gopher").test.add() + require("gopher").test.exported() + require("gopher").test.all() + ``` -" Also you can put cursor on the struct and run: -:GoImpl [interface] -``` + For named tests see `:h gopher.nvim-gotests-named` +</details> -Example of usage: +<details> + <summary> + <b>Run commands like <code>go mod/get/etc</code> inside of nvim</b> + </summary> -```vim -" Example -:GoImpl r Read io.Reader -" or simply put your cursor in the struct and run: -:GoImpl io.Reader -``` + ```vim + :GoGet github.com/gorilla/mux + + " Link can have an `http` or `https` prefix. + :GoGet https://github.com/lib/pq + + " You can provide more than one package url + :GoGet github.com/jackc/pgx/v5 github.com/google/uuid/ + + " go mod commands + :GoMod tidy + :GoMod init new-shiny-project -6. Generate tests with [gotests](https://github.com/cweill/gotests) + " go work commands + :GoWork sync -Generate one test for a specific function/method: + " run go generate in cwd + :GoGenerate -```vim -:GoTestAdd -``` + " run go generate for the current file + :GoGenerate % + ``` +</details> -Generate all tests for all functions/methods in current file: +<details> + <summary> + <b>Interface implementation via <a href="https://github.com/josharian/impl">impl<a></b> + </summary> -```vim -:GoTestsAll -``` + Syntax of the command: + ```vim + :GoImpl [receiver] [interface] -Generate tests only for exported functions/methods in current file: + " also you can put a cursor on the struct and run + :GoImpl [interface] + ``` -```vim -:GoTestsExp -``` + Usage examples: + ```vim + :GoImpl r Read io.Reader + :GoImpl Write io.Writer -7. Run `go generate` command; + " or you can simply put a cursor on the struct and run + :GoImpl io.Reader + ``` +</details> -```vim -" Run `go generate` in cwd path -:GoGenerate +<details> + <summary> + <b>Generate boilerplate for doc comments</b> + </summary> -" Run `go generate` for current file -:GoGenerate % -``` + First set a cursor on **public** package/function/interface/struct and execute: -8. Generate doc comment + ```vim + :GoCmt + ``` +</details> -First set a cursor on **public** package/function/interface/struct and execute: -```vim -:GoCmt -``` +<details> + <summary> + <b>Generate <code>if err != nil {</code> via <a href="https://github.com/koron/iferr">iferr</a></b> + </summary> -9. Generate `if err` + Set the cursor on the line with `err` and execute -Set cursor on the line with **err** and execute: + ```vim + :GoIfErr + ``` +</details> -```vim -:GoIfErr -``` +<details> + <summary> + <b>Setup <a href="https://github.com/mfussenegger/nvim-dap">nvim-dap</a> for go in one line</b> + </summary> -10. Setup nvim-dap for go in one line. + THIS FEATURE WILL BE REMOVED IN `0.1.6` -Notice: [nvim-dap](https://github.com/mfussenegger/nvim-dap) is required + note [nvim-dap](https://github.com/mfussenegger/nvim-dap) has to be installed -```lua -require"gopher.dap".setup() -``` + ```lua + require("gopher.dap").setup() + ``` +</details> ## Contributing@@ -164,3 +237,4 @@ ## Thanks
- [go.nvim](https://github.com/ray-x/go.nvim) - [nvim-dap-go](https://github.com/leoluz/nvim-dap-go) +- [iferr](https://github.com/koron/iferr)
@@ -0,0 +1,49 @@
+version: "3" +tasks: + format: + desc: formats all lua files in repo + cmds: + - stylua . + + lint: + desc: runs all linters + cmds: + - task: selene + - task: stylua:check + + selene: + desc: runs lua linter(selene) + cmds: + - selene . + + stylua:check: + desc: runs stylua in check mode + cmds: + - stylua --check . + + stylua: + desc: runs lua formatter + cmds: + - stylua . + + test: + desc: runs all tests + aliases: [tests, spec] + cmds: + - | + nvim --headless \ + -u ./scripts/minimal_init.lua \ + -c "PlenaryBustedDirectory spec \ + {minimal_init='./scripts/minimal_init.lua' \ + ,sequential=true}" \ + -c ":qa!" + + docgen: + desc: generate vimhelp + cmds: + - | + nvim --noplugin \ + --headless \ + -u "./scripts/minimal_init.lua" \ + -c "luafile ./scripts/docgen.lua" \ + -c ":qa!"
@@ -1,3 +1,3 @@
function! health#gopher#check() - lua require"gopher.health".check() + lua require("gopher.health").check() endfunction
@@ -0,0 +1,220 @@
+*gopher.nvim* + +============================================================================== + +gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. +It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. + +------------------------------------------------------------------------------ + *gopher.nvim-table-of-contents* +Table of Contents + Setup....................................................|gopher.nvim-setup| + Install dependencies..............................|gopher.nvim-install-deps| + Configuration...........................................|gopher.nvim-config| + Modifty struct tags................................|gopher.nvim-struct-tags| + Auto implementation of interface methods..................|gopher.nvim-impl| + Generating unit tests boilerplate......................|gopher.nvim-gotests| + Iferr....................................................|gopher.nvim-iferr| + Generate comments.....................................|gopher.nvim-comments| + Setup `nvim-dap` for Go......................................|gopher.nvim-dap| + +------------------------------------------------------------------------------ + *gopher.nvim-setup* + `gopher.setup`({user_config}) +Setup function. This method simply merges default configs with opts table. +You can read more about configuration at |gopher.nvim-config| +Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| + +Usage ~ +`require("gopher").setup {}` (replace `{}` with your `config` table) +Parameters ~ +{user_config} gopher.Config + +------------------------------------------------------------------------------ + *gopher.nvim-install-deps* + `gopher.install_deps` +Gopher.nvim implements most of its features using third-party tools. +To install these tools, you can run `:GoInstallDeps` command +or call `require("gopher").install_deps()` if you want ues lua api. + + +============================================================================== +------------------------------------------------------------------------------ + *gopher.nvim-config* +config it is the place where you can configure the plugin. +also this is optional is you're ok with default settings. +You can look at default options |gopher.nvim-config-defaults| + +------------------------------------------------------------------------------ + *gopher.nvim-config-defaults* + `default_config` +>lua + local default_config = { + --minidoc_replace_end + + -- log level, you might consider using DEBUG or TRACE for degugging the plugin + ---@type number + log_level = vim.log.levels.INFO, + + -- user specified paths to binaries + ---@class gopher.ConfigCommand + commands = { + go = "go", + gomodifytags = "gomodifytags", + gotests = "gotests", + impl = "impl", + iferr = "iferr", + dlv = "dlv", + }, + ---@class gopher.ConfigGotests + gotests = { + -- gotests doesn't have template named "default" so this plugin uses "default" to set the default template + template = "default", + -- path to a directory containing custom test code templates + ---@type string|nil + template_dir = nil, + -- switch table tests from using slice to map (with test name for the key) + -- works only with gotests installed from develop branch + named = false, + }, + ---@class gopher.ConfigGoTag + gotag = { + ---@type gopher.ConfigGoTagTransform + transform = "snakecase", + }, + } +< +Class ~ +{gopher.Config} + + +============================================================================== +------------------------------------------------------------------------------ + *gopher.nvim-struct-tags* +struct-tags is utilizing the `gomodifytags` tool to add or remove tags to struct fields. +Usage ~ +- put your coursor on the struct +- run `:GoTagAdd json` to add json tags to struct fields +- run `:GoTagRm json` to remove json tags to struct fields + +note: if you dont spesify the tag it will use `json` as default + +simple example: +>go + // before + type User struct { + // ^ put your cursor here + // run `:GoTagAdd yaml` + ID int + Name string + } + + // after + type User struct { + ID int `yaml:id` + Name string `yaml:name` + } +< + + +============================================================================== +------------------------------------------------------------------------------ + *gopher.nvim-impl* +impl is utilizing the `impl` tool to generate method stubs for interfaces. +Usage ~ + +1. put your coursor on the struct on which you want implement the interface + and run `:GoImpl io.Reader` + which will automatically choose the reciver for the methods and + implement the `io.Reader` interface +2. same as previous but with custom receiver, so put your coursor on the struct + run `:GoImpl w io.Writer` + where `w` is the receiver and `io.Writer` is the interface +3. specift receiver, struct, and interface + there's no need to put your coursor on the struct if you specify all arguments + `:GoImpl r RequestReader io.Reader` + where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface + +simple example: +>go + type BytesReader struct{} + // ^ put your cursor here + // run `:GoImpl b io.Reader` + + // this is what you will get + func (b *BytesReader) Read(p []byte) (n int, err error) { + panic("not implemented") // TODO: Implement + } +< + + +============================================================================== +------------------------------------------------------------------------------ + *gopher.nvim-gotests* +gotests is utilizing the `gotests` tool to generate unit tests boilerplate. +Usage ~ + +- generate unit test for spesisfic function/method + - to specift the function/method put your cursor on it + - run `:GoTestAdd` + +- generate unit tests for all functions/methods in current file + - run `:GoTestsAll` + +- generate unit tests only for exported(public) functions/methods + - run `:GoTestsExp` + +you can also specify the template to use for generating the tests. see |gopher.nvim-config| +more details about templates can be found at: https://github.com/cweill/gotests + + +------------------------------------------------------------------------------ + *gopher.nvim-gotests-named* + +if you prefare using named tests, you can enable it in the config. +but you would need to install `gotests@develop` because stable version doesn't support this feature. +you can do it with: +>lua + -- simply run go get in your shell: + go install github.com/cweill/gotests/...@develop + + -- if you want to install it within neovim, you can use one of this: + + vim.fn.jobstart("go install github.com/cweill/gotests/...@develop") + + -- or if you want to use mason: + require("mason-tool-installer").setup { + ensure_installed = { + { "gotests", version = "develop" }, + } + } +< + +if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim| + + +============================================================================== +------------------------------------------------------------------------------ + *gopher.nvim-iferr* +if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check. +Usage ~ +execute `:GoIfErr` near any err variable to insert the check + + +============================================================================== +------------------------------------------------------------------------------ + *gopher.nvim-comments* +Usage ~ +Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line. +This module provides a way to generate comments for Go code. + + +============================================================================== +------------------------------------------------------------------------------ + *gopher.nvim-dap* +This module sets up `nvim-dap` for Go. +Usage ~ +just call `require("gopher.dap").setup()`, and you're good to go. + + + vim:tw=78:ts=8:noet:ft=help:norl:
@@ -1,17 +0,0 @@
-return { - ---@param lib string - ---@return boolean - lualib_is_found = function(lib) - local is_found, _ = pcall(require, lib) - return is_found - end, - - ---@param bin string - ---@return boolean - binary_is_found = function(bin) - if vim.fn.executable(bin) == 1 then - return true - end - return false - end, -}
@@ -1,40 +0,0 @@
----Run any go commands like `go generate`, `go get`, `go mod` ----@param cmd string ----@param ... string|string[] -return function(cmd, ...) - local Job = require "plenary.job" - local c = require("gopher.config").config.commands - local u = require "gopher._utils" - - local args = { ... } - if #args == 0 then - u.notify("please provice any arguments", "error") - return - end - - if cmd == "generate" and #args == 1 and args[1] == "%" then - args[1] = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter - elseif cmd == "get" then - for i, arg in ipairs(args) do - ---@diagnostic disable-next-line: param-type-mismatch - local m = string.match(arg, "^https://(.*)$") or string.match(arg, "^http://(.*)$") or arg - table.remove(args, i) - table.insert(args, i, m) - end - end - - local cmd_args = vim.list_extend({ cmd }, args) ---@diagnostic disable-line: missing-parameter - Job:new({ - command = c.go, - args = cmd_args, - on_exit = function(_, retval) - if retval ~= 0 then - u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") - u.notify(cmd .. " " .. unpack(cmd_args), "debug") - return - end - - u.notify("go " .. cmd .. " was success runned", "info") - end, - }):start() -end
@@ -0,0 +1,33 @@
+local h = vim.health or require "health" +local health = {} + +health.start = h.start or h.report_start +health.ok = h.ok or h.report_ok +health.warn = h.warn or h.report_warn +health.error = h.error or h.report_error +health.info = h.info or h.report_info + +---@param module string +---@return boolean +function health.is_lualib_found(module) + local is_found, _ = pcall(require, module) + return is_found +end + +---@param bin string +---@return boolean +function health.is_binary_found(bin) + if vim.fn.executable(bin) == 1 then + return true + end + return false +end + +---@param ft string +---@return boolean +function health.is_treesitter_parser_available(ft) + local ok, parser = pcall(vim.treesitter.get_parser, 0, ft) + return ok and parser ~= nil +end + +return health
@@ -1,48 +1,34 @@
----@diagnostic disable: param-type-mismatch -return { - ---@param t table - ---@return boolean - empty = function(t) - if t == nil then - return true - end +local c = require "gopher.config" +local log = require "gopher._utils.log" +local utils = {} - return next(t) == nil - end, - - ---@param s string - ---@return string - rtrim = function(s) - local n = #s - while n > 0 and s:find("^%s", n) do - n = n - 1 - end +---@param msg string +---@param lvl number +function utils.deferred_notify(msg, lvl) + vim.defer_fn(function() + vim.notify(msg, lvl, { + title = c.___plugin_name, + }) + log.debug(msg) + end, 0) +end - return s:sub(1, n) - end, - - ---@param msg string - ---@param lvl string|integer - notify = function(msg, lvl) - local l - if lvl == "error" or lvl == 4 then - l = vim.log.levels.ERROR - elseif lvl == "info" or lvl == 2 then - l = vim.log.levels.INFO - elseif lvl == "debug" or lvl == 1 then - l = vim.log.levels.DEBUG - end +---@param msg string +---@param lvl? number +function utils.notify(msg, lvl) + lvl = lvl or vim.log.levels.INFO + vim.notify(msg, lvl, { + title = c.___plugin_name, + }) + log.debug(msg) +end - vim.defer_fn(function() - vim.notify(msg, l) - end, 0) - end, +-- safe require +---@param module string module name +function utils.sreq(module) + local ok, m = pcall(require, module) + assert(ok, string.format("gopher.nvim dependency error: %s not installed", module)) + return m +end - ---safe require - ---@param name string module name - sreq = function(name) - local ok, m = pcall(require, name) - assert(ok, string.format("gopher.nvim dependency error: %s not installed", name)) - return m - end, -} +return utils
@@ -0,0 +1,170 @@
+-- thanks https://github.com/tjdevries/vlog.nvim +-- and https://github.com/williamboman/mason.nvim +-- for the code i have stolen(or have inspected by idk) +local c = require "gopher.config" + +---@class Gopher.Logger +---@field get_outfile fun():string +---@field trace fun(...) +---@field fmt_trace fun(...) +---@field debug fun(...) +---@field fmt_debug fun(...) +---@field info fun(...) +---@field fmt_info fun(...) +---@field warn fun(...) +---@field fmt_warn fun(...) +---@field error fun(...) +---@field fmt_error fun(...) + +local config = { + -- Name of the plugin. Prepended to log messages + name = c.___plugin_name, + + -- Should print the output to neovim while running + -- values: 'sync','async',false + use_console = vim.env.GOPHER_VERBOSE_LOGS == "1", + + -- Should highlighting be used in console (using echohl) + highlights = true, + + -- Should write to a file + use_file = true, + + -- Level configuration + modes = { + { name = "trace", hl = "Comment", level = vim.log.levels.TRACE }, + { name = "debug", hl = "Comment", level = vim.log.levels.DEBUG }, + { name = "info", hl = "None", level = vim.log.levels.INFO }, + { name = "warn", hl = "WarningMsg", level = vim.log.levels.WARN }, + { name = "error", hl = "ErrorMsg", level = vim.log.levels.ERROR }, + }, + + -- Can limit the number of decimals displayed for floats + float_precision = 0.01, +} + +---@type Gopher.Logger +---@diagnostic disable-next-line: missing-fields +local log = {} + +---@return string +function log.get_outfile() + return table.concat { + (vim.fn.has "nvim-0.8.0" == 1) and vim.fn.stdpath "log" or vim.fn.stdpath "cache", + ("/%s.log"):format(config.name), + } +end + +-- selene: allow(incorrect_standard_library_use) +local unpack = unpack or table.unpack + +do + local round = function(x, increment) + increment = increment or 1 + x = x / increment + return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment + end + + local tbl_has_tostring = function(tbl) + local mt = getmetatable(tbl) + return mt and mt.__tostring ~= nil + end + + local make_string = function(...) + local t = {} + for i = 1, select("#", ...) do + local x = select(i, ...) + + if type(x) == "number" and config.float_precision then + x = tostring(round(x, config.float_precision)) + elseif type(x) == "table" and not tbl_has_tostring(x) then + x = vim.inspect(x) + else + x = tostring(x) + end + + t[#t + 1] = x + end + return table.concat(t, " ") + end + + local log_at_level = function(level_config, message_maker, ...) + -- Return early if we're below the current_log_level + -- + -- the log level source get from config directly because otherwise it doesnt work + if level_config.level < c.log_level then + return + end + local nameupper = level_config.name:upper() + + local msg = message_maker(...) + local info = debug.getinfo(2, "Sl") + local lineinfo = info.short_src .. ":" .. info.currentline + + -- Output to console + if config.use_console then + local log_to_console = function() + local console_string = + string.format("[%-6s%s] %s: %s", nameupper, os.date "%H:%M:%S", lineinfo, msg) + + if config.highlights and level_config.hl then + vim.cmd(string.format("echohl %s", level_config.hl)) + end + + local split_console = vim.split(console_string, "\n") + for _, v in ipairs(split_console) do + local formatted_msg = string.format("[%s] %s", config.name, vim.fn.escape(v, [["\]])) + + ---@diagnostic disable-next-line: param-type-mismatch + local ok = pcall(vim.cmd, string.format([[echom "%s"]], formatted_msg)) + if not ok then + vim.api.nvim_out_write(msg .. "\n") + end + end + + if config.highlights and level_config.hl then + vim.cmd "echohl NONE" + end + end + if config.use_console == "sync" and not vim.in_fast_event() then + log_to_console() + else + vim.schedule(log_to_console) + end + end + + -- Output to log file + if config.use_file then + local fp = assert(io.open(log.get_outfile(), "a")) + local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg) + fp:write(str) + fp:close() + end + end + + for _, x in ipairs(config.modes) do + -- log.info("these", "are", "separated") + log[x.name] = function(...) ---@diagnostic disable-line: assign-type-mismatch + return log_at_level(x, make_string, ...) + end + + -- log.fmt_info("These are %s strings", "formatted") + log[("fmt_%s"):format(x.name)] = function(...) ---@diagnostic disable-line: assign-type-mismatch + return log_at_level(x, function(...) + local passed = { ... } + local fmt = table.remove(passed, 1) + local inspected = {} + for _, v in ipairs(passed) do + if type(v) == "table" and tbl_has_tostring(v) then + table.insert(inspected, v) + else + table.insert(inspected, vim.inspect(v)) + end + end + return string.format(fmt, unpack(inspected)) + end, ...) + end + end +end + +return log
@@ -0,0 +1,53 @@
+local r = require "gopher._utils.runner" +local c = require("gopher.config").commands +local u = require "gopher._utils" +local gocmd = {} + +---@param args string[] +---@return string[] +local function if_get(args) + for i, arg in ipairs(args) do + local m = string.match(arg, "^https://(.*)$") or string.match(arg, "^http://(.*)$") or arg + table.remove(args, i) + table.insert(args, i, m) + end + return args +end + +---@param args unknown[] +---@return string[] +local function if_generate(args) + if #args == 1 and args[1] == "%" then + args[1] = vim.fn.expand "%" + end + return args +end + +---@param subcmd string +---@param args string[] +---@return string[]|nil +function gocmd.run(subcmd, args) + if #args == 0 then + error "please provice any arguments" + end + + if subcmd == "get" then + args = if_get(args) + end + + if subcmd == "generate" then + args = if_generate(args) + end + + return r.sync(c.go, { + args = { subcmd, unpack(args) }, + on_exit = function(data, status) + if status ~= 0 then + error("gocmd failed: " .. data) + end + u.notify(c.go .. " " .. subcmd .. " successful runned") + end, + }) +end + +return gocmd
@@ -0,0 +1,33 @@
+local Job = require "plenary.job" +local runner = {} + +---@class gopher.RunnerOpts +---@field args? string[] +---@field cwd? string? +---@field on_exit? fun(data:string, status:number) + +---@param cmd string +---@param opts gopher.RunnerOpts +---@return string[]|nil +function runner.sync(cmd, opts) + local output + Job:new({ + command = cmd, + args = opts.args, + cwd = opts.cwd, + on_stderr = function(_, data) + vim.print(data) + end, + on_exit = function(data, status) + output = data:result() + vim.schedule(function() + if opts.on_exit then + opts.on_exit(output, status) + end + end) + end, + }):sync(60000 --[[1 min]]) + return output +end + +return runner
@@ -1,7 +1,7 @@
---@diagnostic disable: param-type-mismatch local nodes = require "gopher._utils.ts.nodes" local u = require "gopher._utils" -local M = { +local ts = { querys = { struct_block = [[((type_declaration (type_spec name:(type_identifier) @struct.name type: (struct_type)))@struct.declaration)]], em_struct_block = [[(field_declaration name:(field_identifier)@struct.name type: (struct_type)) @struct.declaration]],@@ -27,14 +27,14 @@ ---@param col string
---@param bufnr string|nil ---@param do_notify boolean|nil ---@return table|nil -function M.get_struct_node_at_pos(row, col, bufnr, do_notify) +function ts.get_struct_node_at_pos(row, col, bufnr, do_notify) local notify = do_notify or true - local query = M.querys.struct_block .. " " .. M.querys.em_struct_block + local query = ts.querys.struct_block .. " " .. ts.querys.em_struct_block local bufn = bufnr or vim.api.nvim_get_current_buf() local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) if ns == nil then if notify then - u.notify("struct not found", "warn") + u.deferred_notify("struct not found", vim.log.levels.WARN) end else return ns[#ns]@@ -46,14 +46,14 @@ ---@param col string
---@param bufnr string|nil ---@param do_notify boolean|nil ---@return table|nil -function M.get_func_method_node_at_pos(row, col, bufnr, do_notify) +function ts.get_func_method_node_at_pos(row, col, bufnr, do_notify) local notify = do_notify or true - local query = M.querys.func .. " " .. M.querys.method_name + local query = ts.querys.func .. " " .. ts.querys.method_name local bufn = bufnr or vim.api.nvim_get_current_buf() local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) if ns == nil then if notify then - u.notify("function not found", "warn") + u.deferred_notify("function not found", vim.log.levels.WARN) end else return ns[#ns]@@ -65,16 +65,16 @@ ---@param col string
---@param bufnr string|nil ---@param do_notify boolean|nil ---@return table|nil -function M.get_package_node_at_pos(row, col, bufnr, do_notify) +function ts.get_package_node_at_pos(row, col, bufnr, do_notify) local notify = do_notify or true -- stylua: ignore if row > 10 then return end - local query = M.querys.package + local query = ts.querys.package local bufn = bufnr or vim.api.nvim_get_current_buf() local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) if ns == nil then if notify then - u.notify("package not found", "warn") + u.deferred_notify("package not found", vim.log.levels.WARN) return nil end else@@ -87,18 +87,18 @@ ---@param col string
---@param bufnr string|nil ---@param do_notify boolean|nil ---@return table|nil -function M.get_interface_node_at_pos(row, col, bufnr, do_notify) +function ts.get_interface_node_at_pos(row, col, bufnr, do_notify) local notify = do_notify or true - local query = M.querys.interface + local query = ts.querys.interface local bufn = bufnr or vim.api.nvim_get_current_buf() local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) if ns == nil then if notify then - u.notify("interface not found", "warn") + u.deferred_notify("interface not found", vim.log.levels.WARN) end else return ns[#ns] end end -return M +return ts
@@ -1,3 +1,7 @@
+local ts_query = require "nvim-treesitter.query" +local parsers = require "nvim-treesitter.parsers" +local locals = require "nvim-treesitter.locals" +local u = require "gopher._utils" local M = {} local function intersects(row, col, sRow, sCol, eRow, eCol)@@ -53,10 +57,6 @@ ---@param bufnr integer
---@param pos_row string ---@return string function M.get_all_nodes(query, lang, _, bufnr, pos_row, _) - local ts_query = require "nvim-treesitter.query" - local parsers = require "nvim-treesitter.parsers" - local locals = require "nvim-treesitter.locals" - bufnr = bufnr or 0 pos_row = pos_row or 30000@@ -113,8 +113,6 @@ ---@param row string
---@param col string ---@return table function M.nodes_at_cursor(query, default, bufnr, row, col) - local u = require "gopher._utils" - bufnr = bufnr or vim.api.nvim_get_current_buf() local ft = vim.api.nvim_buf_get_option(bufnr, "ft") if row == nil or col == nil then@@ -123,13 +121,19 @@ end
local nodes = M.get_all_nodes(query, ft, default, bufnr, row, col) if nodes == nil then - u.notify("Unable to find any nodes. Place your cursor on a go symbol and try again", "debug") + u.deferred_notify( + "Unable to find any nodes. Place your cursor on a go symbol and try again", + vim.log.levels.DEBUG + ) return nil end nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col)) if nodes == nil or #nodes == 0 then - u.notify("Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col), "debug") + u.deferred_notify( + "Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col), + vim.log.levels.DEBUG + ) return nil end
@@ -1,29 +0,0 @@
-local API = {} -local tags = require "gopher.struct_tags" -local tests = require "gopher.gotests" -local cmd = require "gopher._utils.commands" - -API.install_deps = require "gopher.installer" -API.tags_add = tags.add -API.tags_rm = tags.remove -API.impl = require "gopher.impl" -API.iferr = require "gopher.iferr" -API.comment = require "gopher.comment" -API.test_add = tests.func_test -API.test_exported = tests.all_exported_tests -API.tests_all = tests.all_tests - -API.get = function(...) - cmd("get", ...) -end -API.mod = function(...) - cmd("mod", ...) -end -API.generate = function(...) - cmd("generate", ...) -end -API.work = function(...) - cmd("work", ...) -end - -return API
@@ -1,3 +1,10 @@
+---@toc_entry Generate comments +---@tag gopher.nvim-comments +---@usage Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line. +---@text This module provides a way to generate comments for Go code. + +local log = require "gopher._utils.log" + local function generate(row, col) local ts_utils = require "gopher._utils.ts" local comment, ns = nil, nil@@ -32,6 +39,8 @@
return function() local row, col = unpack(vim.api.nvim_win_get_cursor(0)) local comment, ns = generate(row + 1, col + 1) + + log.debug("generated comment: " .. comment) vim.api.nvim_win_set_cursor(0, { ns.dim.s.r,
@@ -1,33 +1,89 @@
----@class Config ----@field commands ConfigCommands +---@toc_entry Configuration +---@tag gopher.nvim-config +---@text config it is the place where you can configure the plugin. +--- also this is optional is you're ok with default settings. +--- You can look at default options |gopher.nvim-config-defaults| ----@class ConfigCommands ----@field go string ----@field gomodifytags string ----@field gotests string ----@field impl string ----@field iferr string ----@field dlv string +---@type gopher.Config +---@private +local config = {} -local M = { - ---@type Config - config = { - ---set custom commands for tools - commands = { - go = "go", - gomodifytags = "gomodifytags", - gotests = "gotests", - impl = "impl", - iferr = "iferr", - dlv = "dlv", - }, +---@tag gopher.nvim-config.ConfigGoTagTransform +---@text Possible values for |gopher.Config|.gotag.transform: +--- +---@private +---@alias gopher.ConfigGoTagTransform +---| "snakecase" "GopherUser" -> "gopher_user" +---| "camelcase" "GopherUser" -> "gopherUser" +---| "lispcase" "GopherUser" -> "gopher-user" +---| "pascalcase" "GopherUser" -> "GopherUser" +---| "titlecase" "GopherUser" -> "Gopher User" +---| "keep" keeps the original field name + +--minidoc_replace_start { + +---@tag gopher.nvim-config-defaults +---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section):gsub(">", ">lua") +--- +---@class gopher.Config +local default_config = { + --minidoc_replace_end + + -- log level, you might consider using DEBUG or TRACE for degugging the plugin + ---@type number + log_level = vim.log.levels.INFO, + + -- user specified paths to binaries + ---@class gopher.ConfigCommand + commands = { + go = "go", + gomodifytags = "gomodifytags", + gotests = "gotests", + impl = "impl", + iferr = "iferr", + dlv = "dlv", + }, + ---@class gopher.ConfigGotests + gotests = { + -- gotests doesn't have template named "default" so this plugin uses "default" to set the default template + template = "default", + -- path to a directory containing custom test code templates + ---@type string|nil + template_dir = nil, + -- switch table tests from using slice to map (with test name for the key) + -- works only with gotests installed from develop branch + named = false, + }, + ---@class gopher.ConfigGoTag + gotag = { + ---@type gopher.ConfigGoTagTransform + transform = "snakecase", }, } +--minidoc_afterlines_end ----Plugin setup function ----@param opts Config user config -function M.setup(opts) - M.config = vim.tbl_deep_extend("force", M.config, opts or {}) +---@type gopher.Config +---@private +local _config = default_config + +-- I am kinda secret so don't tell anyone about me +-- even dont use me +-- +-- if you don't belive me that i am secret see +-- the line below it says @private +---@private +_config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field + +---@param user_config? gopher.Config +---@private +function config.setup(user_config) + _config = vim.tbl_deep_extend("force", default_config, user_config or {}) end -return M +setmetatable(config, { + __index = function(_, key) + return _config[key] + end, +}) + +return config
@@ -0,0 +1,129 @@
+---@toc_entry Setup `nvim-dap` for Go +---@tag gopher.nvim-dap +---@text This module sets up `nvim-dap` for Go. +---@usage just call `require("gopher.dap").setup()`, and you're good to go. + +local c = require "gopher.config" +local dap = {} + +dap.adapter = function(callback, config) + local host = config.host or "127.0.0.1" + local port = config.port or "38697" + local addr = string.format("%s:%s", host, port) + + local handle, pid_or_err + local stdout = assert(vim.loop.new_pipe(false)) + local opts = { + stdio = { nil, stdout }, + args = { "dap", "-l", addr }, + detached = true, + } + + handle, pid_or_err = vim.loop.spawn(c.commands.dlv, opts, function(status) + if not stdout or not handle then + return + end + + stdout:close() + handle:close() + if status ~= 0 then + print("dlv exited with code", status) + end + end) + + assert(handle, "Error running dlv: " .. tostring(pid_or_err)) + if stdout then + stdout:read_start(function(err, chunk) + assert(not err, err) + if chunk then + vim.schedule(function() + require("dap.repl").append(chunk) + end) + end + end) + end + + -- wait for delve to start + vim.defer_fn(function() + callback { type = "server", host = "127.0.0.1", port = port } + end, 100) +end + +local function args_input() + vim.ui.input({ prompt = "Args: " }, function(input) + return vim.split(input or "", " ") + end) +end + +local function get_arguments() + local co = coroutine.running() + if co then + return coroutine.create(function() + local args = args_input() + coroutine.resume(co, args) + end) + else + return args_input() + end +end + +dap.configuration = { + { + type = "go", + name = "Debug", + request = "launch", + program = "${file}", + }, + { + type = "go", + name = "Debug (Arguments)", + request = "launch", + program = "${file}", + args = get_arguments, + }, + { + type = "go", + name = "Debug Package", + request = "launch", + program = "${fileDirname}", + }, + { + type = "go", + name = "Attach", + mode = "local", + request = "attach", + processId = require("dap.utils").pick_process, + }, + { + type = "go", + name = "Debug test", + request = "launch", + mode = "test", + program = "${file}", + }, + { + type = "go", + name = "Debug test (go.mod)", + request = "launch", + mode = "test", + program = "./${relativeFileDirname}", + }, +} + +-- sets ups nvim-dap for Go in one function call. +function dap.setup() + vim.deprecate( + "gopher.dap", + "you might consider setting up `nvim-dap` manually, or using another plugin(https://github.com/leoluz/nvim-dap-go)", + "v0.1.6", + "gopher" + ) + + local ok, d = pcall(require, "dap") + assert(ok, "gopher.nvim dependency error: dap not installed") + + d.adapters.go = dap.adapter + d.configurations.go = dap.configuration +end + +return dap
@@ -1,98 +0,0 @@
----@diagnostic disable: param-type-mismatch -local function get_arguments() - local function get() - vim.ui.input({ prompt = "Args: " }, function(input) - return vim.split(input or "", " ") ---@diagnostic disable-line: missing-parameter - end) - end - - local co = coroutine.running() - if co then - return coroutine.create(function() - local args = get() - coroutine.resume(co, args) - end) - else - return get() - end -end - -return { - adapter = function(callback, config) - local handle, pid_or_err - local stdout = vim.loop.new_pipe(false) - local host = config.host or "127.0.0.1" - local port = config.port or "38697" - local addr = string.format("%s:%s", host, port) - local opts = { - stdio = { nil, stdout }, - args = { "dap", "-l", addr }, - detached = true, - } - - handle, pid_or_err = vim.loop.spawn("dlv", opts, function(code) - stdout:close() - handle:close() - if code ~= 0 then - print("dlv exited with code", code) - end - end) - - assert(handle, "Error running dlv: " .. tostring(pid_or_err)) - stdout:read_start(function(err, chunk) - assert(not err, err) - if chunk then - vim.schedule(function() - require("dap.repl").append(chunk) - end) - end - end) - - -- Wait for delve to start - vim.defer_fn(function() - callback { type = "server", host = "127.0.0.1", port = port } - end, 100) - end, - configuration = { - { - type = "go", - name = "Debug", - request = "launch", - program = "${file}", - }, - { - type = "go", - name = "Debug (Arguments)", - request = "launch", - program = "${file}", - args = get_arguments, - }, - { - type = "go", - name = "Debug Package", - request = "launch", - program = "${fileDirname}", - }, - { - type = "go", - name = "Attach", - mode = "local", - request = "attach", - processId = require("dap.utils").pick_process, - }, - { - type = "go", - name = "Debug test", - request = "launch", - mode = "test", - program = "${file}", - }, - { - type = "go", - name = "Debug test (go.mod)", - request = "launch", - mode = "test", - program = "./${relativeFileDirname}", - }, - }, -}
@@ -1,73 +1,104 @@
+---@toc_entry Generating unit tests boilerplate +---@tag gopher.nvim-gotests +---@text gotests is utilizing the `gotests` tool to generate unit tests boilerplate. +---@usage +--- - generate unit test for spesisfic function/method +--- - to specift the function/method put your cursor on it +--- - run `:GoTestAdd` +--- +--- - generate unit tests for all functions/methods in current file +--- - run `:GoTestsAll` +--- +--- - generate unit tests only for exported(public) functions/methods +--- - run `:GoTestsExp` +--- +--- you can also specify the template to use for generating the tests. see |gopher.nvim-config| +--- more details about templates can be found at: https://github.com/cweill/gotests +--- + +---@tag gopher.nvim-gotests-named +---@text +--- if you prefare using named tests, you can enable it in the config. +--- but you would need to install `gotests@develop` because stable version doesn't support this feature. +--- you can do it with: +--- >lua +--- -- simply run go get in your shell: +--- go install github.com/cweill/gotests/...@develop +--- +--- -- if you want to install it within neovim, you can use one of this: +--- +--- vim.fn.jobstart("go install github.com/cweill/gotests/...@develop") +--- +--- -- or if you want to use mason: +--- require("mason-tool-installer").setup { +--- ensure_installed = { +--- { "gotests", version = "develop" }, +--- } +--- } +--- < +--- +--- if you choose to install `gotests` within neovim, i recommend adding it to your `build` section in your |lazy.nvim| + +local c = require "gopher.config" +local ts_utils = require "gopher._utils.ts" +local r = require "gopher._utils.runner" local u = require "gopher._utils" -local M = {} +local log = require "gopher._utils.log" +local gotests = {} + +---@param args table +---@private +local function add_test(args) + if c.gotests.named then + table.insert(args, "-named") + end + + if c.gotests.template_dir then + table.insert(args, "-template_dir") + table.insert(args, c.gotests.template_dir) + end + + if c.gotests.template ~= "default" then + table.insert(args, "-template") + table.insert(args, c.gotests.template) + end ----@param cmd_args table -local function run(cmd_args) - local Job = require "plenary.job" - local c = require("gopher.config").config.commands + table.insert(args, "-w") + table.insert(args, vim.fn.expand "%") + + log.debug("generating tests with args: ", args) - Job:new({ - command = c.gotests, - args = cmd_args, - on_exit = function(_, retval) - if retval ~= 0 then - u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") - return + return r.sync(c.commands.gotests, { + args = args, + on_exit = function(data, status) + if not status == 0 then + error("gotests failed: " .. data) end - u.notify("unit test(s) generated", "info") + u.notify "unit test(s) generated" end, - }):start() + }) end ----@param args table -local function add_test(args) - local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter - table.insert(args, "-w") - table.insert(args, fpath) - run(args) -end - ----generate unit test for one function ----@param parallel boolean -function M.func_test(parallel) - local ts_utils = require "gopher._utils.ts" - +-- generate unit test for one function +function gotests.func_test() local ns = ts_utils.get_func_method_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) if ns == nil or ns.name == nil then - u.notify("cursor on func/method and execute the command again", "info") + u.notify("cursor on func/method and execute the command again", vim.log.levels.WARN) return end - local cmd_args = { "-only", ns.name } - if parallel then - table.insert(cmd_args, "-parallel") - end - - add_test(cmd_args) + add_test { "-only", ns.name } end ----generate unit tests for all functions in current file ----@param parallel boolean -function M.all_tests(parallel) - local cmd_args = { "-all" } - if parallel then - table.insert(cmd_args, "-parallel") - end - - add_test(cmd_args) +-- generate unit tests for all functions in current file +function gotests.all_tests() + add_test { "-all" } end ----generate unit tests for all exported functions ----@param parallel boolean -function M.all_exported_tests(parallel) - local cmd_args = {} - if parallel then - table.insert(cmd_args, "-parallel") - end - - table.insert(cmd_args, "-exported") - add_test(cmd_args) +-- generate unit tests for all exported functions +function gotests.all_exported_tests() + add_test { "-exported" } end -return M +return gotests
@@ -1,44 +1,70 @@
-local c = require("gopher.config").config.commands +local health = {} +local cmd = require("gopher.config").commands +local u = require "gopher._utils.health_util" -local requried_for_work_msg = "Gopher.nvim will not work without it!" -local M = { - _required = { - plugins = { - { lib = "plenary", help = requried_for_work_msg }, - { lib = "nvim-treesitter", help = requried_for_work_msg }, - { lib = "dap", help = "Required for set upping debugger" }, +local deps = { + plugin = { + { lib = "dap", msg = "required for `gopher.dap`", optional = true }, + { lib = "plenary", msg = "required for everyting in gopher.nvim", optional = false }, + { lib = "nvim-treesitter", msg = "required for everyting in gopher.nvim", optional = false }, + }, + bin = { + { + bin = cmd.go, + msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`", + optional = false, }, - binarys = { - { bin = c.go, help = "required for GoMod, GoGet, GoGenerate command" }, - { bin = c.gomodifytags, help = "required for modify struct tags" }, - { bin = c.impl, help = "required for interface implementing" }, - { bin = c.gotests, help = "required for test(s) generation" }, - { bin = c.dlv, help = "required for debugger(nvim-dap)" }, + { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = false }, + { bin = cmd.impl, msg = "required for `:GoImpl`", optional = false }, + { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = false }, + { + bin = cmd.gotests, + msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`", + optional = false, }, + { bin = cmd.dlv, msg = "required for debugging, (`nvim-dap`, `gopher.dap`)", optional = true }, + }, + treesitter = { + { parser = "go", msg = "required for `gopher.nvim`", optional = false }, }, } -function M.check() - local health = vim.health or require "health" - local u = require "gopher._utils._health" +function health.check() + u.start "required plugins" + for _, plugin in ipairs(deps.plugin) do + if u.is_lualib_found(plugin.lib) then + u.ok(plugin.lib .. " installed") + else + if plugin.optional then + u.warn(plugin.lib .. " not found, " .. plugin.msg) + else + u.error(plugin.lib .. " not found, " .. plugin.msg) + end + end + end - health.report_start "Required plugins" - for _, plugin in ipairs(M._required.plugins) do - if u.lualib_is_found(plugin.lib) then - health.report_ok(plugin.lib .. " installed.") + u.start "required binaries" + u.info "all those binaries can be installed by `:GoInstallDeps`" + for _, bin in ipairs(deps.bin) do + if u.is_binary_found(bin.bin) then + u.ok(bin.bin .. " installed") else - health.report_error(plugin.lib .. " not found. " .. plugin.help) + if bin.optional then + u.warn(bin.bin .. " not found, " .. bin.msg) + else + u.error(bin.bin .. " not found, " .. bin.msg) + end end end - health.report_start "Required go tools" - for _, binary in ipairs(M._required.binarys) do - if u.binary_is_found(binary.bin) then - health.report_ok(binary.bin .. " installed") + u.start "required treesitter parsers" + for _, parser in ipairs(deps.treesitter) do + if u.is_treesitter_parser_available(parser.parser) then + u.ok(parser.parser .. " parser installed") else - health.report_warn(binary.bin .. " is not installed but " .. binary.help) + u.error(parser.parser .. " parser not found, " .. parser.msg) end end end -return M +return health
@@ -1,21 +1,26 @@
----Add iferr declaration ----That's Lua of vimscript implementation of: ----github.com/koron/iferr -return function() - local c = require("gopher.config").config.commands - local u = require "gopher._utils" +---@toc_entry Iferr +---@tag gopher.nvim-iferr +---@text if you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check. +---@usage execute `:GoIfErr` near any err variable to insert the check + +local c = require "gopher.config" +local log = require "gopher._utils.log" +local iferr = {} +-- That's Lua implementation: github.com/koron/iferr +function iferr.iferr() local boff = vim.fn.wordcount().cursor_bytes - local cmd = (c.iferr .. " -pos " .. boff) - local data = vim.fn.systemlist(cmd, vim.fn.bufnr "%") + local pos = vim.fn.getcurpos()[2] + local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%") if vim.v.shell_error ~= 0 then - u.notify("command " .. cmd .. " exited with code " .. vim.v.shell_error, "error") - return + error("iferr failed: " .. data) + log.error("failed. output: " .. data) end - local pos = vim.fn.getcurpos()[2] vim.fn.append(pos, data) vim.cmd [[silent normal! j=2j]] vim.fn.setpos(".", pos) end + +return iferr
@@ -1,12 +1,43 @@
+---@toc_entry Auto implementation of interface methods +---@tag gopher.nvim-impl +---@text impl is utilizing the `impl` tool to generate method stubs for interfaces. +---@usage +--- 1. put your coursor on the struct on which you want implement the interface +--- and run `:GoImpl io.Reader` +--- which will automatically choose the reciver for the methods and +--- implement the `io.Reader` interface +--- 2. same as previous but with custom receiver, so put your coursor on the struct +--- run `:GoImpl w io.Writer` +--- where `w` is the receiver and `io.Writer` is the interface +--- 3. specift receiver, struct, and interface +--- there's no need to put your coursor on the struct if you specify all arguments +--- `:GoImpl r RequestReader io.Reader` +--- where `r` is the receiver, `RequestReader` is the struct and `io.Reader` is the interface +--- +--- simple example: +--- >go +--- type BytesReader struct{} +--- // ^ put your cursor here +--- // run `:GoImpl b io.Reader` +--- +--- // this is what you will get +--- func (b *BytesReader) Read(p []byte) (n int, err error) { +--- panic("not implemented") // TODO: Implement +--- } +--- < + +local c = require("gopher.config").commands +local r = require "gopher._utils.runner" +local ts_utils = require "gopher._utils.ts" local u = require "gopher._utils" +local impl = {} ---@return string +---@private local function get_struct() - local ts_utils = require "gopher._utils.ts" - local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) if ns == nil then - u.notify("put cursor on a struct or specify a receiver", "info") + u.deferred_notify("put cursor on a struct or specify a receiver", vim.log.levels.INFO) return "" end@@ -18,10 +49,7 @@
return ns.name end -return function(...) - local c = require("gopher.config").config.commands - local Job = require "plenary.job" - +function impl.impl(...) local args = { ... } local iface, recv_name = "", "" local recv = get_struct()@@ -30,7 +58,7 @@ if #args == 0 then
iface = vim.fn.input "impl: generating method stubs for interface: " vim.cmd "redraw!" if iface == "" then - u.notify("usage: GoImpl f *File io.Reader", "info") + u.deferred_notify("usage: GoImpl f *File io.Reader", vim.log.levels.INFO) return end elseif #args == 1 then -- :GoImpl io.Reader@@ -48,28 +76,23 @@ recv_name = select(#args - 2, ...)
recv = string.format("%s %s", recv_name, recv) end - -- stylua: ignore - local cmd_args = { - "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), ---@diagnostic disable-line: missing-parameter - recv, - iface - } - - local res_data - Job:new({ - command = c.impl, - args = cmd_args, - on_exit = function(data, retval) - if retval ~= 0 then - u.notify("command 'impl " .. unpack(cmd_args) .. "' exited with code " .. retval, "error") - return + local output = r.sync(c.impl, { + args = { + "-dir", + vim.fn.fnameescape(vim.fn.expand "%:p:h" --[[@as string]]), + recv, + iface, + }, + on_exit = function(data, status) + if not status == 0 then + error("impl failed: " .. data) end - - res_data = data:result() end, - }):sync() + }) local pos = vim.fn.getcurpos()[2] - table.insert(res_data, 1, "") - vim.fn.append(pos, res_data) + table.insert(output, 1, "") + vim.fn.append(pos, output) end + +return impl
@@ -1,5 +1,70 @@
-local GOPHER = {} +--- *gopher.nvim* +--- +--- ============================================================================== +--- +--- gopher.nvim is a minimalistic plugin for Go development in Neovim written in Lua. +--- It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. + +--- Table of Contents +---@tag gopher.nvim-table-of-contents +---@toc + +local log = require "gopher._utils.log" +local tags = require "gopher.struct_tags" +local tests = require "gopher.gotests" +local gocmd = require("gopher._utils.runner.gocmd").run +local gopher = {} + +---@toc_entry Setup +---@tag gopher.nvim-setup +---@text Setup function. This method simply merges default configs with opts table. +--- You can read more about configuration at |gopher.nvim-config| +--- Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| +--- +---@usage `require("gopher").setup {}` (replace `{}` with your `config` table) +---@param user_config gopher.Config +gopher.setup = function(user_config) + log.debug "setting up config" + require("gopher.config").setup(user_config) + log.debug(vim.inspect(user_config)) +end + +---@toc_entry Install dependencies +---@tag gopher.nvim-install-deps +---@text Gopher.nvim implements most of its features using third-party tools. +--- To install these tools, you can run `:GoInstallDeps` command +--- or call `require("gopher").install_deps()` if you want ues lua api. +gopher.install_deps = require("gopher.installer").install_deps + +gopher.impl = require("gopher.impl").impl +gopher.iferr = require("gopher.iferr").iferr +gopher.comment = require "gopher.comment" + +gopher.tags = { + add = tags.add, + rm = tags.remove, +} + +gopher.test = { + add = tests.func_test, + exported = tests.all_exported_tests, + all = tests.all_tests, +} + +gopher.get = function(...) + gocmd("get", { ... }) +end + +gopher.mod = function(...) + gocmd("mod", { ... }) +end + +gopher.generate = function(...) + gocmd("generate", { ... }) +end -GOPHER.setup = require("gopher.config").setup +gopher.work = function(...) + gocmd("work", { ... }) +end -return GOPHER +return gopher
@@ -1,3 +1,8 @@
+local c = require("gopher.config").commands +local r = require "gopher._utils.runner" +local u = require "gopher._utils" +local installer = {} + local urls = { gomodifytags = "github.com/fatih/gomodifytags", impl = "github.com/josharian/impl",@@ -8,28 +13,24 @@ }
---@param pkg string local function install(pkg) - local Job = require "plenary.job" - local u = require "gopher._utils" - local url = urls[pkg] .. "@latest" - - Job:new({ - command = "go", + r.sync(c.go, { args = { "install", url }, - on_exit = function(_, retval) - if retval ~= 0 then - u.notify("command 'go install " .. url .. "' exited with code " .. retval, "error") + on_exit = function(data, status) + if not status == 0 then + error("go install failed: " .. data) return end - - u.notify("install " .. url .. " finished", "info ") + u.notify("installed: " .. url) end, - }):start() + }) end ---Install required go deps -return function() +function installer.install_deps() for pkg, _ in pairs(urls) do install(pkg) end end + +return installer
@@ -1,13 +1,14 @@
-command! -nargs=* GoTagAdd :lua require"gopher.api".tags_add(<f-args>) -command! -nargs=* GoTagRm :lua require"gopher.api".tags_rm(<f-args>) -command! -nargs=* GoTestAdd :lua require"gopher.api".test_add(<f-args>) -command! -nargs=* GoTestsAll :lua require"gopher.api".tests_all(<f-args>) -command! -nargs=* GoTestsExp :lua require"gopher.api".test_exported(<f-args>) -command! -nargs=* GoMod :lua require"gopher.api".mod(<f-args>) -command! -nargs=* GoGet :lua require"gopher.api".get(<f-args>) -command! -nargs=* GoWork :lua require"gopher.api".work(<f-args>) -command! -nargs=* GoImpl :lua require"gopher.api".impl(<f-args>) -command! -nargs=* GoGenerate :lua require"gopher.api".generate(<f-args>) -command! GoCmt :lua require"gopher.api".comment() -command! GoIfErr :lua require"gopher.api".iferr() -command! GoInstallDeps :lua require"gopher.api".install_deps() +command! -nargs=* GoTagAdd :lua require"gopher".tags.add(<f-args>) +command! -nargs=* GoTagRm :lua require"gopher".tags.rm(<f-args>) +command! GoTestAdd :lua require"gopher".test.add() +command! GoTestsAll :lua require"gopher".test.all() +command! GoTestsExp :lua require"gopher".test.exported() +command! -nargs=* GoMod :lua require"gopher".mod(<f-args>) +command! -nargs=* GoGet :lua require"gopher".get(<f-args>) +command! -nargs=* GoWork :lua require"gopher".work(<f-args>) +command! -nargs=* GoImpl :lua require"gopher".impl(<f-args>) +command! -nargs=* GoGenerate :lua require"gopher".generate(<f-args>) +command! GoCmt :lua require"gopher".comment() +command! GoIfErr :lua require"gopher".iferr() +command! GoInstallDeps :lua require"gopher".install_deps() +command! GopherLog :lua vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
@@ -0,0 +1,33 @@
+---@diagnostic disable: undefined-global +--# selene: allow(undefined_variable) + +local okay, minidoc = pcall(require, "mini.doc") +if not okay then + error "mini.doc not found, please install it. https://github.com/echasnovski/mini.doc" + return +end + +local files = { + "lua/gopher/init.lua", + "lua/gopher/config.lua", + "lua/gopher/struct_tags.lua", + "lua/gopher/impl.lua", + "lua/gopher/gotests.lua", + "lua/gopher/iferr.lua", + "lua/gopher/comment.lua", + "lua/gopher/dap.lua", +} + +minidoc.setup() + +local hooks = vim.deepcopy(minidoc.default_hooks) +hooks.write_pre = function(lines) + -- Remove first two lines with `======` and `------` delimiters to comply + -- with `:h local-additions` template + table.remove(lines, 1) + table.remove(lines, 1) + + return lines +end + +MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks })
@@ -0,0 +1,34 @@
+local function root(p) + local f = debug.getinfo(1, "S").source:sub(2) + return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (p or "") +end + +local function install_plug(plugin) + local name = plugin:match ".*/(.*)" + local package_root = root ".tests/site/pack/deps/start/" + if not vim.loop.fs_stat(package_root .. name) then + print("Installing " .. plugin) + vim.fn.mkdir(package_root, "p") + vim.fn.system { + "git", + "clone", + "--depth=1", + "https://github.com/" .. plugin .. ".git", + package_root .. "/" .. name, + } + end +end + +vim.cmd [[set runtimepath=$VIMRUNTIME]] +vim.opt.runtimepath:append(root()) +vim.opt.packpath = { root ".tests/site" } +vim.notify = print + +install_plug "nvim-lua/plenary.nvim" +install_plug "nvim-treesitter/nvim-treesitter" +install_plug "echasnovski/mini.doc" -- used for docs generation + +vim.env.XDG_CONFIG_HOME = root ".tests/config" +vim.env.XDG_DATA_HOME = root ".tests/data" +vim.env.XDG_STATE_HOME = root ".tests/state" +vim.env.XDG_CACHE_HOME = root ".tests/cache"
@@ -1,41 +0,0 @@
-describe("gopher.config", function() - it("can be required", function() - require "gopher.config" - end) - - it(".setup() when gets empty table not edit config", function() - local c = require "gopher.config" - c.setup {} - - assert.are.same(c.config.commands.go, "go") - assert.are.same(c.config.commands.gomodifytags, "gomodifytags") - assert.are.same(c.config.commands.gotests, "gotests") - assert.are.same(c.config.commands.impl, "impl") - end) - - it(".setup() when get one custom value sets that", function() - local c = require "gopher.config" - c.setup { commands = { - go = "custom_go", - } } - - assert.are.same(c.config.commands.go, "custom_go") - end) - - it(".setup() when get all custom values sets it", function() - local c = require "gopher.config" - c.setup { - commands = { - go = "go1.18", - gomodifytags = "user-gomodifytags", - gotests = "gotests", - impl = "goimpl", - }, - } - - assert.are.same(c.config.commands.go, "go1.18") - assert.are.same(c.config.commands.gomodifytags, "user-gomodifytags") - assert.are.same(c.config.commands.gotests, "gotests") - assert.are.same(c.config.commands.impl, "goimpl") - end) -end)
@@ -1,19 +0,0 @@
-describe("gopher._utils", function() - it("can be requried", function() - require "gopher._utils" - end) - - it(".empty() with non-empty talbe", function() - local empty = require("gopher._utils").empty - local res = empty { first = "1", second = 2 } - - assert.are.same(res, false) - end) - - it(".empty() with empty talbe", function() - local empty = require("gopher._utils").empty - local res = empty {} - - assert.are.same(res, true) - end) -end)
@@ -0,0 +1,29 @@
+describe("gopher.config", function() + it(".setup() should provide default when .setup() is not called", function() + local c = require "gopher.config" + + assert.are.same(c.commands.go, "go") + assert.are.same(c.commands.gomodifytags, "gomodifytags") + assert.are.same(c.commands.gotests, "gotests") + assert.are.same(c.commands.impl, "impl") + assert.are.same(c.commands.iferr, "iferr") + assert.are.same(c.commands.dlv, "dlv") + end) + + it(".setup() should change options on users config", function() + local c = require "gopher.config" + c.setup { + commands = { + go = "go1.420", + gomodifytags = "iDontUseRustBtw", + }, + } + + assert.are.same(c.commands.go, "go1.420") + assert.are.same(c.commands.gomodifytags, "iDontUseRustBtw") + assert.are.same(c.commands.gotests, "gotests") + assert.are.same(c.commands.impl, "impl") + assert.are.same(c.commands.iferr, "iferr") + assert.are.same(c.commands.dlv, "dlv") + end) +end)
@@ -0,0 +1,15 @@
+describe("gopher._utils", function() + local u = require "gopher._utils" + + describe(".sreq()", function() + it("can require existing module", function() + assert.are.same(require "gopher", u.sreq "gopher") + end) + + it("cannot require non-existing module", function() + assert.has.errors(function() + u.sreq "iDontExistBtw" + end) + end) + end) +end)