Compare commits

..

84 commits
v0.1.2 ... 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
141 changed files with 3724 additions and 1043 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'}"

397
README.md
View file

@ -1,141 +1,296 @@
# gopher.nvim # 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. Minimalistic plugin for Go development in Neovim written in Lua.
It's not an LSP tool, the main goal of this plugin is add go tooling support in Neovim. It's **NOT** an LSP tool, the 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 ```lua
use { -- NOTE: this plugin is already lazy-loaded and adds only about 1ms
-- of load time to your config
{
"olexsmir/gopher.nvim", "olexsmir/gopher.nvim",
requires = { -- dependencies ft = "go",
"nvim-lua/plenary.nvim", -- branch = "develop"
"nvim-treesitter/nvim-treesitter", -- (optional) updates the plugin's dependencies on each update
}, build = function()
} vim.cmd.GoInstallDeps()
``` end,
---@module "gopher"
Also, run `TSInstall go` if `go` parser if isn't installed yet. ---@type gopher.Config
opts = {},
## Config
By `.setup` function you can configure the plugin.
Note:
- Installer does not install the tool in user set path
```lua
require("gopher").setup {
commands = {
go = "go",
gomodifytags = "gomodifytags",
gotests = "~/go/bin/gotests", -- also you can set custom command path
impl = "impl",
},
} }
``` ```
## Features ## Features
1. Installation requires this go tool: <details>
<summary>
<b>Install plugin's go deps</b>
</summary>
```vim ```vim
:GoInstallDeps :GoInstallDeps
```
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>
<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
" add json tag
:GoTagAdd json
" add json tag with omitempty option
:GoTagAdd json=omitempty
" remove yaml tag
:GoTagRm yaml
```
```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
" 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
```
```lua
-- or you can use lua api
require("gopher").test.add()
require("gopher").test.exported()
require("gopher").test.all()
```
For named tests see `:h gopher.nvim-gotests-named`
</details>
<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>
<details>
<summary>
<b>Interface implementation via <a href="https://github.com/josharian/impl">impl<a></b>
</summary>
![Auto interface implementation demo](./vhs/impl.gif)
Syntax of the command:
```vim
:GoImpl [receiver] [interface]
" also you can put a cursor on the struct and run
:GoImpl [interface]
```
Usage examples:
```vim
:GoImpl r Read io.Reader
:GoImpl Write io.Writer
" or you can simply put a cursor on the struct and run
:GoImpl io.Reader
```
</details>
<details>
<summary>
<b>Generate boilerplate for doc comments</b>
</summary>
![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,
},
}
``` ```
It will install next tools: ## 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`.
- [gomodifytags](https://github.com/fatih/gomodifytags) If the issue persists, feel free to [open a new issue](https://github.com/olexsmir/gopher.nvim/issues/new).
- [impl](https://github.com/josharian/impl)
- [gotests](https://github.com/cweill/gotests)
2. Modify struct tags: ## Contributing
By default `json` tag will be added/removed, if not set:
```vim PRs are always welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)
:GoTagAdd json " For add json tag
:GoTagRm yaml " For remove yaml tag
```
3. Run `go mod` command:
```vim
:GoMod tidy " Runs `go mod tidy`
:GoMod init asdf " Runs `go mod init asdf`
```
4. Run `go get` command
Link can have a `http` or `https` prefix.
You can provide more than one package url:
```vim
:GoGet github.com/gorilla/mux
```
5. Interface implementation
Command syntax:
```vim
:GoImpl [receiver] [interface]
" Also you can put cursor on the struct and run:
:GoImpl [interface]
```
Example of usage:
```vim
" Example
:GoImpl r Read io.Reader
" or simply put your cursor in the struct and run:
:GoImpl io.Reader
```
6. Generate tests with [gotests](https://github.com/cweill/gotests)
Generate one test for spesific function/method:
```vim
:GoTestAdd
```
Generate all tests for all functions/methods in current file:
```vim
:GoTestsAll
```
Generate tests only for exported functions/methods in current file:
```vim
:GoTestsExp
```
7. Run `go generate` command;
```vim
" Run `go generate` in cwd path
:GoGenerate
" Run `go generate` for current file
:GoGenerate %
```
8. Generate doc comment
First set a cursor on **public** package/function/interface/struct and execure:
```vim
:GoCmt
```
## Thanks:
- [go.nvim](https://github.com/ray-x/go.nvim)

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,56 +1,72 @@
return { local c = require "gopher.config"
---@param t table local log = require "gopher._utils.log"
---@return boolean local utils = {}
empty = function(t)
if t == nil then ---@param msg string
return true ---@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
---@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
end
return res
end
return next(t) == nil ---@param s string
end, ---@return string
function utils.trimend(s)
local r, _ = string.gsub(s, "%s+$", "")
return r
end
---@param s string -- Since indentation can be spaces or tabs, that's my hack around it
---@return string ---@param line string
rtrim = function(s) ---@param indent integer
local n = #s ---@return string
while n > 0 and s:find("^%s", n) do function utils.indent(line, indent)
n = n - 1 local char = string.sub(line, 1, 1)
if char ~= " " and char ~= "\t" then
char = " "
end
return string.rep(char, indent)
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
end
return tbl
end
return s:sub(1, n) return utils
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
end
return false
end,
---@param msg string
---@param lvl string|integer
notify = function(msg, lvl)
local l
if lvl == "error" or lvl == 4 then
l = vim.log.levels.ERROR
elseif lvl == "info" or lvl == 2 then
l = vim.log.levels.INFO
elseif lvl == "debug" or lvl == 1 then
l = vim.log.levels.DEBUG
end
vim.defer_fn(function()
vim.notify(msg, l)
end, 0)
end,
}

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,87 +0,0 @@
local nodes = require "gopher._utils.ts.nodes"
local u = require "gopher._utils"
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]],
package = [[(package_clause (package_identifier)@package.name)@package.clause]],
interface = [[((type_declaration (type_spec name:(type_identifier) @interface.name type:(interface_type)))@interface.declaration)]],
method_name = [[((method_declaration receiver: (parameter_list)@method.receiver name: (field_identifier)@method.name body:(block))@method.declaration)]],
func = [[((function_declaration name: (identifier)@function.name) @function.declaration)]],
},
}
---@return table
local function get_name_defaults()
return {
["func"] = "function",
["if"] = "if",
["else"] = "else",
["for"] = "for",
}
end
---@param row string
---@param col string
---@param bufnr string|nil
---@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
u.notify("struct not found", "warn")
else
return ns[#ns]
end
end
---@param row string
---@param col string
---@param bufnr string|nil
---@return table|nil
function M.get_func_method_node_at_pos(row, col, bufnr)
local query = M.querys.func .. " " .. M.querys.method_name
local bufn = bufnr or vim.api.nvim_get_current_buf()
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
if ns == nil then
u.notify("function not found", "warn")
else
return ns[#ns]
end
end
---@param row string
---@param col string
---@param bufnr string|nil
---@return table|nil
function M.get_package_node_at_pos(row, col, bufnr)
-- stylua: ignore
if row > 10 then return end
local query = M.querys.package
local bufn = bufnr or vim.api.nvim_get_current_buf()
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
if ns == nil then
u.notify("package not found", "warn")
return nil
else
return ns[#ns]
end
end
---@param row string
---@param col string
---@param bufnr string|nil
---@return table|nil
function M.get_interface_node_at_pos(row, col, bufnr)
local query = M.querys.interface
local bufn = bufnr or vim.api.nvim_get_current_buf()
local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col)
if ns == nil then
u.notify("interface not found", "warn")
else
return ns[#ns]
end
end
return M

View file

@ -1,137 +0,0 @@
local ts_query = require "nvim-treesitter.query"
local parsers = require "nvim-treesitter.parsers"
local locals = require "nvim-treesitter.locals"
local u = require "gopher._utils"
local M = {}
local function intersects(row, col, sRow, sCol, eRow, eCol)
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
u.notify("Unable to find any nodes. Place your cursor on a go symbol and try again", "debug")
return nil
end
nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col))
if nodes == nil or #nodes == 0 then
u.notify("Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col), "debug")
return nil
end
return nodes
end
return M

View file

@ -1,50 +1,71 @@
local ts_utils = require "gopher._utils.ts" ---@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 function generate(row, col) local ts = require "gopher._utils.ts"
local comment, ns = nil, nil local log = require "gopher._utils.log"
local u = require "gopher._utils"
local comment = {}
ns = ts_utils.get_package_node_at_pos(row, col) --- NOTE: The order of functions executed inside this function is IMPORTANT.
if ns ~= nil and ns ~= {} then --- This function is extremely fragile; run tests after making any changes.
comment = "// Package " .. ns.name .. " provides " .. ns.name ---
return comment, ns ---@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 end
ns = ts_utils.get_struct_node_at_pos(row, col) local s_ok, s_res = pcall(ts.get_struct_under_cursor, bufnr)
if ns ~= nil and ns ~= {} then if s_ok then
comment = "// " .. ns.name .. " " .. ns.type .. " " return u.indent(line, s_res.indent) .. "// " .. s_res.name .. " "
return comment, ns
end end
ns = ts_utils.get_func_method_node_at_pos(row, col) local v_ok, v_res = pcall(ts.get_variable_under_cursor, bufnr)
if ns ~= nil and ns ~= {} then if v_ok then
comment = "// " .. ns.name .. " " .. ns.type .. " " return u.indent(line, v_res.indent) .. "// " .. v_res.name .. " "
return comment, ns
end end
ns = ts_utils.get_interface_node_at_pos(row, col) local f_ok, f_res = pcall(ts.get_func_under_cursor, bufnr)
if ns ~= nil and ns ~= {} then if f_ok then
comment = "// " .. ns.name .. " " .. ns.type .. " " return u.indent(line, f_res.indent) .. "// " .. f_res.name .. " "
return comment, ns
end end
return "// ", {} 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 end
return function() function comment.comment()
local row, col = unpack(vim.api.nvim_win_get_cursor(0)) local bufnr = vim.api.nvim_get_current_buf()
local comment, ns = generate(row + 1, col + 1) local lnum = vim.fn.getcurpos()[2]
local line = vim.fn.getline(lnum)
vim.api.nvim_win_set_cursor(0, { local cmt = generate(bufnr, line)
ns.dim.s.r, log.debug("generated comment:", {
ns.dim.s.c, comment = cmt,
line = line,
}) })
vim.fn.append(row - 1, comment) vim.fn.append(lnum - 1, cmt)
vim.fn.setpos(".", { bufnr, lnum, #cmt })
vim.api.nvim_win_set_cursor(0, { vim.cmd "startinsert!"
ns.dim.s.r,
#comment + 1,
})
vim.cmd [[startinsert!]]
end end
return comment

View file

@ -1,19 +1,142 @@
local M = { ---@type gopher.Config
config = { ---@dochide
---set custom commands for tools ---@diagnostic disable-next-line: missing-fields .setup() gets injected later
commands = { local config = {}
go = "go",
gomodifytags = "gomodifytags", ---@tag gopher.nvim-config.ConfigGoTagTransform
gotests = "gotests", ---@text Possible values for |gopher.Config|.gotag.transform:
impl = "impl", ---
}, ---@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
---Plugin setup function ---@type gopher.Config
---@param opts table user options ---@dochide
function M.setup(opts) local _config = default_config
M.config = vim.tbl_deep_extend("force", M.config, opts)
-- 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 end
return M 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,28 +0,0 @@
local Job = require "plenary.job"
local c = require("gopher.config").config.commands
local u = require "gopher._utils"
---run "go generate"
return function(...)
local args = { ... }
if #args == 1 and args[1] == "%" then
args[1] = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter
end
local cmd_args = vim.list_extend({ "generate" }, args)
Job
:new({
command = c.go,
args = cmd_args,
on_exit = function(_, retval)
if retval ~= 0 then
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
return
end
u.notify("go generate was success runned", "info")
end,
})
:start()
end

View file

@ -1,35 +0,0 @@
local Job = require "plenary.job"
local c = require("gopher.config").config.commands
local u = require "gopher._utils"
---run "go get"
return function(...)
local args = { ... }
if #args == 0 then
u.notify("please provide a package url to get", "error")
return
end
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 = c.go,
args = cmd_args,
on_exit = function(_, retval)
if retval ~= 0 then
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
return
end
u.notify("go get was success runned", "info")
end,
})
:start()
end

View file

@ -1,29 +0,0 @@
local Job = require "plenary.job"
local c = require("gopher.config").config.commands
local u = require "gopher._utils"
---run "go mod"
return function(...)
local args = { ... }
if #args == 0 then
u.notify("please provide any mod command", "error")
return
end
local cmd_args = vim.list_extend({ "mod" }, args)
Job
:new({
command = c.go,
args = cmd_args,
on_exit = function(_, retval)
if retval ~= 0 then
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
return
end
u.notify("go mod was success runned", "info")
end,
})
:start()
end

View file

@ -1,73 +1,76 @@
local Job = require "plenary.job" ---@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 ts_utils = require "gopher._utils.ts"
local c = require("gopher.config").config.commands local r = require "gopher._utils.runner"
local u = require "gopher._utils" local u = require "gopher._utils"
local M = {} local log = require "gopher._utils.log"
local gotests = {}
---@param cmd_args table
local function run(cmd_args)
Job
:new({
command = c.gotests,
args = cmd_args,
on_exit = function(_, retval)
if retval ~= 0 then
u.notify("command 'go " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
return
end
u.notify("unit test(s) generated", "info")
end,
})
:start()
end
---@param args table ---@param args table
---@dochide
local function add_test(args) local function add_test(args)
local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter 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, "-w")
table.insert(args, fpath) table.insert(args, vim.fn.expand "%")
run(args)
end
---generate unit test for one function log.debug("generating tests with args: ", args)
---@param parallel boolean
function M.func_test(parallel) local rs = r.sync { c.commands.gotests, unpack(args) }
local ns = ts_utils.get_func_method_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) if rs.code ~= 0 then
if ns == nil or ns.name == nil then error("gotests failed: " .. rs.stderr)
u.notify("cursor on func/method and execute the command again", "info")
return
end end
local cmd_args = { "-only", ns.name } u.notify "unit test(s) generated"
if parallel then
table.insert(cmd_args, "-parallel")
end
add_test(cmd_args)
end end
---generate unit tests for all functions in current file -- generate unit test for one function
---@param parallel boolean function gotests.func_test()
function M.all_tests(parallel) local bufnr = vim.api.nvim_get_current_buf()
local cmd_args = { "-all" } local func = ts_utils.get_func_under_cursor(bufnr)
if parallel then
table.insert(cmd_args, "-parallel")
end
add_test(cmd_args) add_test { "-only", func.name }
end end
---generate unit tests for all exported functions -- generate unit tests for all functions in current file
---@param parallel boolean function gotests.all_tests()
function M.all_exported_tests(parallel) add_test { "-all" }
local cmd_args = {}
if parallel then
table.insert(cmd_args, "-parallel")
end
table.insert(cmd_args, "-exported")
add_test(cmd_args)
end end
return M -- generate unit tests for all exported functions
function gotests.all_exported_tests()
add_test { "-exported" }
end
return gotests

View file

@ -1,40 +1,59 @@
local health = vim.health or require "health" local c = require("gopher.config").commands
local utils = require "gopher._utils" local health = {}
local c = require("gopher.config").config.commands
local M = { local deps = {
_required = { vim_version = "nvim-0.10",
plugins = { bin = {
{ lib = "plenary" }, {
{ lib = "nvim-treesitter" }, bin = c.go,
}, msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`, `:GoInstallDepsSync`",
binarys = {
{ bin = c.go, help = "required for GoMod, GoGet, GoGenerate command" },
{ bin = c.gomodifytags, help = "required for modify struct tags" },
{ bin = c.impl, help = "required for interface implementing" },
{ bin = c.gotests, help = "required for test(s) generation" },
}, },
{ bin = c.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() ---@param bin {bin:string, msg:string, optional:boolean}
health.report_start "Required plugins" local function check_binary(bin)
for _, plugin in ipairs(M._required.plugins) do if vim.fn.executable(bin.bin) == 1 then
if utils.lualib_is_found(plugin.lib) then vim.health.ok(bin.bin .. " is found oh PATH: `" .. vim.fn.exepath(bin.bin) .. "`")
health.report_ok(plugin.lib .. " installed.") else
else vim.health.error(bin.bin .. " not found on PATH, " .. bin.msg)
health.report_error(plugin.lib .. " not found. Gopher.nvim will not work without it!")
end
end
health.report_start "Required go tools"
for _, binary in ipairs(M._required.binarys) do
if utils.binary_is_found(binary.bin) then
health.report_ok(binary.bin .. " installed")
else
health.report_warn(binary.bin .. " is not installed but " .. binary.help)
end
end end
end end
return M ---@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.error("`" .. ts.parser .. "` parser not found")
end
end
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,75 +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 ts_utils = require "gopher._utils.ts"
local c = require("gopher.config").config.commands
local u = require "gopher._utils" local u = require "gopher._utils"
local impl = {}
---@return string function impl.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
u.notify("put cursor on a struct or specify a receiver", "info")
return ""
end
vim.api.nvim_win_set_cursor(0, {
ns.dim.e.r,
ns.dim.e.c,
})
return ns.name
end
return function(...)
local args = { ... } local args = { ... }
local iface, recv_name = "", "" local iface, recv = "", ""
local recv = get_struct() local bufnr = vim.api.nvim_get_current_buf()
if #args == 0 then if #args == 0 then
iface = vim.fn.input "impl: generating method stubs for interface: " u.notify("arguments not provided. usage: :GoImpl f *File io.Reader", vim.log.levels.ERROR)
vim.cmd "redeaw!" return
if iface == "" then
u.notify("usage: GoImpl f *File io.Reader", "info")
return
end
elseif #args == 1 then -- :GoImpl io.Reader elseif #args == 1 then -- :GoImpl io.Reader
recv = string.lower(recv) .. " *" .. recv local st = ts_utils.get_struct_under_cursor(bufnr)
vim.cmd "redraw!" iface = args[1]
iface = select(1, ...) recv = string.lower(st.name) .. " *" .. st.name
elseif #args == 2 then -- :GoImpl w io.Writer elseif #args == 2 then -- :GoImpl w io.Writer
recv_name = select(1, ...) local st = ts_utils.get_struct_under_cursor(bufnr)
recv = string.format("%s *%s", recv_name, recv) iface = args[2]
iface = select(#args, ...) recv = args[1] .. " *" .. st.name
elseif #args > 2 then elseif #args == 3 then -- :GoImpl r Struct io.Reader
iface = select(#args, ...) recv = args[1] .. " *" .. args[2]
recv = select(#args - 1, ...) iface = args[3]
recv_name = select(#args - 2, ...)
recv = string.format("%s %s", recv_name, recv)
end end
-- stylua: ignore assert(iface ~= "", "interface not provided")
local cmd_args = { assert(recv ~= "", "receiver not provided")
"-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), ---@diagnostic disable-line: missing-parameter
recv,
iface
}
local res_data local dir = vim.fn.fnameescape(vim.fn.expand "%:p:h")
Job local rs = r.sync { c.impl, "-dir", dir, recv, iface }
:new({ if rs.code ~= 0 then
command = c.impl, error("failed to implement interface: " .. rs.stderr)
args = cmd_args, end
on_exit = function(data, retval)
if retval ~= 0 then
u.notify("command 'impl " .. unpack(cmd_args) .. "' exited with code " .. retval, "error")
return
end
res_data = data:result()
end,
})
:sync()
local pos = vim.fn.getcurpos()[2] local pos = vim.fn.getcurpos()[2]
table.insert(res_data, 1, "") local output = u.remove_empty_lines(vim.split(rs.stdout, "\n"))
vim.fn.append(pos, res_data)
table.insert(output, 1, "")
vim.fn.append(pos, output)
end end
return impl

View file

@ -1,18 +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 tags = require "gopher.struct_tags"
local gotests = require "gopher.gotests" local tests = require "gopher.gotests"
local go = require "gopher.go"
local gopher = {} local gopher = {}
gopher.install_deps = require "gopher.installer" ---@toc_entry Setup
gopher.tags_add = tags.add ---@tag gopher.nvim-setup()
gopher.tags_rm = tags.remove ---@text Setup function. This method simply merges default config with opts table.
gopher.mod = require "gopher.gomod" --- You can read more about configuration at |gopher.nvim-config|
gopher.get = require "gopher.goget" --- Calling this function is optional, if you ok with default settings.
gopher.impl = require "gopher.impl" --- See |gopher.nvim.config|
gopher.generate = require "gopher.gogenerate" ---
gopher.comment = require "gopher.comment" ---@usage >lua
gopher.test_add = gotests.func_test --- require("gopher").setup {} -- use default config or replace {} with your own
gopher.test_exported = gotests.all_exported_tests --- <
gopher.tests_all = gotests.all_tests ---@param user_config gopher.Config See |gopher.nvim-config|
gopher.setup = require("gopher.config").setup 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 return gopher

View file

@ -1,34 +1,66 @@
local Job = require "plenary.job" local c = require "gopher.config"
local r = require "gopher._utils.runner"
local u = require "gopher._utils" local u = require "gopher._utils"
local log = require "gopher._utils.log"
local installer = {}
local urls = { local urls = {
gomodifytags = "github.com/fatih/gomodifytags", gomodifytags = "github.com/fatih/gomodifytags@latest",
impl = "github.com/josharian/impl", impl = "github.com/josharian/impl@latest",
gotests = "github.com/cweill/gotests/...", gotests = "github.com/cweill/gotests/...@develop",
iferr = "github.com/koron/iferr@latest",
json2go = "olexsmir.xyz/json2go/cmd/json2go@latest",
} }
---@param pkg string ---@param opt vim.SystemCompleted
local function install(pkg) ---@param url string
local url = urls[pkg] .. "@latest" local function handle_intall_exit(opt, url)
if opt.code ~= 0 then
vim.schedule(function()
u.notify("go install failed: " .. url)
end)
Job log.error("go install failed:", "url", url, "opt", vim.inspect(opt))
:new({ return
command = "go", end
args = { "install", url },
on_exit = function(_, retval)
if retval ~= 0 then
u.notify("command 'go install " .. url .. "' exited with code " .. retval, "error")
return
end
u.notify("install " .. url .. " finished", "info ") vim.schedule(function()
end, u.notify("go install-ed: " .. url)
}) end)
:start() 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 end
---Install required go deps ---Install required go deps
return function() ---@param opts? {sync:boolean}
for pkg, _ in pairs(urls) do function installer.install_deps(opts)
install(pkg) opts = opts or {}
for _, url in pairs(urls) do
if opts.sync then
install_sync(url)
else
install(url)
end
end 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,120 +1,200 @@
local Job = require "plenary.job" ---@toc_entry Modify struct tags
local ts_utils = require "gopher._utils.ts" ---@tag gopher.nvim-struct-tags
local u = require "gopher._utils" ---@text
local c = require("gopher.config").config.commands --- `struct_tags` is utilizing the `gomodifytags` tool to add or remove tags to
local M = {} --- 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 ts = require "gopher._utils.ts"
local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter local r = require "gopher._utils.runner"
local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) local c = require "gopher.config"
if ns == nil or ns == {} then local u = require "gopher._utils"
return local log = require "gopher._utils.log"
end 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 -- stylua: ignore
local cmd_args = { local cmd = {
c.commands.gomodifytags,
"-transform", c.gotag.transform,
"-format", "json", "-format", "json",
"-file", fpath, "-file", fpath,
"-w" "-w",
} }
-- by struct name of line pos -- `-struct` and `-line` cannot be combined, setting them separately
if ns.name == nil then if range or st.is_varstruct then
local _, csrow, _, _ = unpack(vim.fn.getpos ".") table.insert(cmd, "-line")
table.insert(cmd_args, "-line") table.insert(cmd, string.format("%d,%d", (range or st).start, (range or st).end_))
table.insert(cmd_args, csrow)
else else
table.insert(cmd_args, "-struct") table.insert(cmd, "-struct")
table.insert(cmd_args, ns.name) table.insert(cmd, st.name)
end end
-- set user args for cmd for _, v in ipairs(user_args) do
local arg = { ... } table.insert(cmd, v)
for _, v in ipairs(arg) do
table.insert(cmd_args, v)
end end
-- set default tag for "clear tags" local rs = r.sync(cmd)
if #arg == 1 and arg[1] ~= "-clear-tags" then if rs.code ~= 0 then
table.insert(cmd_args, "json") log.error("tags: failed to set tags " .. rs.stderr)
error("failed to set tags " .. rs.stderr)
end end
-- get result of "gomodifytags" works local res = vim.json.decode(rs.stdout)
local res_data if res["errors"] then
Job log.error("tags: got an error " .. vim.inspect(res))
:new({ error("failed to set tags " .. vim.inspect(res["errors"]))
command = c.gomodifytags,
args = cmd_args,
on_exit = function(data, retval)
if retval ~= 0 then
u.notify(
"command 'gomodifytags " .. unpack(cmd_args) .. "' exited with code " .. retval,
"error"
)
return
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
u.notify("failed to set tags " .. vim.inspect(tagged), "error")
end end
for i, v in ipairs(tagged.lines) do for i, v in ipairs(res["lines"]) do
tagged.lines[i] = u.rtrim(v) res["lines"][i] = u.trimend(v)
end end
-- write goted tags
vim.api.nvim_buf_set_lines( vim.api.nvim_buf_set_lines(
0, bufnr,
tagged.start - 1, res["start"] - 1,
tagged.start - 1 + #tagged.lines, res["start"] - 1 + #res["lines"],
false, true,
tagged.lines res["lines"]
) )
vim.cmd "write"
end end
---add tags to struct under cursor ---@dochide
---@param ... unknown ---@param option string
function M.add(...) local function option_to_tag(option)
local arg = { ... } return option:match "^(.-)="
if #arg == nil or arg == "" then
arg = { "json" }
end
local cmd_args = { "-add-tags" }
for _, v in ipairs(arg) do
table.insert(cmd_args, v)
end
modify(unpack(cmd_args))
end end
---remove tags to struct under cursor ---@dochide
---@param ... unknown ---@param args string[]
function M.remove(...) local function unwrap_if_needed(args)
local arg = { ... } local out = {}
if #arg == nil or arg == "" then for _, v in pairs(args) do
arg = { "json" } for _, p in pairs(vim.split(v, ",")) do
table.insert(out, p)
end
end end
return out
local cmd_args = { "-remove-tags" }
for _, v in ipairs(arg) do
table.insert(cmd_args, v)
end
modify(unpack(cmd_args))
end end
return M ---@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
return {
tags = table.concat(u.list_unique(tags), ","),
options = table.concat(u.list_unique(options), ","),
}
end
-- 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
-- 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
-- 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] [vim]
any = true any = true
[describe] [MiniTest]
any = true 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,11 +0,0 @@
command! -nargs=* GoTagAdd :lua require"gopher".tags_add(<f-args>)
command! -nargs=* GoTagRm :lua require"gopher".tags_rm(<f-args>)
command! -nargs=* GoTestAdd :lua require"gopher".test_add(<f-args>)
command! -nargs=* GoTestsAll :lua require"gopher".tests_all(<f-args>)
command! -nargs=* GoTestsExp :lua require"gopher".test_exported(<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! -nargs=* GoGenerate :lua require"gopher".generate(<f-args>)
command! GoCmt :lua require"gopher".comment()
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"`
}
}

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