Compare commits

...

103 commits
v0.1.0 ... main

Author SHA1 Message Date
6a3924cee5
feat: json2go (#130)
* feat(json2go): implement

* feat(json2go): support manual input

* chore(readme): add json2go

* chore(docs): add docs
2025-12-08 21:29:34 +02:00
4a2384ade8
Merge pull request #129 (v0.4.0) 2025-11-27 17:12:38 +02:00
7764f6e37f
revert: automatically restart lsp server (#128)
semi revert of 7c198a1b36
2025-11-25 17:02:38 +02:00
f599e75963
chore: mention repo mirrors on other forges 2025-11-20 17:35:37 +02:00
906e340b4f
chore: update config docs 2025-11-06 17:20:08 +02:00
fabdcc5fb3
fix: type annotation 2025-11-06 17:19:53 +02:00
03de1f5ffc
chore: fix formatting 2025-11-06 17:19:53 +02:00
7a18d9f7bd
feat(strct-tags): add support for tag options (#126)
* feat(struct_tags): add options support

* refactor(struct-tags): give input field better name

* feat(struct-tag): add default option

* refactor: make it work on neovim version below 0.12

* chore(struct-tags): update the demo

* refactor: unite struct_tags util with main logic
2025-11-06 14:58:21 +02:00
0de1892ca9
chore(vhs): improve iferr demo 2025-10-26 13:55:44 +02:00
399845fee2
tests: remove plenary from dependencies, since we dont use it 2025-09-28 15:19:22 +03:00
ef4a3a1c7a
fix: type annotation for logger 2025-09-28 15:19:22 +03:00
e8d53840d4
refactor(health): mark all bin deps as required, check if vim version is 0.10 or higher 2025-09-16 15:14:17 +03:00
f4809cd2ec
chore(readme): address the most common issue
there's been several issues related to not installed deps: #124, #111,
#108, and more
2025-09-16 15:14:17 +03:00
0b2c26ebbb
refactor(config): allow both and 'string', and 'nil' for optional params 2025-09-13 12:53:03 +03:00
d1eaebab6b
refactor: fix config type annotations 2025-09-12 20:25:09 +03:00
7c198a1b36
feat: automatically restart lsp server (opt-in) 2025-09-10 16:17:15 +03:00
440a7cc432
chore: add vim 0.11.4 to the suite 2025-09-04 22:54:58 +03:00
1e7af1b212
feat(comment): add support for: interface methods, struct fields, variables (#123)
* refactor(comment): dont use unnecessary function

* chore: quick way to open vim in dev mode

* feat(comment): add comment on on interface method

* feat(comment): add comment on a struct field

* feat(comment): add comment on a variable

* docs: add note about the generate function

* docs: gopher.TsResult

* fix(utils): handle case when indentation is wrong
2025-09-04 16:52:18 +03:00
295e21e637
chore: start demos in vim (#122) 2025-08-31 14:30:13 +03:00
Oleksandr Smirnov
7e8c6f41aa
Merge pull request #121 from olexsmir/develop
sync develop with main
2025-08-30 17:01:47 +03:00
b7ce5d1f35
chore: update readme 2025-08-30 16:59:50 +03:00
Oleksandr Smirnov
9bf72afc63 chore: add demos (#118)
* chore: add dataset for demos

* chore(demos): add demos

* chore: update CONTRIBUTING

* this copefully will fix comments gif
2025-08-30 16:59:50 +03:00
c0951c4769
chore: update CONTRIBUTING.md 2025-08-30 16:59:50 +03:00
d6b41494f1
fix(testutils): validate provided cursor position 2025-08-30 16:59:50 +03:00
Oleksandr Smirnov
b6d3815f9b feat(struct_tags): add range support (#117)
* feat(struct_tags): add range support

* refactor: use `start`, and `end_` naming for ranges
2025-08-30 16:58:32 +03:00
8c87952964
chore(ci): install go bins via gopher.nvim itself 2025-08-30 16:58:32 +03:00
92b0ebb6a7
fix(config): type annotations 2025-08-30 16:58:32 +03:00
e90f766ea3
refactor(test): make helper test function more ergonomic
In my opinion, requiring user to access tests via `T["module_name"]`
was too fragile and typos prone.
2025-08-27 22:00:52 +03:00
53ab4274c2
chore: update tasks, run tests on all recent versions 2025-08-27 21:20:36 +03:00
09696900a5
test: add test for config 2025-08-27 20:56:07 +03:00
6f9648805e
chore: remove unused rules from nvim selene setup 2025-08-27 20:51:30 +03:00
Smirnov Oleksandr
de585144eb
fix(config): use new vim.validate, and not old one (#114) 2025-06-12 17:16:26 +03:00
Smirnov Oleksandr
9db5931af1
fix(impl): not error if no args provided (#110)
* fix(impl): handle case with no arguments provided

* fix(config): validate missing field
2025-04-03 16:52:38 +03:00
Smirnov Oleksandr
9d28cdebf1
fix: add separate timeout for instalelr (#109)
* fix(installer): add separate timeout for installer
2025-03-30 17:20:08 +03:00
Smirnov Oleksandr
ea28fc4e6f
chore: remove nvim-treesitter from dependencies (#107)
* refactor(health): remove treesitter from required plugins

* chore: remove nvim-treesitter from dependencies

* chore(health): update messages
2025-03-27 23:18:27 +02:00
Smirnov Oleksandr
847c79ab76
Merge pull request #106 from olexsmir/develop 2025-03-23 23:08:09 +02:00
7725689d1d
feat: add pkg.json 2025-03-23 22:42:56 +02:00
Smirnov Oleksandr
77754fe362
fix: nightly(0.11) (#104)
* chore: fix minimal_init, load default plugins correctly

* refactor(ts): make it work on nightly

* chore: get nightly back in ci

* fix(tests): some how i now i need to run vim.treesitter.start() to make it work

* feat(ts): check if parser is found

* chore: use --clean instead --noplugin

* refactor(tests): use auto commands instead of putting it in each test

* chore: show the diff of the doc
2025-03-23 19:06:13 +02:00
Smirnov Oleksandr
c0b2834652
docs: update (#103)
* chore: add @dochide annotation

- it's easier to distinguish @private and something i dont want to see in docs

* docs: update

* refactor: move thing out to utils

* fix: lua-ls error

* fixup! refactor: move thing out to utils

* docs: update
2025-03-23 15:46:54 +02:00
592fe82760
chore(ci): update checkout in tests 2025-03-22 21:19:59 +02:00
Smirnov Oleksandr
3dcf708d34
refactor: some refactoring of tests (#102)
* feat(tests): add utils that does most of tests boilerplate

* refactor(tests): rewrite using new thing

* refactor(tests): clean up everywhere

* refactor(tests): remove boilerplate even further

* refactor(tests): soon it will be too far

* refactor(tests): some test renaming
2025-03-22 21:19:59 +02:00
Smirnov Oleksandr
7ebe190c96
refactor/comamnds dont require .setup (#101)
* refactor(commands)!: change the way of disabling commands

BREAKING CHANGE: not it does not require calling .setup()

* docs: update docs
2025-03-22 21:19:59 +02:00
Smirnov Oleksandr
ab68a58b34
Create LICENSE 2025-03-22 21:19:59 +02:00
Smirnov Oleksandr
acd4e6fc30
fix!: fix "Command X not found after loading gopher.nvim" (#100)
BREAKING CHANGE: now calling .setup is required
2025-03-22 21:19:59 +02:00
Smirnov Oleksandr
969db908f8
fix(struct_tags): edge case with structs declared as var (#99)
* fix(struct_tags): edge case with structs declared as var

* test: test it and fix it

* fixup! test: test it and fix it

* fixup! fix(struct_tags): edge case with structs declared as var

* fixup! test: test it and fix it
2025-03-22 21:19:59 +02:00
Smirnov Oleksandr
a993ece59f
feat(config): vim.validate all config after setup (#98) 2025-03-22 21:19:59 +02:00
6ee261cfa6
docs: update readme 2025-03-22 21:19:59 +02:00
Smirnov Oleksandr
7af08c9780
test: unit (#97)
* refactor(utils): remove unused function

* fix(installer): actually pass what should be passed

* docs: add explanation comment

* test: add utils

* refactor(utils): flatten the dir of files

* remove .luarc
2025-03-21 22:12:26 +02:00
Smirnov Oleksandr
9aa0038125
refactor: minimize amount of vimscript (#96)
* refactor: remove autoload

* since nvim 0.9 health.lua files are threaded as checkhealth provider

*  prof of concept 

* fix(runner.gocmd)!: i forgot to update it when i was working on #85

* fix(plugin): now commands register properly

* fix(plugin): fix command name for :GoIfErr

* fix(plugin): respect `setup_commands` option

* docs: update

* refactor(plugin): use vim.schedule

* docs: update CONTRIBUTING
2025-03-21 21:44:42 +02:00
Smirnov Oleksandr
c5cc5080fa
refactor(installer): install gotests@develop by default (#95)
* refactor(installer): automatically install gotests@develop

* docs: update
2025-03-21 00:54:04 +02:00
Smirnov Oleksandr
55bc5787d1
refactor: struct tags (#94)
* refactor(struct_tags): finally my hands got to this

* feat(struct_tags): trim output

* feat(struct_tags): add :GoTagClear

* docgen

* refactor(struct_tags): error on out-of-bounds

* feat(struct_tags): add support for working with multiple tags at the once

* test(struct_tags): test both possible inputs

* refactor(struct_tags): add type annotation, dont force write

* refactor(struct_tags): optimization ig

* docs: fix

* fixup! refactor(struct_tags): add type annotation, dont force write

* task docgen

---------

Co-authored-by: Oliver <1571880470@qq.com>
2025-03-19 18:06:33 +02:00
Smirnov Oleksandr
e9f2eef5e7
refactor: treesitter utils (#91)
* refactor(ts_utils): i dont know why event it was here

* fix: typos

* fix(struct_tags)!: remove statement that i used for debug

* refactor(ts_util): start from scratch

* refactor(struct_tags): use new ts_util

* fixup! refactor(struct_tags): use new ts_util

* test(struct_tags): add support for multiple structs

* fix(gotests): use new api

* fix(impl): refactor some logic, use new api

* docs(ts): add an explanation

* refactor(_utils.ts): all public methods are just adapters

* fix(comment): now it works

* fixup! refactor(_utils.ts): all public methods are just adapters

* fixup! fixup! refactor(_utils.ts): all public methods are just adapters

* test(comment): e2e

* tests(comment): fix

* refactor(utils.ts): fix, docs

* test(comment): fix tests again

* fix(tests/comments): well, now i fell stupid

* refactor(ts): add assert just to be sure that all elements are in the result

* fix(ts): type annotations

* fix(ts): pass bufnr to vim.treesitter.get_node

* chore(ci): disable nightly

* chore(ci): reorganize
2025-03-19 15:09:57 +02:00
Smirnov Oleksandr
f171953e43
chore(ci)!: use rolling stable version of nvim (#90)
* chore(ci)!: use rolling stable version of nvim

* fix(ci): use correct version on doc check
2025-03-03 14:27:02 +02:00
Smirnov Oleksandr
d1a21bffab
feat(iferr): add -message support (#89)
* feat(iferr): add *-message* support

* generate docs
2025-03-03 14:22:28 +02:00
Smirnov Oleksandr
bb31271311
chore: add docs checking thing (#88)
* chore: add doc checking thing

* testing

* Revert "testing"

This reverts commit ae0618d89f.
2025-03-02 17:26:23 +02:00
Smirnov Oleksandr
57b5dbf62e
feat(struct_tags): set default tag (#87)
* feat(struct_tags): add config option for default tag

* docs: docgen

* fix(struct_tags): as it turns out it didnt work as i supposed to before, but now it does
2025-03-02 17:19:00 +02:00
Smirnov Oleksandr
c2f64db4a8
refactor(health): remove deprecations (#86) 2025-03-02 16:43:30 +02:00
Smirnov Oleksandr
6016ca57d4
refactor: use vim.system instead of pleanry (#85)
* refactor!: migrate to vim.system

* refactor(gotests): use vim.system

* refactor(iferr): use vim.system

* refactor(impl): use vim.system

* refactor(installer): use vim.system and add sync mode

* test: fix gotests' tests

* refactor(struct_tags): use vim.system

* chore(ci): install all deps explicitly

* refactor(installer)!: add sync as an option

* docs: update readme
2025-03-02 16:31:50 +02:00
Smirnov Oleksandr
837897a79d
fix: typos (#82)
* docs(gotests): update and fix

* fix(health): typos

* docs(impl): update and fix typos

* docs(config): typos

* docs(iferr): update

* typos

* docs(struct_tags): update

* docs: fix typos

* docs: Capitalization

* docgen
2025-02-25 14:08:15 +02:00
Smirnov Oleksandr
cd8a5efc88
refactor!: remove dap adapter (#81) 2025-02-25 13:16:24 +02:00
Smirnov Oleksandr
da960189c1
tests: improve testing (#80)
* chore: setup mini.test

* chore(ci): setup new test runner, install plugin deps

* chore(ci): test only on stable and nightly releases

* test: iferr

* test: struct_tags

* test: impl

* test: gotests
2025-02-24 14:21:01 +02:00
Smirnov Oleksandr
0ed14a40d9
fix(iferr): improve error handling (#75) 2024-12-23 22:44:13 +02:00
Smirnov Oleksandr
f55c15ada8
chore: add nvim 0.10 to test suit (#69) 2024-06-15 12:54:48 +03:00
Smirnov Oleksandr
a995af3415
sync develop and main (#68)
* healthcheck: refactoring, remove deprecation wanings (#35)

* refactor(checkhealth): remove deprecation warnings, complete rewrite

* refactor(checkhealth): rename util file

* style(healthchecker): reformat lua in vim file

* refactor(health): move all report function into table

* add editorconfig (#36)

* refactor of public plugin's api (#37)

* refactor: move all plugin functionality to init.lua

* fix(commands): now it uses correct module paths

* refactor(config): change way how it handles options

* refactor(gotests): use correct config, change way how deps required, fix some typos

* fix(healthchecker): use correct config

* refactor(iferr): change api

* refactor(impl): change api

* refactor(installer): change api

* refactor(struct_tags): change way of importting deps

* refactor(struct_tags): rename M to struct_tags

* run stylua

* refactor(dap): make it all in one file, and make some refactoring

* refactor(_utils): change way how it organizes

* fix: use new _utils api

* refactor(_utils.health): reorganize module

* refactor(_utils.ts): some renameing, moving requires lines

* run stylua

* update tooling (#38)

* chore: delete pre-commit

* chore: switch from makefile to taskfile

* chore(ci): update and add one more linter

* chore(editorconfig): add config for Go

* chore(editorconfig): remove max_line_length

* fix: editorconfig-check on README.md

* feat: run tests independent from user's nvim config

* remove editorconfig-checker

* fix(config): now it not removes .setup() from itself after calling
.setup()

* fix(config): now it works correctly

* chore: update taskfile, and linter config

* feat(config): make it optional to call .setup()

* run tests independent of user nvim setup (#39)

* chore(lua_ls): now lua_ls knows about testing functions

* spec: change way how tests srtuctured

* test(config): refactor tests

* test: utils

* refactor(utils): remove not used function

* chore(ci): add test runner

* chore(ci): remove taskfile from deps

* fix: now it works

* fix(dap): now dlv uses cmd to run from config

* chore(ci): run tests on many versions of nvim

* refactor: commands runner (#42)

* feat(utils): first impl of own commands runner

* refactor(gotests): uses own runner instead of vendored

* refactor(utils): back to plenary.job

* refactor(gotests): use new runner, clean code

* fix(runner): now it returns output correctly

* refactor(iferr): use vim.system

i have tried to use _utils.runner, but i can't figure out how to make `< file.go` for the command

* refactor(impl): use new runner

* refactor(installer): use new runner

* refactor(struct_tags): use new runner

* refactor: commands such as :GoGet runs with new runner

* refactor: throw errors in more lua way, i think

* refactor(utils): notify now has title

* refactor: use more correct way of notifying

* refactor(runner): write error message on error

* add ability for setting custom tools options (#44)

* feat(gotests): add custom templates support

* feat(struct_tags): add support for custom `transform` option

* fix(health): check if bin is installed, and added message about treesitter parser

* fix: fix iferr config (#56)

* Add support for named tests (#50)

* fix(typo): README.md (#47)

* feat: add support for named tests

* test

* tags in table

* debug installer msg

* test

* hardcoded @develop

* get gotests tag from setup()

* update readme

* store install tag in urls table

* removed gotests tag

* update README.md

* remove urls installer index reference

* remove named arg from add_test()

* .

* update README.md

* update README.md

---------

Co-authored-by: Steve M <gearcog@users.noreply.github.com>

* reformat .editorconfig config

* refactor(api)!: mave tags and gotests api into their sub tables

* add help file, and docs (#59)

* idk how good this idea is

* this could be working but i still cant figure out how to run it

* ignore tags that mini.doc gens, but why?

* chore(taskfile): force exiting after tests

because i got infinit ci

* chore(ci): add more nvim versions to run on

* chore: update taskfile

* feat: add docs generator

* docs: its only begining

* refactor: update docgen script

* docs: write some more

* docs(config): update

* docs: update readme

* language

* hope it would work

* what about that?

* maybe this would work?

* update md

* upd

* WHY DOESNT IT WORKING

* idk by but 0.9.3 just fails the ci, so i deleted it from suite

* again update, why does markdown not work in embeded html

* maybe it can help?

* upd

* again update

* kinda fix

* fix: formatting

* again some updating

* some readme updating

* fix, this shouldnt be in repo

* i finnaly undertood how to fix this fking skill issue

* fix(struct_tags): typo

* refactor(docs): change the order in generated file

* docs: install deps

* refactor(scripts): rename doc-gen script

* docs(impl): write docs

* docs(dap): add doc

* stylua .

* docs(struct_tags): add doc

* docs(gotests): add docs

* docs(iferr): add docs

* docs(comment): add doc

* update CONTRIBUTING.md

* docs(README): talk about `develop` branch

* docs: update README.md

* refactor(health): keep in mind new way of health check (#63)

* feat: add logger (#64)

* refactor(health): keep in mind new way of health check (#63)

* feat(log): add logger module

* refactor(utils): remove unused code

* refactor(log, utils): get plugin name from config

* refactor(logger): add some type annotations

* refactor(utils): log notifications

* feat: LOGGER™

* feat(config): TYPES

* refactor(log): dont give a thing about var that is not even declared

* feat(log): add easy way to open log

* refactor(log): some types

* update types

* docs: regen

* fix(log): make setting log level by config work

* feat(iferr): write error to log file if occur

* feat(gotests): add logger

* add deprecation message (#67)

---------

Co-authored-by: Arne Van Maele <93863978+arnevm123@users.noreply.github.com>
Co-authored-by: Alex <49870662+ysomad@users.noreply.github.com>
Co-authored-by: Steve M <gearcog@users.noreply.github.com>
2024-06-15 12:43:06 +03:00
Steve M
ac27f4b679
fix(typo): README.md (#47) 2023-10-09 15:54:13 +03:00
Elliott Minns
03cabf675c
Resolve Neovim Deprecations for version 0.10 (#26)
* fix deprecation warnings for treesitter parse_query

* use treesitter.query.parse

* fix get_node_text deprecation
2023-05-07 17:56:26 +03:00
Smirnov Oleksandr
c7b061b124 add stand with Ukraine banner
yeah, I'm a Ukrainian and I don't know what i didn't it before
2023-05-01 15:01:45 +03:00
Pavel
cc46546a93
Fix typo (#25) 2023-02-01 14:42:26 +02:00
Smirnov Oleksandr
b5c3358521
refactor tests (#23)
* fix: add `nvim-dap` as test dep. fix makefile

* refactor(spec): struct_tags
2022-12-19 12:27:39 +02:00
Smirnov Oleksandr
1363d55056 refactor: change minimal spec config 2022-12-19 11:52:07 +02:00
Smirnov Oleksandr
2f0edbfdfc
refactor: move requires in place where they used (#22)
* refactor(comment): move require to a function

* refactor(gotests): move requires inside of functions

* refactor(health): move requires inside of function

* refactor(iferr): move requires inside of a function

* refactor(impl): move some requires inside of functions

* refactor(installer): move requires inside of function

* refactor(struct_tags): move requires into function

* refactor(dap): move import into function

* refactor(utils): move import into functions
2022-12-19 11:47:43 +02:00
Smirnov Oleksandr
e8fe6c5b15
feat(config): add types annotation (#21) 2022-11-13 12:08:23 +00:00
Smirnov Oleksandr
f835464d7f
Some refactoring (#20)
* feat: move all lua api into `api` module

* feat: remove boilerplate code, add go work suport

* refactor(utils): separete module for health

* refactor(dap): remove copy-paste code

* fix: comment

* chore(lsp): disable type checking

* feat: add `go work` command
2022-10-07 14:31:54 +00:00
Smirnov Oleksandr
d65884b182 Fix linters (#19)
* fix selene

* chore(ci): pin stylua version
2022-09-22 18:20:37 +03:00
Smirnov Oleksandr
aa535e7ed1 feat: add dlv as dep 2022-09-22 17:54:49 +03:00
Smirnov Oleksandr
93be1b1d6f docs(dap): add 2022-09-22 17:39:06 +03:00
Smirnov Oleksandr
9f6e94f46f feat(health): add check for nvim-dap 2022-09-22 17:36:14 +03:00
Smirnov Oleksandr
413efcc9f2 feat(dap): fist naive implementaion 2022-09-22 17:28:04 +03:00
Smirnov Oleksandr
80d0659425 docs: add a contributing section in the readme
I forgot to do this in the last commit. F...
2022-07-11 23:38:42 +03:00
Smirnov Oleksandr
94f5b61012 docs: add contributing guide 2022-07-11 23:35:54 +03:00
Smirnov Oleksandr
e4942aa070 docs(iferr): add to config & tools 2022-07-07 14:25:11 +03:00
Smirnov Oleksandr
eb19ecdc05
Add iferr integration (#15)
* feat(iferr): add to installer & config

* feat(iferr): add first implementation

* docs(iferr): add
2022-07-07 14:19:07 +03:00
Smirnov Oleksandr
4b83da6d7f style: reformat all lua code 2022-07-07 14:09:48 +03:00
Smirnov Oleksandr
4fee3543e0 fix(config): set default options if user provides empty table 2022-07-05 11:46:20 +03:00
Smirnov Oleksandr
bb494abd1e
Fix health check for nvim 0.7(#13) 2022-07-02 22:30:12 +03:00
1stvaliduser
08f9156ebc
Fix typos(UWU) (#11)
* uwu

* okn't

* h
2022-07-01 15:33:34 +03:00
Smirnov Oleksandr
0fcdceeb89
Add comment generator (#10)
* feat(_utils.ts): get package

* feat(_utils.ts): get interface

* feat(_utils.ts): update type annotations

* feat(comments): first naive implementation

* docs: add comment [skip ci]
2022-06-30 12:00:13 +03:00
Smirnov Oleksandr
40a2839eab
feat(config): add base implementation
* feat(config): naive implementation

feat(config): get command for a run from config

* docs: add config

* fix typo [skip ci]

* test: add config
2022-06-27 20:28:35 +03:00
Smirnov Oleksandr
1db0914cfc fix(init): use exist function for test_add 2022-06-27 20:24:49 +03:00
Smirnov Oleksandr
bfbee0f825 fix(gotests): print help message by vim.notify 2022-06-27 16:15:30 +03:00
Smirnov Oleksandr
ebb3894b3b fix(_utils): set notification level 2022-06-27 14:30:02 +03:00
Smirnov Oleksandr
2adbb49064 refactor(_utils): notify by vim.notify in _utils 2022-06-25 12:00:58 +03:00
Smirnov Oleksandr
096bc8e7ee feat: add help messages
feat(goget): add help message

feat(gomod): add help message
2022-06-24 15:28:52 +03:00
Smirnov Oleksandr
0b415c32e8 refactor: migrate to vim.notify from prints 2022-06-24 15:27:08 +03:00
Smirnov Oleksandr
b4fd34ec17 feat(gotests): generate tests only for exported func
docs(gotests): generate test for exported funcs
2022-06-23 13:24:29 +03:00
Smirnov Oleksandr
c5096aacc0 fix(installer): update gotests path 2022-06-23 13:17:46 +03:00
Smirnov Oleksandr
5642af746b fix(installer): run commands without timeout 2022-06-23 13:09:24 +03:00
Smirnov Olexander
250d4de718 refactor: add comments & update type anotation 2022-06-22 14:42:14 +03:00
Smirnov Olexander
c1de269ef4 feat(installer): add gotests 2022-06-21 17:58:10 +03:00
Smirnov Olexander
70297f28e7 feat(gotests): add generate all tests
docs(gotests): add generate all tests
2022-06-21 17:51:59 +03:00
Smirnov Olexander
acd414d454 feat(health): check on gotests 2022-06-21 17:51:59 +03:00
Smirnov Olexander
8b3c68e38a feat(gotests): add generate one test
docs(gotests): add generate one test
2022-06-21 17:51:59 +03:00
Smirnov Olexander
507ccb6347 feat: add GoGenerate command
feat(health): add gogenerate

docs(gogenerate): add doc
2022-06-21 14:31:37 +03:00
139 changed files with 3766 additions and 703 deletions

16
.editorconfig Normal file
View file

@ -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

4
.envrc Normal file
View file

@ -0,0 +1,4 @@
dotenv
# GOPHER_DIR - needed only for tests, to find the root of the project
env_vars_required GOPHER_DIR

View file

@ -1,23 +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 }}
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 .

58
.github/workflows/linters.yml vendored Normal file
View file

@ -0,0 +1,58 @@
name: linters
on:
push:
branches:
- main
- develop
pull_request:
jobs:
linters:
name: Lua
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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: .
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Task
uses: arduino/setup-task@v1
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install NeoVim
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: stable
- name: Cache .tests
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/.tests
key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }}
- name: Generate docs
run: task docgen
- name: Diff
run: |
git diff doc
exit $(git status --porcelain doc | wc -l | tr -d " ")

62
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,62 @@
name: tests
on:
push:
branches:
- main
- develop
pull_request:
jobs:
tests:
strategy:
matrix:
os: [ubuntu-latest]
version:
- stable
- nightly
- v0.10.0
- v0.10.4
- v0.11.0
- v0.11.1
- v0.11.2
- v0.11.3
- v0.11.4
runs-on: ${{ matrix.os }}
steps:
- name: Install Task
uses: arduino/setup-task@v1
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "1.24.0"
check-latest: false
- name: Install NeoVim
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.version }}
- uses: actions/checkout@v4
- name: Cache .tests
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/.tests
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }}
- name: Install Go bins
run: task install-deps
- name: Run Tests
run: |
nvim --version
task test

4
.gitignore vendored
View file

@ -1 +1,3 @@
playground/
/playground/
/.tests/
/.env

View file

@ -1,15 +0,0 @@
repos:
- repo: local
hooks:
- id: stylua
name: StyLua
language: rust
entry: stylua
types: [lua]
args: ["--check", "-"]
- id: selene
name: Selene
language: rust
entry: selene
types: [lua]
args: ["-"]

49
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,49 @@
# Contributing to `gopher.nvim`
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
```
### Formatting and linting
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
sudo pacman -S selene stylua
# or whatever is your package manager
```
For formatting use this following commands, or setup your editor to integrate with selene/stylua:
```bash
task format
task lint
```
### Documentation
Here we're using [mini.doc](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-doc.md)
for generating vimhelp files based on [LuaCats](https://luals.github.io/wiki/annotations/) annotations in comments.
For demo gifs in [readme](./README.md) we're using [vhs](https://github.com/charmbracelet/vhs).
All files related to demos live in [/vhs](./vhs) dir.
You can generate docs with:
```bash
task docgen # generates vimhelp
task vhs:generate # generates demo gifs
```
### Commit messages
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), please follow it.
### Testing
For testing this plugins uses [mini.test](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md).
All tests live in [/spec](./spec) dir.
You can run tests with:
```bash
task test
```

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Oleksandr Smirnov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,11 +0,0 @@
.PHONY:
.SILENT:
format:
stylua **/*.lua
lint:
selene **/*.lua
test:
nvim --headless -u ./spec/minimal.vim -c "PlenaryBustedDirectory spec {minimal_init='./spec/minimal.vim'}"

275
README.md
View file

@ -1,81 +1,296 @@
# gopher.nvim
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg)](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 add go tooling support in neovim.
It's **NOT** an LSP tool, the goal of this plugin is to add go tooling support in Neovim.
## Install
> All development of new and maybe undocumented, and unstable features is happening on [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)
## Table of content
* [How to install](#install-using-lazynvim)
* [Features](#features)
* [Configuration](#configuration)
* [Troubleshooting](#troubleshooting)
* [Contributing](#contributing)
## Install (using [lazy.nvim](https://github.com/folke/lazy.nvim))
Requirements:
- **Neovim 0.10** or later
- Treesitter parser for `go`(`:TSInstall go` if you use [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter))
- [Go](https://github.com/golang/go) installed
> [!IMPORTANT]
> If you prefer using other forges, this repository is also mirrored at:
> - [tangled.org](https://tangled.org): [`https://tangled.org/olexsmir.xyz/gopher.nvim`](https://tangled.org/olexsmir.xyz/gopher.nvim)
> - [codeberg.org](https://codeberg.org): [`https://codeberg.org/olexsmir/gopher.nvim`](https://codeberg.org/olexsmir/gopher.nvim)
```lua
use {
-- NOTE: this plugin is already lazy-loaded and adds only about 1ms
-- of load time to your config
{
"olexsmir/gopher.nvim",
requires = { -- dependencies
"nvim-lua/plenary.nvim",
"nvim-treesitter/nvim-treesitter",
},
ft = "go",
-- branch = "develop"
-- (optional) updates the plugin's dependencies on each update
build = function()
vim.cmd.GoInstallDeps()
end,
---@module "gopher"
---@type gopher.Config
opts = {},
}
```
Also, run `TSInstall go` if install the `go` parser if not installed yet.
## Features
1. Install requires go tools:
<details>
<summary>
<b>Install plugin's go deps</b>
</summary>
```vim
:GoInstallDeps
```
This will install next tools:
This will install the following tools:
- [gomodifytags](https://github.com/fatih/gomodifytags)
- [impl](https://github.com/josharian/impl)
- [gotests](https://github.com/cweill/gotests)
- [iferr](https://github.com/koron/iferr)
- [json2go](https://github.com/olexsmir/json2go)
</details>
2. Modify struct tags:
By default be added/removed `json` tag, if not set.
<details>
<summary>
<b>Add and remove tags for structs via <a href="https://github.com/fatih/gomodifytags">gomodifytags</a></b>
</summary>
![Add tags demo](./vhs/tags.gif)
By default `json` tag will be added/removed, if not set:
```vim
:GoTagAdd json " For add json tag
:GoTagRm yaml " For remove yaml tag
" add json tag
:GoTagAdd json
" add json tag with omitempty option
:GoTagAdd json=omitempty
" remove yaml tag
:GoTagRm yaml
```
3. Run `go mod` command
```lua
-- or you can use lua api
require("gopher").tags.add "xml"
require("gopher").tags.rm "proto"
```
</details>
<details>
<summary>
<b>Generating tests via <a href="https://github.com/cweill/gotests">gotests</a></b>
</summary>
```vim
:GoMod tidy " Runs `go mod tidy`
:GoMod init asdf " Runs `go mod init asdf`
" Generate one test for a specific function/method(one under cursor)
:GoTestAdd
" Generate all tests for all functions/methods in the current file
:GoTestsAll
" Generate tests for only exported functions/methods in the current file
:GoTestsExp
```
4. Run `go get` command
```lua
-- or you can use lua api
require("gopher").test.add()
require("gopher").test.exported()
require("gopher").test.all()
```
Link can has a `http` or `https` prefix.
For named tests see `:h gopher.nvim-gotests-named`
</details>
You can provide more that one package url.
<details>
<summary>
<b>Run commands like <code>go mod/get/etc</code> inside of nvim</b>
</summary>
```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
" go work commands
:GoWork sync
" run go generate in cwd
:GoGenerate
" run go generate for the current file
:GoGenerate %
```
</details>
5. Interface implementation
<details>
<summary>
<b>Interface implementation via <a href="https://github.com/josharian/impl">impl<a></b>
</summary>
Command syntax:
![Auto interface implementation demo](./vhs/impl.gif)
Syntax of the command:
```vim
:GoImpl [receiver] [interface]
" Also you can put cursor on the struct and run:
" also you can put a cursor on the struct and run
:GoImpl [interface]
```
Example of usage:
Usage examples:
```vim
" Example
:GoImpl r Read io.Reader
" or simply put your cursor in the struct and run:
:GoImpl Write io.Writer
" or you can simply put a cursor on the struct and run
:GoImpl io.Reader
```
</details>
## Thanks
<details>
<summary>
<b>Generate boilerplate for doc comments</b>
</summary>
- [go.nvim](https://github.com/ray-x/go.nvim)
![Generate comments](./vhs/comment.gif)
First set a cursor on **public** package/function/interface/struct and execute:
```vim
:GoCmt
```
</details>
<details>
<summary>
<b>Convert json to Go types</b>
</summary>
![Convert JSON to Go types](./vhs/json2go.gif)
`:GoJson` opens a temporary buffer where you can paste or write JSON.
Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the generated Go struct into the original buffer at the cursor position.
Alternatively, you can pass JSON directly as an argument:
```vim
:GoJson {"name": "Alice", "age": 30}
```
Additionally, `gopher.json2go` provides lua api, see `:h gopher.nvim-json2go` for details.
</details>
<details>
<summary>
<b>Generate <code>if err != nil {</code> via <a href="https://github.com/koron/iferr">iferr</a></b>
</summary>
![Generate if err != nil {](./vhs/iferr.gif)
Set the cursor on the line with `err` and execute
```vim
:GoIfErr
```
</details>
## Configuration
> [!IMPORTANT]
>
> If you need more info look `:h gopher.nvim`
**Take a look at default options (might be a bit outdated, look `:h gopher.nvim-config`)**
```lua
require("gopher").setup {
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
log_level = vim.log.levels.INFO,
-- timeout for running internal commands
timeout = 2000,
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
installer_timeout = 999999,
-- user specified paths to binaries
commands = {
go = "go",
gomodifytags = "gomodifytags",
gotests = "gotests",
impl = "impl",
iferr = "iferr",
},
gotests = {
-- a default template that gotess will use.
-- gotets doesn't have template named `default`, we use it to represent absence of the provided template.
template = "default",
-- path to a directory containing custom test code templates
template_dir = nil,
-- use named tests(map with test name as key) in table tests(slice of structs by default)
named = false,
},
gotag = {
transform = "snakecase",
-- default tags to add to struct fields
default_tag = "json",
-- default tag option added struct fields, set to nil to disable
-- e.g: `option = "json=omitempty,xml=omitempty`
option = nil,
},
iferr = {
-- choose a custom error message, nil to use default
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
message = nil,
},
json2go = {
-- command used to open interactive input.
-- e.g: `split`, `botright split`, `tabnew`
interactive_cmd = "vsplit",
-- name of autogenerated struct
-- e.g: "MySuperCoolName"
type_name = nil,
},
}
```
## Troubleshooting
The most common issue with the plugin is missing dependencies.
Run `:checkhealth gopher` to verify that the plugin is installed correctly.
If any binaries are missing, install them using `:GoInstallDeps`.
If the issue persists, feel free to [open a new issue](https://github.com/olexsmir/gopher.nvim/issues/new).
## Contributing
PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)

42
Taskfile.yml Normal file
View file

@ -0,0 +1,42 @@
version: "3"
includes:
vhs:
taskfile: ./vhs/Taskfile.yml
dir: ./vhs
tasks:
lint:
cmds:
- selene .
- stylua --check .
format:
cmd: stylua .
test:
cmds:
- |
nvim --clean --headless \
-u ./scripts/minimal_init.lua \
-c "lua MiniTest.run()" \
-c ":qa!"
nvim:
cmd: nvim --clean -u "./scripts/minimal_init.lua" {{ .CLI_ARGS }}
docgen:
desc: generate vimhelp
cmds:
- |
nvim --clean --headless \
-u "./scripts/minimal_init.lua" \
-c "luafile ./scripts/docgen.lua" \
-c ":qa!"
install-deps:
desc: installs go bin (used in CI)
cmds:
- |
nvim --clean --headless \
-u "./scripts/minimal_init.lua" \
+GoInstallDepsSync +qa

View file

@ -1,3 +0,0 @@
function! health#gopher#check()
lua require"gopher.health".check()
endfunction

1
doc/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/tags

294
doc/gopher.nvim.txt Normal file
View file

@ -0,0 +1,294 @@
*gopher.nvim* Enhance your golang experience
MIT License Copyright (c) 2025 Oleksandr Smirnov
==============================================================================
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
Setup ................................................ |gopher.nvim-setup()|
Install dependencies ............................ |gopher.nvim-dependencies|
Config ................................................ |gopher.nvim-config|
Commands ............................................ |gopher.nvim-commands|
Modify struct tags ............................... |gopher.nvim-struct-tags|
json2go .............................................. |gopher.nvim-json2go|
Auto implementation of interface methods ................ |gopher.nvim-impl|
Generating unit tests boilerplate .................... |gopher.nvim-gotests|
Iferr .................................................. |gopher.nvim-iferr|
Generate comments ................................... |gopher.nvim-comments|
------------------------------------------------------------------------------
*gopher.nvim-setup()*
`gopher.setup`({user_config})
Setup function. This method simply merges default config with opts table.
You can read more about configuration at |gopher.nvim-config|
Calling this function is optional, if you ok with default settings.
See |gopher.nvim.config|
Usage ~
>lua
require("gopher").setup {} -- use default config or replace {} with your own
<
Parameters ~
{user_config} `(gopher.Config)` See |gopher.nvim-config|
------------------------------------------------------------------------------
*gopher.nvim-dependencies*
`gopher.install_deps`
Gopher.nvim implements most of its features using third-party tools. To
install plugin's dependencies, you can run:
`:GoInstallDeps` or `:GoInstallDepsSync`
or use `require("gopher").install_deps()` if you prefer lua api.
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-config*
`default_config`
>lua
local default_config = {
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
---@type number
log_level = vim.log.levels.INFO,
-- timeout for running internal commands
---@type number
timeout = 2000,
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
---@type number
installer_timeout = 999999,
-- user specified paths to binaries
---@class gopher.ConfigCommand
commands = {
go = "go",
gomodifytags = "gomodifytags",
gotests = "gotests",
impl = "impl",
iferr = "iferr",
json2go = "json2go",
},
---@class gopher.ConfigGotests
gotests = {
-- a default template that gotess will use.
-- gotets doesn't have template named `default`, we use it to represent absence of the provided template.
template = "default",
-- path to a directory containing custom test code templates
---@type string|nil
template_dir = nil,
-- use named tests(map with test name as key) in table tests(slice of structs by default)
named = false,
},
---@class gopher.ConfigGoTag
gotag = {
---@type gopher.ConfigGoTagTransform
transform = "snakecase",
-- default tags to add to struct fields
default_tag = "json",
-- default tag option added struct fields, set to nil to disable
-- e.g: `option = "json=omitempty,xml=omitempty`
---@type string|nil
option = nil,
},
---@class gopher.ConfigIfErr
iferr = {
-- choose a custom error message, nil to use default
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
---@type string|nil
message = nil,
},
---@class gopher.ConfigJson2Go
json2go = {
-- command used to open interactive input.
-- e.g: `split`, `botright split`, `tabnew`
interactive_cmd = "vsplit",
-- name of autogenerated struct, if nil none, will the default one of json2go.
-- e.g: "MySuperCoolName"
---@type string|nil
type_name = nil,
},
}
<
Class ~
{gopher.Config}
Fields ~
{setup} `(fun(user_config?: gopher.Config))`
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-commands*
If don't want to automatically register plugins' commands,
you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin.
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-struct-tags*
`struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to
struct fields.
Usage ~
How to add/remove/clear tags to struct fields:
1. Place cursor on the struct
2. Run `:GoTagAdd json` to add json tags to struct fields
3. Run `:GoTagRm json` to remove json tags to struct fields
4. Run `:GoTagClear` to clear all tags from struct fields
If you want to add/remove tag with options, you can use `json=omitempty`
(where json is tag, and omitempty is its option).
Example: `:GoTagAdd xml json=omitempty`
NOTE: if you dont specify the tag it will use `json` as default
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-json2go*
Convert json to go type annotations.
Usage ~
`:GoJson` opens a temporary buffer where you can paste or write JSON.
Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the
generated Go struct into the original buffer at the cursor position.
Alternatively, you can pass JSON directly as an argument:
>vim
:GoJson {"name": "Alice", "age": 30}
<
------------------------------------------------------------------------------
*json2go.transform()*
`json2go.transform`({json_str})
Parameters ~
{json_str} `(string)` Json string that is going to be converted to go type.
Return ~
`(string)` `(optional)` Go type, or nil if failed.
------------------------------------------------------------------------------
*json2go.json2go()*
`json2go.json2go`({json_str})
Converts json string to go type, and puts result under the cursor. If
[json_str] is nil, will open an interactive prompt (with cmd set in
config).
Parameters ~
{json_str} `(optional)` `(string)`
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-impl*
Integration of `impl` tool to generate method stubs for interfaces.
Usage ~
1. Automatically implement an interface for a struct:
- Place your cursor on the struct where you want to implement the interface.
- Run `:GoImpl io.Reader`
- This will automatically determine the receiver and implement the `io.Reader` interface.
2. Specify a custom receiver:
- Place your cursor on the struct
- Run `:GoImpl w io.Writer`, where:
- `w` is the receiver.
- `io.Writer` is the interface to implement.
3. Explicitly specify the receiver, struct, and interface:
- No need to place the cursor on the struct if all arguments are provided.
- Run `:GoImpl r RequestReader io.Reader`, where:
- `r` is the receiver.
- `RequestReader` is the struct.
- `io.Reader` is the interface to implement.
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 specific function/method:
1. Place your cursor on the desired function/method.
2. 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: https://github.com/cweill/gotests
If you prefer named tests, you can enable them in |gopher.nvim-config|.
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-iferr*
`iferr` provides a way to way to automatically insert `if err != nil` check.
If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
Usage ~
Execute `:GoIfErr` near any `err` variable to insert the check
==============================================================================
------------------------------------------------------------------------------
*gopher.nvim-comments*
This module provides a way to generate comments for Go code.
Usage ~
Set cursor on line with function/method/struct/etc and
run `:GoCmt` to generate a comment.
vim:tw=78:ts=8:noet:ft=help:norl:

View file

@ -1,39 +1,72 @@
return {
---@param t table
---@return boolean
empty = function(t)
if t == nil then
return true
local c = require "gopher.config"
local log = require "gopher._utils.log"
local utils = {}
---@param msg string
---@param lvl? integer by default `vim.log.levels.INFO`
function utils.notify(msg, lvl)
lvl = lvl or vim.log.levels.INFO
vim.notify(msg, lvl, {
---@diagnostic disable-next-line:undefined-field
title = c.___plugin_name,
})
log.debug(msg)
end
return next(t) == nil
end,
---@param path string
---@return string
function utils.readfile_joined(path)
return table.concat(vim.fn.readfile(path), "\n")
end
---@param t string[]
---@return string[]
function utils.remove_empty_lines(t)
local res = {}
for _, line in ipairs(t) do
if line ~= "" then
table.insert(res, line)
end
end
return res
end
---@param s string
---@return string
rtrim = function(s)
local n = #s
while n > 0 and s:find("^%s", n) do
n = n - 1
function utils.trimend(s)
local r, _ = string.gsub(s, "%s+$", "")
return r
end
return s:sub(1, n)
end,
---@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
-- Since indentation can be spaces or tabs, that's my hack around it
---@param line string
---@param indent integer
---@return string
function utils.indent(line, indent)
local char = string.sub(line, 1, 1)
if char ~= " " and char ~= "\t" then
char = " "
end
return string.rep(char, indent)
end
return false
end,
}
---@generic T
---@param tbl T[]
---@return T[]
function utils.list_unique(tbl)
if vim.fn.has "nvim-0.12" == 1 then
return vim.list.unique(tbl)
end
for i = #tbl, 1, -1 do
for j = 1, i - 1 do
if tbl[i] == tbl[j] then
table.remove(tbl, i)
break
end
end
end
return tbl
end
return utils

171
lua/gopher/_utils/log.lua Normal file
View file

@ -0,0 +1,171 @@
-- 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
---@diagnostic disable-next-line:undefined-field
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 doesn't 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

View file

@ -0,0 +1,39 @@
local c = require "gopher.config"
local runner = {}
---@class gopher.RunnerOpts
---@field cwd? string
---@field timeout? number
---@field stdin? boolean|string|string[]
---@field text? boolean
---@param cmd (string|number)[]
---@param on_exit fun(out:vim.SystemCompleted)
---@param opts? gopher.RunnerOpts
---@return vim.SystemObj
function runner.async(cmd, on_exit, opts)
opts = opts or {}
return vim.system(cmd, {
cwd = opts.cwd or nil,
timeout = opts.timeout or c.timeout,
stdin = opts.stdin or nil,
text = opts.text or true,
}, on_exit)
end
---@param cmd (string|number)[]
---@param opts? gopher.RunnerOpts
---@return vim.SystemCompleted
function runner.sync(cmd, opts)
opts = opts or {}
return vim
.system(cmd, {
cwd = opts.cwd or nil,
timeout = opts.timeout or c.timeout,
stdin = opts.stdin or nil,
text = opts.text or true,
})
:wait()
end
return runner

160
lua/gopher/_utils/ts.lua Normal file
View file

@ -0,0 +1,160 @@
local ts = {}
local queries = {
struct = [[
[(type_spec name: (type_identifier) @_name
type: (struct_type))
(var_declaration (var_spec
name: (identifier) @_name @_var
type: (struct_type)))
(short_var_declaration
left: (expression_list (identifier) @_name @_var)
right: (expression_list (composite_literal
type: (struct_type))))]
]],
struct_field = [[
(field_declaration name: (field_identifier) @_name)
]],
func = [[
[(function_declaration name: (identifier) @_name)
(method_declaration name: (field_identifier) @_name)
(method_elem name: (field_identifier) @_name)]
]],
package = [[
(package_identifier) @_name
]],
interface = [[
(type_spec
name: (type_identifier) @_name
type: (interface_type))
]],
var = [[
[(var_declaration (var_spec name: (identifier) @_name))
(short_var_declaration
left: (expression_list (identifier) @_name @_var))]
]],
}
---@param parent_type string[]
---@param node TSNode
---@return TSNode?
local function get_parent_node(parent_type, node)
---@type TSNode?
local current = node
while current do
if vim.tbl_contains(parent_type, current:type()) then
break
end
current = current:parent()
if current == nil then
return nil
end
end
return current
end
---@param query vim.treesitter.Query
---@param node TSNode
---@param bufnr integer
---@return {name:string, is_varstruct:boolean}
local function get_captures(query, node, bufnr)
local res = {}
for id, _node in query:iter_captures(node, bufnr) do
if query.captures[id] == "_name" then
res["name"] = vim.treesitter.get_node_text(_node, bufnr)
end
if query.captures[id] == "_var" then
res["is_varstruct"] = true
end
end
return res
end
---@class gopher.TsResult
---@field name string Name of the struct, function, etc
---@field start integer Line number where the declaration starts
---@field end_ integer Line number where the declaration ends
---@field indent integer Number of spaces/tabs in the current cursor line
---@field is_varstruct boolean Is struct declared as `var S struct{}` or `s := struct{}{}`
---@param bufnr integer
---@param parent_type string[]
---@param query string
---@return gopher.TsResult
local function do_stuff(bufnr, parent_type, query)
if not vim.treesitter.get_parser(bufnr, "go") then
error "No treesitter parser found for go"
end
local node = vim.treesitter.get_node { bufnr = bufnr }
if not node then
error "No nodes found under the cursor"
end
local parent_node = get_parent_node(parent_type, node)
if not parent_node then
error "No parent node found under the cursor"
end
local q = vim.treesitter.query.parse("go", query)
local res = get_captures(q, parent_node, bufnr)
assert(res.name ~= nil, "No capture name found")
local start_row, start_col, end_row, _ = parent_node:range()
res["indent"] = start_col
res["start"] = start_row + 1
res["end_"] = end_row + 1
return res
end
---@param bufnr integer
function ts.get_struct_under_cursor(bufnr)
--- should be both type_spec and type_declaration
--- because in cases like `type ( T struct{}, U struct{} )`
---
--- var_declaration is for cases like `var x struct{}`
--- short_var_declaration is for cases like `x := struct{}{}`
---
--- it always chooses last struct type in the list
return do_stuff(bufnr, {
"type_spec",
"type_declaration",
"var_declaration",
"short_var_declaration",
}, queries.struct)
end
---@param bufnr integer
function ts.get_struct_field_under_cursor(bufnr)
return do_stuff(bufnr, { "field_declaration" }, queries.struct_field)
end
---@param bufnr integer
function ts.get_func_under_cursor(bufnr)
--- since this handles both and funcs and methods we should check for both parent nodes
return do_stuff(bufnr, {
"method_elem",
"function_declaration",
"method_declaration",
}, queries.func)
end
---@param bufnr integer
function ts.get_package_under_cursor(bufnr)
return do_stuff(bufnr, { "package_clause" }, queries.package)
end
---@param bufnr integer
function ts.get_interface_under_cursor(bufnr)
return do_stuff(bufnr, { "type_declaration" }, queries.interface)
end
---@param bufnr integer
function ts.get_variable_under_cursor(bufnr)
return do_stuff(bufnr, { "var_declaration", "short_var_declaration" }, queries.var)
end
return ts

View file

@ -1,34 +0,0 @@
local nodes = require "gopher._utils.ts.nodes"
local M = {
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]],
},
}
---@return table
local function get_name_defaults()
return {
["func"] = "function",
["if"] = "if",
["else"] = "else",
["for"] = "for",
}
end
---@param row any
---@param col any
---@param bufnr any
---@return table|nil
function M.get_struct_node_at_pos(row, col, bufnr)
local query = M.querys.struct_block .. " " .. M.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
print "struct not found"
else
return ns[#ns]
end
end
return M

View file

@ -1,136 +0,0 @@
local ts_query = require "nvim-treesitter.query"
local parsers = require "nvim-treesitter.parsers"
local locals = require "nvim-treesitter.locals"
local M = {}
local function intersects(row, col, sRow, sCol, eRow, eCol)
if sRow > row or eRow < row then
return false
end
if sRow == row and sCol > col then
return false
end
if eRow == row and eCol < col then
return false
end
return true
end
---@param nodes table
---@param row string
---@param col string
---@return table
function M.intersect_nodes(nodes, row, col)
local found = {}
for idx = 1, #nodes do
local node = nodes[idx]
local sRow = node.dim.s.r
local sCol = node.dim.s.c
local eRow = node.dim.e.r
local eCol = node.dim.e.c
if intersects(row, col, sRow, sCol, eRow, eCol) then
table.insert(found, node)
end
end
return found
end
---@param nodes table
---@return table
function M.sort_nodes(nodes)
table.sort(nodes, function(a, b)
return M.count_parents(a) < M.count_parents(b)
end)
return nodes
end
---@param query string
---@param lang string
---@param bufnr integer
---@param pos_row string
---@return string
function M.get_all_nodes(query, lang, _, bufnr, pos_row, _)
bufnr = bufnr or 0
pos_row = pos_row or 30000
local ok, parsed_query = pcall(function()
return vim.treesitter.parse_query(lang, query)
end)
if not ok then
return nil
end
local parser = parsers.get_parser(bufnr, lang)
local root = parser:parse()[1]:root()
local start_row, _, end_row, _ = root:range()
local results = {}
for match in ts_query.iter_prepared_matches(parsed_query, root, bufnr, start_row, end_row) do
local sRow, sCol, eRow, eCol, declaration_node
local type, name, op = "", "", ""
locals.recurse_local_nodes(match, function(_, node, path)
local idx = string.find(path, ".[^.]*$")
op = string.sub(path, idx + 1, #path)
type = string.sub(path, 1, idx - 1)
if op == "name" then
name = vim.treesitter.query.get_node_text(node, bufnr)
elseif op == "declaration" or op == "clause" then
declaration_node = node
sRow, sCol, eRow, eCol = node:range()
sRow = sRow + 1
eRow = eRow + 1
sCol = sCol + 1
eCol = eCol + 1
end
end)
if declaration_node ~= nil then
table.insert(results, {
declaring_node = declaration_node,
dim = { s = { r = sRow, c = sCol }, e = { r = eRow, c = eCol } },
name = name,
operator = op,
type = type,
})
end
end
return results
end
---@param query string
---@param default string
---@param bufnr string
---@param row string
---@param col string
---@return table
function M.nodes_at_cursor(query, default, bufnr, row, col)
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
row, col = unpack(vim.api.nvim_win_get_cursor(0))
end
local nodes = M.get_all_nodes(query, ft, default, bufnr, row, col)
if nodes == nil then
print "Unable to find any nodes. place your cursor on a go symbol and try again"
return nil
end
nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col))
if nodes == nil or #nodes == 0 then
print("Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col))
return nil
end
return nodes
end
return M

71
lua/gopher/comment.lua Normal file
View file

@ -0,0 +1,71 @@
---@toc_entry Generate comments
---@tag gopher.nvim-comments
---@text
--- This module provides a way to generate comments for Go code.
---
---@usage
--- Set cursor on line with function/method/struct/etc and
--- run `:GoCmt` to generate a comment.
local ts = require "gopher._utils.ts"
local log = require "gopher._utils.log"
local u = require "gopher._utils"
local comment = {}
--- NOTE: The order of functions executed inside this function is IMPORTANT.
--- This function is extremely fragile; run tests after making any changes.
---
---@param bufnr integer
---@param line string
---@return string
---@dochide
local function generate(bufnr, line)
local sf_ok, sf_res = pcall(ts.get_struct_field_under_cursor, bufnr)
if sf_ok then
return u.indent(line, sf_res.indent) .. "// " .. sf_res.name .. " "
end
local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr)
if s_ok then
return u.indent(line, s_res.indent) .. "// " .. s_res.name .. " "
end
local v_ok, v_res = pcall(ts.get_variable_under_cursor, bufnr)
if v_ok then
return u.indent(line, v_res.indent) .. "// " .. v_res.name .. " "
end
local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr)
if f_ok then
return u.indent(line, f_res.indent) .. "// " .. f_res.name .. " "
end
local i_ok, i_res = pcall(ts.get_interface_under_cursor, bufnr)
if i_ok then
return u.indent(line, i_res.indent) .. "// " .. i_res.name .. " "
end
local p_ok, p_res = pcall(ts.get_package_under_cursor, bufnr)
if p_ok then
return "// Package " .. p_res.name .. " provides "
end
return "// "
end
function comment.comment()
local bufnr = vim.api.nvim_get_current_buf()
local lnum = vim.fn.getcurpos()[2]
local line = vim.fn.getline(lnum)
local cmt = generate(bufnr, line)
log.debug("generated comment:", {
comment = cmt,
line = line,
})
vim.fn.append(lnum - 1, cmt)
vim.fn.setpos(".", { bufnr, lnum, #cmt })
vim.cmd "startinsert!"
end
return comment

142
lua/gopher/config.lua Normal file
View file

@ -0,0 +1,142 @@
---@type gopher.Config
---@dochide
---@diagnostic disable-next-line: missing-fields .setup() gets injected later
local config = {}
---@tag gopher.nvim-config.ConfigGoTagTransform
---@text Possible values for |gopher.Config|.gotag.transform:
---
---@dochide
---@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
---@toc_entry Config
---@tag gopher.nvim-config
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
---@class gopher.Config
---@field setup fun(user_config?: gopher.Config)
local default_config = {
-- log level, you might consider using DEBUG or TRACE for debugging the plugin
---@type number
log_level = vim.log.levels.INFO,
-- timeout for running internal commands
---@type number
timeout = 2000,
-- timeout for running installer commands(e.g :GoDepsInstall, :GoDepsInstallSync)
---@type number
installer_timeout = 999999,
-- user specified paths to binaries
---@class gopher.ConfigCommand
commands = {
go = "go",
gomodifytags = "gomodifytags",
gotests = "gotests",
impl = "impl",
iferr = "iferr",
json2go = "json2go",
},
---@class gopher.ConfigGotests
gotests = {
-- a default template that gotess will use.
-- gotets doesn't have template named `default`, we use it to represent absence of the provided template.
template = "default",
-- path to a directory containing custom test code templates
---@type string|nil
template_dir = nil,
-- use named tests(map with test name as key) in table tests(slice of structs by default)
named = false,
},
---@class gopher.ConfigGoTag
gotag = {
---@type gopher.ConfigGoTagTransform
transform = "snakecase",
-- default tags to add to struct fields
default_tag = "json",
-- default tag option added struct fields, set to nil to disable
-- e.g: `option = "json=omitempty,xml=omitempty`
---@type string|nil
option = nil,
},
---@class gopher.ConfigIfErr
iferr = {
-- choose a custom error message, nil to use default
-- e.g: `message = 'fmt.Errorf("failed to %w", err)'`
---@type string|nil
message = nil,
},
---@class gopher.ConfigJson2Go
json2go = {
-- command used to open interactive input.
-- e.g: `split`, `botright split`, `tabnew`
interactive_cmd = "vsplit",
-- name of autogenerated struct, if nil none, will the default one of json2go.
-- e.g: "MySuperCoolName"
---@type string|nil
type_name = nil,
},
}
--minidoc_afterlines_end
---@type gopher.Config
---@dochide
local _config = default_config
-- I am kinda secret so don't tell anyone about me even dont use me
--
-- if you don't believe 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
---@dochide
function config.setup(user_config)
vim.validate("user_config", user_config, "table", true)
_config = vim.tbl_deep_extend("force", vim.deepcopy(default_config), user_config or {})
vim.validate("log_level", _config.log_level, "number")
vim.validate("timeout", _config.timeout, "number")
vim.validate("installer_timeout", _config.timeout, "number")
vim.validate("commands", _config.commands, "table")
vim.validate("commands.go", _config.commands.go, "string")
vim.validate("commands.gomodifytags", _config.commands.gomodifytags, "string")
vim.validate("commands.gotests", _config.commands.gotests, "string")
vim.validate("commands.impl", _config.commands.impl, "string")
vim.validate("commands.iferr", _config.commands.iferr, "string")
vim.validate("commands.json2go", _config.commands.json2go, "string")
vim.validate("gotests", _config.gotests, "table")
vim.validate("gotests.template", _config.gotests.template, "string")
vim.validate("gotests.template_dir", _config.gotests.template_dir, { "string", "nil" })
vim.validate("gotests.named", _config.gotests.named, "boolean")
vim.validate("gotag", _config.gotag, "table")
vim.validate("gotag.transform", _config.gotag.transform, "string")
vim.validate("gotag.default_tag", _config.gotag.default_tag, "string")
vim.validate("gotag.option", _config.gotag.option, { "string", "nil" })
vim.validate("iferr", _config.iferr, "table")
vim.validate("iferr.message", _config.iferr.message, { "string", "nil" })
vim.validate("json2go.installer_timeout", _config.json2go.interactive_cmd, "string")
vim.validate("json2go.type_name", _config.json2go.type_name, { "string", "nil" })
end
setmetatable(config, {
__index = function(_, key)
return _config[key]
end,
})
---@dochide
return config

56
lua/gopher/go.lua Normal file
View file

@ -0,0 +1,56 @@
local c = require "gopher.config"
local u = require "gopher._utils"
local r = require "gopher._utils.runner"
local go = {}
local function run(subcmd, args)
local rs = r.sync { c.commands.go, subcmd, unpack(args) }
if rs.code ~= 0 then
error("go " .. subcmd .. " failed: " .. rs.stderr)
end
u.notify(c.commands.go .. " " .. subcmd .. " ran successful")
return rs.stdout
end
---@param args string[]
function go.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
run("get", args)
end
---@param args string[]
function go.mod(args)
run("mod", args)
end
---@param args string[]
function go.work(args)
-- TODO: use `gopls.tidy`
run("work", args)
end
---Executes `go generate`
---If only argument is `%` it's going to be equivalent to `go generate <path to current file>`
---@param args string[]
function go.generate(args)
-- TODO: use `gopls.generate`
if #args == 0 then
error "please provide arguments"
end
if #args == 1 and args[1] == "%" then
args[1] = vim.fn.expand "%"
end
run("generate", args)
end
return go

View file

@ -1,27 +0,0 @@
local Job = require "plenary.job"
return function(...)
local 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
local cmd_args = vim.list_extend({ "get" }, args)
Job
:new({
command = "go",
args = cmd_args,
on_exit = function(_, retval)
if retval ~= 0 then
print("command exited with code " .. retval)
return
else
print "command runs successfully"
end
end,
})
:start()
end

View file

@ -1,21 +0,0 @@
local Job = require "plenary.job"
return function(...)
local args = { ... }
local cmd_args = vim.list_extend({ "mod" }, args)
Job
:new({
command = "go",
args = cmd_args,
on_exit = function(_, retval)
if retval ~= 0 then
print("command exited with code " .. retval)
return
else
print "command runs successfully"
end
end,
})
:start()
end

76
lua/gopher/gotests.lua Normal file
View file

@ -0,0 +1,76 @@
---@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 specific function/method:
--- 1. Place your cursor on the desired function/method.
--- 2. 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: https://github.com/cweill/gotests
---
--- If you prefer named tests, you can enable them in |gopher.nvim-config|.
local c = require "gopher.config"
local ts_utils = require "gopher._utils.ts"
local r = require "gopher._utils.runner"
local u = require "gopher._utils"
local log = require "gopher._utils.log"
local gotests = {}
---@param args table
---@dochide
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
table.insert(args, "-w")
table.insert(args, vim.fn.expand "%")
log.debug("generating tests with args: ", args)
local rs = r.sync { c.commands.gotests, unpack(args) }
if rs.code ~= 0 then
error("gotests failed: " .. rs.stderr)
end
u.notify "unit test(s) generated"
end
-- generate unit test for one function
function gotests.func_test()
local bufnr = vim.api.nvim_get_current_buf()
local func = ts_utils.get_func_under_cursor(bufnr)
add_test { "-only", func.name }
end
-- generate unit tests for all functions in current file
function gotests.all_tests()
add_test { "-all" }
end
-- generate unit tests for all exported functions
function gotests.all_exported_tests()
add_test { "-exported" }
end
return gotests

View file

@ -1,36 +1,59 @@
local utils = require "gopher._utils"
local M = {
_required = {
plugins = {
{ lib = "plenary" },
{ lib = "nvim-treesitter" },
local c = require("gopher.config").commands
local health = {}
local deps = {
vim_version = "nvim-0.10",
bin = {
{
bin = c.go,
msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`, `:GoInstallDepsSync`",
},
binarys = {
{ bin = "go", help = "required for GoMod command" },
{ bin = "gomodifytags", help = "required for modify struct tags" },
{ bin = "impl", help = "required for interface implementing" },
{ bin = c.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`" },
{ bin = c.impl, msg = "required for `:GoImpl`" },
{ bin = c.iferr, msg = "required for `:GoIfErr`" },
{ bin = c.gotests, msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`" },
},
treesitter = {
{ parser = "go", msg = "required for most of the parts of `gopher.nvim`" },
},
}
function M.check()
vim.health.report_start "Required plugins"
for _, plugin in ipairs(M._required.plugins) do
if utils.lualib_is_found(plugin.lib) then
vim.health.report_ok(plugin.lib .. " installed.")
---@param bin {bin:string, msg:string, optional:boolean}
local function check_binary(bin)
if vim.fn.executable(bin.bin) == 1 then
vim.health.ok(bin.bin .. " is found oh PATH: `" .. vim.fn.exepath(bin.bin) .. "`")
else
vim.health.report_error(plugin.lib .. " not found. Gopher.nvim will not work without it!")
vim.health.error(bin.bin .. " not found on PATH, " .. bin.msg)
end
end
vim.health.report_start "Required go tools"
for _, binary in ipairs(M._required.binarys) do
if utils.binary_is_found(binary.bin) then
vim.health.report_ok(binary.bin .. " installed")
---@param ts {parser:string, msg:string}
local function check_treesitter(ts)
local ok, parser = pcall(vim.treesitter.get_parser, 0, ts.parser)
if ok and parser ~= nil then
vim.health.ok("`" .. ts.parser .. "` parser is installed")
else
vim.health.report_warn(binary.bin .. " is not installed but " .. binary.help)
end
vim.health.error("`" .. ts.parser .. "` parser not found")
end
end
return M
function health.check()
vim.health.start "Neovim version"
if vim.fn.has(deps.vim_version) == 1 then
vim.health.ok "Neovim version is compatible"
else
vim.health.error(deps.vim_version .. " or newer is required")
end
vim.health.start "Required binaries (those can be installed with `:GoInstallDeps`)"
for _, bin in ipairs(deps.bin) do
check_binary(bin)
end
vim.health.start "Treesitter"
for _, parser in ipairs(deps.treesitter) do
check_treesitter(parser)
end
end
return health

48
lua/gopher/iferr.lua Normal file
View file

@ -0,0 +1,48 @@
-- Thanks https://github.com/koron/iferr for vim implementation
---@toc_entry Iferr
---@tag gopher.nvim-iferr
---@text
--- `iferr` provides a way to way to automatically insert `if err != nil` check.
--- If you want to change `-message` option of `iferr` tool, see |gopher.nvim-config|
---
---@usage Execute `:GoIfErr` near any `err` variable to insert the check
local c = require "gopher.config"
local u = require "gopher._utils"
local r = require "gopher._utils.runner"
local log = require "gopher._utils.log"
local iferr = {}
function iferr.iferr()
local curb = vim.fn.wordcount().cursor_bytes
local pos = vim.fn.getcurpos()[2]
local fpath = vim.fn.expand "%"
local cmd = { c.commands.iferr, "-pos", curb }
if c.iferr.message ~= nil and type(c.iferr.message) == "string" then
table.insert(cmd, "-message")
table.insert(cmd, c.iferr.message)
end
local rs = r.sync(cmd, {
stdin = u.readfile_joined(fpath),
})
if rs.code ~= 0 then
if string.find(rs.stderr, "no functions at") then
u.notify("iferr: no function at " .. curb, vim.log.levels.ERROR)
log.warn("iferr: no function at " .. curb)
return
end
log.error("ferr: failed. output: " .. rs.stderr)
error("iferr failed: " .. rs.stderr)
end
vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n")))
vim.cmd [[silent normal! j=2j]]
vim.fn.setpos(".", pos --[[@as integer[] ]])
end
return iferr

View file

@ -1,74 +1,80 @@
local Job = require "plenary.job"
---@toc_entry Auto implementation of interface methods
---@tag gopher.nvim-impl
---@text
--- Integration of `impl` tool to generate method stubs for interfaces.
---
---@usage
--- 1. Automatically implement an interface for a struct:
--- - Place your cursor on the struct where you want to implement the interface.
--- - Run `:GoImpl io.Reader`
--- - This will automatically determine the receiver and implement the `io.Reader` interface.
---
--- 2. Specify a custom receiver:
--- - Place your cursor on the struct
--- - Run `:GoImpl w io.Writer`, where:
--- - `w` is the receiver.
--- - `io.Writer` is the interface to implement.
---
--- 3. Explicitly specify the receiver, struct, and interface:
--- - No need to place the cursor on the struct if all arguments are provided.
--- - Run `:GoImpl r RequestReader io.Reader`, where:
--- - `r` is the receiver.
--- - `RequestReader` is the struct.
--- - `io.Reader` is the interface to implement.
---
--- 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 = {}
local function get_struct()
local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
if ns == nil then
print "put cursor on struct or specify a receiver"
return ""
end
vim.api.nvim_win_set_cursor(0, {
ns.dim.e.r,
ns.dim.e.c,
})
return ns.name
end
return function(...)
function impl.impl(...)
local args = { ... }
local iface, recv_name = "", ""
local recv = get_struct()
local iface, recv = "", ""
local bufnr = vim.api.nvim_get_current_buf()
if #args == 0 then
iface = vim.fn.input "impl: generating method stubs for interface: "
vim.cmd "redeaw!"
if iface == "" then
print "usage: GoImpl f *File io.Reader"
end
elseif #args == 1 then -- :GoImpl io.Reader
recv = string.lower(recv) .. " *" .. recv
vim.cmd "redraw!"
iface = select(1, ...)
elseif #args == 2 then -- :GoImpl w io.Writer
recv_name = select(1, ...)
recv = string.format("%s *%s", recv_name, recv)
iface = select(#args, ...)
elseif #args > 2 then
iface = select(#args, ...)
recv = select(#args - 1, ...)
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 = "impl",
args = cmd_args,
on_exit = function(data, retval)
if retval ~= 0 then
print("command exited with code " .. retval)
u.notify("arguments not provided. usage: :GoImpl f *File io.Reader", vim.log.levels.ERROR)
return
elseif #args == 1 then -- :GoImpl io.Reader
local st = ts_utils.get_struct_under_cursor(bufnr)
iface = args[1]
recv = string.lower(st.name) .. " *" .. st.name
elseif #args == 2 then -- :GoImpl w io.Writer
local st = ts_utils.get_struct_under_cursor(bufnr)
iface = args[2]
recv = args[1] .. " *" .. st.name
elseif #args == 3 then -- :GoImpl r Struct io.Reader
recv = args[1] .. " *" .. args[2]
iface = args[3]
end
res_data = data:result()
end,
})
:sync()
assert(iface ~= "", "interface not provided")
assert(recv ~= "", "receiver not provided")
local dir = vim.fn.fnameescape(vim.fn.expand "%:p:h")
local rs = r.sync { c.impl, "-dir", dir, recv, iface }
if rs.code ~= 0 then
error("failed to implement interface: " .. rs.stderr)
end
local pos = vim.fn.getcurpos()[2]
table.insert(res_data, 1, "")
vim.fn.append(pos, res_data)
local output = u.remove_empty_lines(vim.split(rs.stdout, "\n"))
-- table.insert(res_data, 1, "")
-- vim.fn.append(vim.fn.getcurpos()[2], res_data)
table.insert(output, 1, "")
vim.fn.append(pos, output)
end
return impl

View file

@ -1,11 +1,66 @@
--- *gopher.nvim* Enhance your golang experience
---
--- MIT License Copyright (c) 2025 Oleksandr Smirnov
---
--- ==============================================================================
---
--- 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
---@toc
local log = require "gopher._utils.log"
local tags = require "gopher.struct_tags"
local tests = require "gopher.gotests"
local go = require "gopher.go"
local gopher = {}
gopher.install_deps = require "gopher.installer"
gopher.tags_add = tags.add
gopher.tags_rm = tags.remove
gopher.mod = require "gopher.gomod"
gopher.get = require "gopher.goget"
gopher.impl = require "gopher.impl"
---@toc_entry Setup
---@tag gopher.nvim-setup()
---@text Setup function. This method simply merges default config with opts table.
--- You can read more about configuration at |gopher.nvim-config|
--- Calling this function is optional, if you ok with default settings.
--- See |gopher.nvim.config|
---
---@usage >lua
--- require("gopher").setup {} -- use default config or replace {} with your own
--- <
---@param user_config gopher.Config See |gopher.nvim-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-dependencies
---@text
--- Gopher.nvim implements most of its features using third-party tools. To
--- install plugin's dependencies, you can run:
--- `:GoInstallDeps` or `:GoInstallDepsSync`
--- or use `require("gopher").install_deps()` if you prefer 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").comment
gopher.tags = {
add = tags.add,
rm = tags.remove,
clear = tags.clear,
}
gopher.test = {
add = tests.func_test,
exported = tests.all_exported_tests,
all = tests.all_tests,
}
gopher.get = go.get
gopher.mod = go.mod
gopher.work = go.work
gopher.generate = go.generate
return gopher

View file

@ -1,31 +1,66 @@
local Job = require "plenary.job"
local c = require "gopher.config"
local r = require "gopher._utils.runner"
local u = require "gopher._utils"
local log = require "gopher._utils.log"
local installer = {}
local urls = {
gomodifytags = "github.com/fatih/gomodifytags",
impl = "github.com/josharian/impl",
gomodifytags = "github.com/fatih/gomodifytags@latest",
impl = "github.com/josharian/impl@latest",
gotests = "github.com/cweill/gotests/...@develop",
iferr = "github.com/koron/iferr@latest",
json2go = "olexsmir.xyz/json2go/cmd/json2go@latest",
}
local function install(pkg)
local url = urls[pkg] .. "@latest"
---@param opt vim.SystemCompleted
---@param url string
local function handle_intall_exit(opt, url)
if opt.code ~= 0 then
vim.schedule(function()
u.notify("go install failed: " .. url)
end)
Job
:new({
command = "go",
args = { "install", url },
on_exit = function(_, ret_val)
if ret_val ~= 0 then
print("command exited with code " .. ret_val)
log.error("go install failed:", "url", url, "opt", vim.inspect(opt))
return
end
print("install " .. url .. " finished")
end,
})
:sync()
vim.schedule(function()
u.notify("go install-ed: " .. url)
end)
end
---@param url string
local function install(url)
vim.schedule(function()
u.notify("go install-ing: " .. url)
end)
r.async({ c.commands.go, "install", url }, function(opt)
handle_intall_exit(opt, url)
end, { timeout = c.installer_timeout })
end
---@param url string
local function install_sync(url)
vim.schedule(function()
u.notify("go install-ing: " .. url)
end)
local rs = r.sync({ c.commands.go, "install", url }, { timeout = c.installer_timeout })
handle_intall_exit(rs, url)
end
---Install required go deps
return function()
for pkg, _ in pairs(urls) do
install(pkg)
---@param opts? {sync:boolean}
function installer.install_deps(opts)
opts = opts or {}
for _, url in pairs(urls) do
if opts.sync then
install_sync(url)
else
install(url)
end
end
end
return installer

137
lua/gopher/json2go.lua Normal file
View file

@ -0,0 +1,137 @@
---@toc_entry json2go
---@tag gopher.nvim-json2go
---@text
--- Convert json to go type annotations.
---
---@usage
--- `:GoJson` opens a temporary buffer where you can paste or write JSON.
--- Saving the buffer (`:w` or `:wq`) automatically closes it and inserts the
--- generated Go struct into the original buffer at the cursor position.
---
--- Alternatively, you can pass JSON directly as an argument:
--- >vim
--- :GoJson {"name": "Alice", "age": 30}
--- <
local c = require "gopher.config"
local log = require "gopher._utils.log"
local u = require "gopher._utils"
local r = require "gopher._utils.runner"
local json2go = {}
---@dochide
---@param bufnr integer
---@param cpos integer
---@param type_ string
local function apply(bufnr, cpos, type_)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local input_lines = u.remove_empty_lines(vim.split(type_, "\n"))
for i, line in pairs(input_lines) do
table.insert(lines, cpos + i, line)
end
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
end
-- Convert json string to go type.
---
---@param json_str string Json string that is going to be converted to go type.
---@return string? Go type, or nil if failed.
function json2go.transform(json_str)
local cmd = { c.commands.json2go }
if c.json2go.type_name then
table.insert(cmd, "-type", c.json2go.type_name)
end
local rs = r.sync(cmd, { stdin = json_str })
if rs.code ~= 0 then
u.notify("json2go: got this error: " .. rs.stdout, vim.log.levels.ERROR)
log.error("json2go: got this error: " .. rs.stdout)
return
end
return rs.stdout
end
---@dochide
---@param ocpos integer
local function interactive(ocpos)
local obuf = vim.api.nvim_get_current_buf()
local owin = vim.api.nvim_get_current_win()
-- setup the input window
local buf = vim.api.nvim_create_buf(false, true)
vim.cmd(c.json2go.interactive_cmd)
local win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf(win, buf)
vim.api.nvim_buf_set_name(buf, "[GoJson input]")
vim.api.nvim_set_option_value("filetype", "jsonc", { buf = buf })
vim.api.nvim_set_option_value("buftype", "acwrite", { buf = buf })
vim.api.nvim_set_option_value("swapfile", false, { buf = buf })
vim.api.nvim_set_option_value("bufhidden", "delete", { buf = buf })
vim.api.nvim_buf_set_lines(buf, 0, -1, false, {
"// Write your json here.",
"// Writing and quitting (:wq), will generate go struct under the cursor.",
"",
"",
})
vim.api.nvim_create_autocmd("BufLeave", { buffer = buf, command = "stopinsert" })
vim.api.nvim_create_autocmd("BufWriteCmd", {
buffer = buf,
once = true,
callback = function()
local input = vim.api.nvim_buf_get_lines(buf, 0, -1, true)
local inp = table.concat(
vim
.iter(input)
:filter(function(line)
local found = string.find(line, "^//.*")
return (not found) or (line == "")
end)
:totable(),
"\n"
)
local go_type = json2go.transform(inp)
if not go_type then
error "cound't convert json to go type"
end
vim.api.nvim_set_option_value("modified", false, { buf = buf })
apply(obuf, ocpos, go_type)
vim.api.nvim_set_current_win(owin)
vim.api.nvim_win_set_cursor(owin, { ocpos + 1, 0 })
vim.schedule(function()
pcall(vim.api.nvim_win_close, win, true)
pcall(vim.api.nvim_buf_delete, buf, {})
end)
end,
})
vim.cmd "normal! G"
vim.cmd "startinsert"
end
--- Converts json string to go type, and puts result under the cursor. If
--- [json_str] is nil, will open an interactive prompt (with cmd set in
--- config).
---
---@param json_str? string
function json2go.json2go(json_str)
local cur_line = vim.api.nvim_win_get_cursor(0)[1]
if not json_str then
interactive(cur_line)
return
end
local go_type = json2go.transform(json_str)
if not go_type then
error "cound't convert json to go type"
end
apply(0, cur_line, go_type)
end
return json2go

View file

@ -1,116 +1,200 @@
local Job = require "plenary.job"
local ts_utils = require "gopher._utils.ts"
local utils = require "gopher._utils"
local M = {}
---@toc_entry Modify struct tags
---@tag gopher.nvim-struct-tags
---@text
--- `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to
--- struct fields.
---
---@usage
--- How to add/remove/clear tags to struct fields:
--- 1. Place cursor on the struct
--- 2. Run `:GoTagAdd json` to add json tags to struct fields
--- 3. Run `:GoTagRm json` to remove json tags to struct fields
--- 4. Run `:GoTagClear` to clear all tags from struct fields
---
--- If you want to add/remove tag with options, you can use `json=omitempty`
--- (where json is tag, and omitempty is its option).
--- Example: `:GoTagAdd xml json=omitempty`
---
---
--- NOTE: if you dont specify the tag it will use `json` as default
---
--- 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`
--- }
--- <
local function modify(...)
local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter
local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0)))
if ns == nil or ns == {} then
return
end
local ts = require "gopher._utils.ts"
local r = require "gopher._utils.runner"
local c = require "gopher.config"
local u = require "gopher._utils"
local log = require "gopher._utils.log"
local struct_tags = {}
---@dochide
---@class gopher.StructTagInput
---@field input string[] User provided tags
---@field range? gopher.StructTagRange (optional)
---@dochide
---@class gopher.StructTagRange
---@field start number
---@field end_ number
---@param fpath string
---@param bufnr integer
---@param range? gopher.StructTagRange
---@param user_args string[]
---@dochide
local function handle_tags(fpath, bufnr, range, user_args)
local st = ts.get_struct_under_cursor(bufnr)
-- stylua: ignore
local cmd_args = {
local cmd = {
c.commands.gomodifytags,
"-transform", c.gotag.transform,
"-format", "json",
"-file", fpath,
"-w"
"-w",
}
-- by struct name of line pos
if ns.name == nil then
local _, csrow, _, _ = unpack(vim.fn.getpos ".")
table.insert(cmd_args, "-line")
table.insert(cmd_args, csrow)
-- `-struct` and `-line` cannot be combined, setting them separately
if range or st.is_varstruct then
table.insert(cmd, "-line")
table.insert(cmd, string.format("%d,%d", (range or st).start, (range or st).end_))
else
table.insert(cmd_args, "-struct")
table.insert(cmd_args, ns.name)
table.insert(cmd, "-struct")
table.insert(cmd, st.name)
end
-- set user args for cmd
local arg = { ... }
for _, v in ipairs(arg) do
table.insert(cmd_args, v)
for _, v in ipairs(user_args) do
table.insert(cmd, v)
end
-- set default tag for "clear tags"
if #arg == 1 and arg[1] ~= "-clear-tags" then
table.insert(cmd_args, "json")
local rs = r.sync(cmd)
if rs.code ~= 0 then
log.error("tags: failed to set tags " .. rs.stderr)
error("failed to set tags " .. rs.stderr)
end
-- get result of "gomodifytags" works
local res_data
Job
:new({
command = "gomodifytags",
args = cmd_args,
on_exit = function(data, retval)
if retval ~= 0 then
print("command exited with code " .. retval)
return
local res = vim.json.decode(rs.stdout)
if res["errors"] then
log.error("tags: got an error " .. vim.inspect(res))
error("failed to set tags " .. vim.inspect(res["errors"]))
end
res_data = data:result()
end,
})
:sync()
-- decode goted value
local tagged = vim.json.decode(table.concat(res_data))
if
tagged.errors ~= nil
or tagged.lines == nil
or tagged["start"] == nil
or tagged["start"] == 0
then
print("failed to set tags " .. vim.inspect(tagged))
for i, v in ipairs(res["lines"]) do
res["lines"][i] = u.trimend(v)
end
for i, v in ipairs(tagged.lines) do
tagged.lines[i] = utils.rtrim(v)
end
-- write goted tags
vim.api.nvim_buf_set_lines(
0,
tagged.start - 1,
tagged.start - 1 + #tagged.lines,
false,
tagged.lines
bufnr,
res["start"] - 1,
res["start"] - 1 + #res["lines"],
true,
res["lines"]
)
vim.cmd "write"
end
---add tags to struct under cursor
---@param ... unknown
function M.add(...)
local arg = { ... }
if #arg == nil or arg == "" then
arg = { "json" }
---@dochide
---@param option string
local function option_to_tag(option)
return option:match "^(.-)="
end
local cmd_args = { "-add-tags" }
for _, v in ipairs(arg) do
table.insert(cmd_args, v)
---@dochide
---@param args string[]
local function unwrap_if_needed(args)
local out = {}
for _, v in pairs(args) do
for _, p in pairs(vim.split(v, ",")) do
table.insert(out, p)
end
end
return out
end
modify(unpack(cmd_args))
---@dochide
---@class gopher.StructTagsArgs
---@field tags string
---@field options string
---@dochide
---@param args string[]
---@return gopher.StructTagsArgs
function struct_tags.parse_args(args)
args = unwrap_if_needed(args)
local tags, options = {}, {}
for _, v in pairs(args) do
if string.find(v, "=") then
table.insert(options, v)
table.insert(tags, option_to_tag(v))
else
table.insert(tags, v)
end
end
---remove tags to struct under cursor
---@param ... unknown
function M.remove(...)
local arg = { ... }
if #arg == nil or arg == "" then
arg = { "json" }
return {
tags = table.concat(u.list_unique(tags), ","),
options = table.concat(u.list_unique(options), ","),
}
end
local cmd_args = { "-remove-tags" }
for _, v in ipairs(arg) do
table.insert(cmd_args, v)
-- Adds tags to a struct under the cursor. See `:h gopher.nvim-struct-tags`.
---@param opts gopher.StructTagInput
---@dochide
function struct_tags.add(opts)
log.debug("adding tags", opts)
local fpath = vim.fn.expand "%"
local bufnr = vim.api.nvim_get_current_buf()
local user_args = struct_tags.parse_args(opts.input)
handle_tags(fpath, bufnr, opts.range, {
"-add-tags",
(user_args.tags ~= "") and user_args.tags or c.gotag.default_tag,
(user_args.options ~= "" or c.gotag.option) and "-add-options" or nil,
(user_args.options ~= "") and user_args.options or c.gotag.option,
})
end
modify(unpack(cmd_args))
-- Removes tags from a struct under the cursor. See `:h gopher.nvim-struct-tags`.
---@dochide
---@param opts gopher.StructTagInput
function struct_tags.remove(opts)
log.debug("removing tags", opts)
local fpath = vim.fn.expand "%"
local bufnr = vim.api.nvim_get_current_buf()
local user_args = struct_tags.parse_args(opts.input)
handle_tags(fpath, bufnr, opts.range, {
"-remove-tags",
(user_args.tags ~= "") and user_args.tags or c.gotag.default_tag,
(user_args.options ~= "" or c.gotag.option ~= nil) and "-remove-options" or nil,
(user_args.options ~= "") and user_args.options or c.gotag.option,
})
end
return M
-- Removes all tags from a struct under the cursor.
-- See `:h gopher.nvim-struct-tags`.
---@dochide
function struct_tags.clear()
local fpath = vim.fn.expand "%"
local bufnr = vim.api.nvim_get_current_buf()
handle_tags(fpath, bufnr, nil, { "-clear-tags" })
end
return struct_tags

View file

@ -1,42 +1,5 @@
[vim]
any = true
[describe]
[MiniTest]
any = true
[[describe.args]]
type = "string"
[[describe.args]]
type = "function"
[it]
any = true
[[it.args]]
type = "string"
[[it.args]]
type = "function"
[before_each]
any = true
[[before_each.args]]
type = "function"
[[after_each.args]]
type = "function"
[assert]
any = true
[assert.is_not]
any = true
[[assert.equals.args]]
type = "any"
[[assert.equals.args]]
type = "any"
[[assert.equals.args]]
type = "any"
required = false
[[assert.same.args]]
type = "any"
[[assert.same.args]]
type = "any"

10
pkg.json Normal file
View file

@ -0,0 +1,10 @@
{
"name": "gopher.nvim",
"engines": {
"nvim": "^0.10.0"
},
"repository": {
"type": "git",
"url": "https://github.com/olexsmir/gopher.nvim"
}
}

107
plugin/gopher.lua Normal file
View file

@ -0,0 +1,107 @@
---@toc_entry Commands
---@tag gopher.nvim-commands
---@text
--- If don't want to automatically register plugins' commands,
--- you can set `vim.g.gopher_register_commands` to `false`, before loading the plugin.
if vim.g.gopher_register_commands == false then
return
end
---@param name string
---@param fn fun(args: table)
---@param nargs? number|"*"|"?"
---@param range? boolean
---@private
local function cmd(name, fn, nargs, range)
vim.api.nvim_create_user_command(name, fn, {
nargs = nargs or 0,
range = range or false,
})
end
cmd("GopherLog", function()
vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
end)
cmd("GoIfErr", function()
require("gopher").iferr()
end)
cmd("GoCmt", function()
require("gopher").comment()
end)
cmd("GoImpl", function(args)
require("gopher").impl(unpack(args.fargs))
end, "*")
-- :GoInstall
cmd("GoInstallDeps", function()
require("gopher").install_deps()
end)
cmd("GoInstallDepsSync", function()
require("gopher").install_deps { sync = true }
end)
-- :GoTag
cmd("GoTagAdd", function(opts)
require("gopher").tags.add {
input = opts.fargs,
range = (opts.count ~= -1) and {
start = opts.line1,
end_ = opts.line2,
} or nil,
}
end, "*", true)
cmd("GoTagRm", function(opts)
require("gopher").tags.rm {
input = opts.fargs,
range = (opts.count ~= -1) and {
start = opts.line1,
end_ = opts.line2,
} or nil,
}
end, "*", true)
cmd("GoTagClear", function()
require("gopher").tags.clear()
end)
-- :GoJson
cmd("GoJson", function(opts)
local inp = ((opts.args ~= "" and opts.args) or nil)
require("gopher.json2go").json2go(inp)
end, "*")
-- :GoTest
cmd("GoTestAdd", function()
require("gopher").test.add()
end)
cmd("GoTestsAll", function()
require("gopher").test.all()
end)
cmd("GoTestsExp", function()
require("gopher").test.exported()
end)
-- :Go
cmd("GoMod", function(opts)
require("gopher").mod(opts.fargs)
end, "*")
cmd("GoGet", function(opts)
require("gopher").get(opts.fargs)
end, "*")
cmd("GoWork", function(opts)
require("gopher").get(opts.fargs)
end, "*")
cmd("GoGenerate", function(opts)
require("gopher").generate(opts.fargs or { "" })
end, "?")

View file

@ -1,6 +0,0 @@
command! -nargs=* GoTagAdd :lua require"gopher".tags_add(<f-args>)
command! -nargs=* GoTagRm :lua require"gopher".tags_rm(<f-args>)
command! -nargs=* GoMod :lua require"gopher".mod(<f-args>)
command! -nargs=* GoGet :lua require"gopher".get(<f-args>)
command! -nargs=* GoImpl :lua require"gopher".impl(<f-args>)
command! GoInstallDeps :lua require"gopher".install_deps()

38
scripts/docgen.lua Normal file
View file

@ -0,0 +1,38 @@
---@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",
"plugin/gopher.lua",
"lua/gopher/struct_tags.lua",
"lua/gopher/json2go.lua",
"lua/gopher/impl.lua",
"lua/gopher/gotests.lua",
"lua/gopher/iferr.lua",
"lua/gopher/comment.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
hooks.sections["@dochide"] = function(s)
s.parent:clear_lines()
end
MiniDoc.generate(files, "doc/gopher.nvim.txt", { hooks = hooks })

72
scripts/minimal_init.lua Normal file
View file

@ -0,0 +1,72 @@
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.uv.fs_stat(package_root .. name) then
print("Installing " .. plugin)
vim
.system({
"git",
"clone",
"--depth=1",
"https://github.com/" .. plugin .. ".git",
package_root .. "/" .. name,
})
:wait()
end
end
install_plug "nvim-treesitter/nvim-treesitter"
install_plug "echasnovski/mini.doc" -- used for docs generation
install_plug "folke/tokyonight.nvim" -- theme for generating demos
install_plug "echasnovski/mini.test"
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"
vim.opt.runtimepath:append(root())
vim.opt.packpath:append(root ".tests/site")
vim.o.swapfile = false
vim.o.writebackup = false
vim.notify = vim.print
-- install go treesitter parse
require("nvim-treesitter.install").ensure_installed_sync "go"
require("gopher").setup {
log_level = vim.log.levels.OFF,
timeout = 4000,
}
-- setup mini.test only when running headless nvim
if #vim.api.nvim_list_uis() == 0 then
require("mini.test").setup {
collect = {
find_files = function()
return vim.fn.globpath("spec", "**/*_test.lua", true, true)
end,
},
}
end
-- set colorscheme only when running ui
if #vim.api.nvim_list_uis() == 1 then
vim.cmd.colorscheme "tokyonight-night"
vim.o.cursorline = true
vim.o.number = true
end
-- needed for tests, i dont know the reason why, but on start
-- vim is not able to use treesitter for go by default
vim.api.nvim_create_autocmd("FileType", {
pattern = "go",
callback = function(args)
vim.treesitter.start(args.buf, "go")
end,
})

View file

@ -1 +1,2 @@
std="nvim+lua51"
std = "nvim+lua52"
exclude = [".tests/*"]

0
spec/fixtures/comment/empty_input.go vendored Normal file
View file

2
spec/fixtures/comment/empty_output.go vendored Normal file
View file

@ -0,0 +1,2 @@
//

5
spec/fixtures/comment/func_input.go vendored Normal file
View file

@ -0,0 +1,5 @@
package main
func Test(a int) bool {
return false
}

6
spec/fixtures/comment/func_output.go vendored Normal file
View file

@ -0,0 +1,6 @@
package main
// Test
func Test(a int) bool {
return false
}

View file

@ -0,0 +1,3 @@
package main
type Testinger interface{}

View file

@ -0,0 +1,6 @@
package main
type Testinger interface {
Get(id string) int
Set(id string, val int)
}

View file

@ -0,0 +1,7 @@
package main
type Testinger interface {
Get(id string) int
// Set
Set(id string, val int)
}

View file

@ -0,0 +1,5 @@
package main
type Testinger interface {
Method(input string) error
}

View file

@ -0,0 +1,6 @@
package main
type Testinger interface {
// Method
Method(input string) error
}

View file

@ -0,0 +1,4 @@
package main
// Testinger
type Testinger interface{}

View file

@ -0,0 +1,18 @@
package main
type (
TestOne struct {
Asdf string
ID int
}
TestTwo struct {
Fesa int
A bool
}
TestThree struct {
Asufj int
Fs string
}
)

View file

@ -0,0 +1,19 @@
package main
type (
TestOne struct {
Asdf string
ID int
}
TestTwo struct {
// Fesa
Fesa int
A bool
}
TestThree struct {
Asufj int
Fs string
}
)

7
spec/fixtures/comment/method_input.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
type Method struct{}
func (Method) Run() error {
return nil
}

View file

@ -0,0 +1,8 @@
package main
type Method struct{}
// Run
func (Method) Run() error {
return nil
}

View file

@ -0,0 +1 @@
package main

View file

@ -0,0 +1,2 @@
// Package main provides
package main

View file

@ -0,0 +1,7 @@
package main
type CommentStruct struct {
Name string
Address string
Aliases []string
}

View file

@ -0,0 +1,8 @@
package main
type CommentStruct struct {
Name string
// Address
Address string
Aliases []string
}

3
spec/fixtures/comment/struct_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type CommentStruct struct{}

View file

@ -0,0 +1,4 @@
package main
// CommentStruct
type CommentStruct struct{}

5
spec/fixtures/comment/svar_input.go vendored Normal file
View file

@ -0,0 +1,5 @@
package main
func varTest() {
s := "something"
}

6
spec/fixtures/comment/svar_output.go vendored Normal file
View file

@ -0,0 +1,6 @@
package main
func varTest() {
// s
s := "something"
}

5
spec/fixtures/comment/var_input.go vendored Normal file
View file

@ -0,0 +1,5 @@
package main
func test() {
var imAVar string
}

6
spec/fixtures/comment/var_output.go vendored Normal file
View file

@ -0,0 +1,6 @@
package main
func test() {
// imAVar
var imAVar string
}

View file

@ -0,0 +1,8 @@
package main
func main() {
var s struct {
API string
Key string
}
}

View file

@ -0,0 +1,9 @@
package main
func main() {
var s struct {
API string
// Key
Key string
}
}

9
spec/fixtures/iferr/iferr_input.go vendored Normal file
View file

@ -0,0 +1,9 @@
package main
func test() error {
return nil
}
func main() {
err := test()
}

12
spec/fixtures/iferr/iferr_output.go vendored Normal file
View file

@ -0,0 +1,12 @@
package main
func test() error {
return nil
}
func main() {
err := test()
if err != nil {
return
}
}

7
spec/fixtures/iferr/message_input.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
func getErr() error { return nil }
func test() error {
err := getErr()
}

10
spec/fixtures/iferr/message_output.go vendored Normal file
View file

@ -0,0 +1,10 @@
package main
func getErr() error { return nil }
func test() error {
err := getErr()
if err != nil {
return fmt.Errorf("failed to %w", err)
}
}

3
spec/fixtures/impl/closer_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type CloserTest struct{}

7
spec/fixtures/impl/closer_output.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
type CloserTest2 struct{}
func (closertest *CloserTest2) Close() error {
panic("not implemented") // TODO: Implement
}

3
spec/fixtures/impl/reader_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type Read struct{}

7
spec/fixtures/impl/reader_output.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
func (r *Read2) Read(p []byte) (n int, err error) {
panic("not implemented") // TODO: Implement
}
type Read2 struct{}

3
spec/fixtures/impl/writer_input.go vendored Normal file
View file

@ -0,0 +1,3 @@
package main
type WriterTest struct{}

7
spec/fixtures/impl/writer_output.go vendored Normal file
View file

@ -0,0 +1,7 @@
package main
type WriterTest2 struct{}
func (w *WriterTest2) Write(p []byte) (n int, err error) {
panic("not implemented") // TODO: Implement
}

View file

@ -0,0 +1,2 @@
package main

View file

@ -0,0 +1,5 @@
package main
type AutoGenerated struct {
Json bool `json:"json"`
}

2
spec/fixtures/json2go/manual_input.go vendored Normal file
View file

@ -0,0 +1,2 @@
package main

View file

@ -0,0 +1,7 @@
package main
type AutoGenerated struct {
User struct {
Name string `json:"name"`
} `json:"user"`
}

11
spec/fixtures/tags/add_many_input.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int
Name string
Num int64
Another struct {
First int
Second string
}
}

11
spec/fixtures/tags/add_many_output.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int `test4:"id" test5:"id" test1:"id" test2:"id"`
Name string `test4:"name" test5:"name" test1:"name" test2:"name"`
Num int64 `test4:"num" test5:"num" test1:"num" test2:"num"`
Another struct {
First int `test4:"first" test5:"first" test1:"first" test2:"first"`
Second string `test4:"second" test5:"second" test1:"second" test2:"second"`
} `test4:"another" test5:"another" test1:"another" test2:"another"`
}

14
spec/fixtures/tags/add_range_input.go vendored Normal file
View file

@ -0,0 +1,14 @@
package main
type Test struct {
ID int
Name string
Num int64
Cost int
Thingy []string
Testing int
Another struct {
First int
Second string
}
}

14
spec/fixtures/tags/add_range_output.go vendored Normal file
View file

@ -0,0 +1,14 @@
package main
type Test struct {
ID int
Name string `gopher:"name"`
Num int64 `gopher:"num"`
Cost int `gopher:"cost"`
Thingy []string
Testing int
Another struct {
First int
Second string
}
}

11
spec/fixtures/tags/clear_input.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int `json:"id" yaml:"id" xml:"id" db:"id"`
Name string `json:"name" yaml:"name" xml:"name" db:"name"`
Num int64 `json:"num" yaml:"num" xml:"num" db:"num"`
Another struct {
First int `json:"first" yaml:"first" xml:"first" db:"first"`
Second string `json:"second" yaml:"second" xml:"second" db:"second"`
} `json:"another" yaml:"another" xml:"another" db:"another"`
}

11
spec/fixtures/tags/clear_output.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int
Name string
Num int64
Another struct {
First int
Second string
}
}

18
spec/fixtures/tags/many_input.go vendored Normal file
View file

@ -0,0 +1,18 @@
package main
type (
TestOne struct {
Asdf string
ID int
}
TestTwo struct {
Fesa int
A bool
}
TestThree struct {
Asufj int
Fs string
}
)

18
spec/fixtures/tags/many_output.go vendored Normal file
View file

@ -0,0 +1,18 @@
package main
type (
TestOne struct {
Asdf string
ID int
}
TestTwo struct {
Fesa int `testing:"fesa"`
A bool `testing:"a"`
}
TestThree struct {
Asufj int
Fs string
}
)

View file

@ -0,0 +1,8 @@
package main
type Test struct {
ID int
Another struct {
Second string
}
}

View file

@ -0,0 +1,8 @@
package main
type Test struct {
ID int `xml:"id,otheroption"`
Another struct {
Second string `xml:"second,otheroption"`
} `xml:"another,otheroption"`
}

View file

@ -0,0 +1,14 @@
package main
type Test struct {
ID int `asdf:"id"`
Name string `asdf:"name"`
Num int64 `asdf:"num"`
Cost int `asdf:"cost"`
Thingy []string `asdf:"thingy"`
Testing int `asdf:"testing"`
Another struct {
First int `asdf:"first"`
Second string `asdf:"second"`
} `asdf:"another"`
}

View file

@ -0,0 +1,14 @@
package main
type Test struct {
ID int `asdf:"id"`
Name string `asdf:"name"`
Num int64
Cost int
Thingy []string
Testing int `asdf:"testing"`
Another struct {
First int `asdf:"first"`
Second string `asdf:"second"`
} `asdf:"another"`
}

View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int `json:"id,omitempty" xml:"id,someoption"`
Name string `json:"name,omitempty" xml:"name,someoption"`
Num int64 `json:"num,omitempty" xml:"num,someoption"`
Another struct {
First int `json:"first,omitempty" xml:"first,someoption"`
Second string `json:"second,omitempty" xml:"second,someoption"`
} `json:"another,omitempty" xml:"another,someoption"`
}

View file

@ -0,0 +1,11 @@
package main
type Test struct {
ID int `xml:"id,someoption"`
Name string `xml:"name,someoption"`
Num int64 `xml:"num,someoption"`
Another struct {
First int `xml:"first,someoption"`
Second string `xml:"second,someoption"`
} `xml:"another,someoption"`
}

11
spec/fixtures/tags/svar_input.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
func main() {
s := struct {
API string
Key string
}{
API: "api.com",
Key: "key",
}
}

11
spec/fixtures/tags/svar_output.go vendored Normal file
View file

@ -0,0 +1,11 @@
package main
func main() {
s := struct {
API string `xml:"api"`
Key string `xml:"key"`
}{
API: "api.com",
Key: "key",
}
}

8
spec/fixtures/tags/var_input.go vendored Normal file
View file

@ -0,0 +1,8 @@
package main
func main() {
var a struct {
TestField1 string
TestField2 string
}
}

8
spec/fixtures/tags/var_output.go vendored Normal file
View file

@ -0,0 +1,8 @@
package main
func main() {
var a struct {
TestField1 string `yaml:"test_field_1"`
TestField2 string `yaml:"test_field_2"`
}
}

View file

@ -0,0 +1,8 @@
package main
type Test struct {
ID int
Another struct {
Second string
}
}

Some files were not shown because too many files have changed in this diff Show more