[mirror] Make your go dev experience better github.com/olexsmir/gopher.nvim
neovim golang

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

authored by olexsmir.xyz and committed by GitHub fbf6441f 65fa1486

Changed files
+213 -31
doc
lua
plugin
spec
+7 -7
doc/gopher.nvim.txt
··· 20 20 21 21 ------------------------------------------------------------------------------ 22 22 *gopher.nvim-setup* 23 - `gopher.setup` 23 + `gopher.setup`({user_config}) 24 24 Setup function. This method simply merges default configs with opts table. 25 25 You can read more about configuration at |gopher.nvim-config| 26 26 Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| 27 27 28 28 Usage ~ 29 29 `require("gopher").setup {}` (replace `{}` with your `config` table) 30 + Parameters ~ 31 + {user_config} gopher.Config 30 32 31 33 ------------------------------------------------------------------------------ 32 34 *gopher.nvim-install-deps* ··· 50 52 local default_config = { 51 53 --minidoc_replace_end 52 54 55 + -- log level, you might consider using DEBUG or TRACE for degugging the plugin 56 + ---@type number 57 + log_level = vim.log.levels.INFO, 58 + 53 59 -- user specified paths to binaries 54 60 ---@class gopher.ConfigCommand 55 61 commands = { ··· 80 86 < 81 87 Class ~ 82 88 {gopher.Config} 83 - 84 - ------------------------------------------------------------------------------ 85 - *config.setup()* 86 - `config.setup`({user_config}) 87 - Parameters ~ 88 - {user_config} `(optional)` gopher.Config 89 89 90 90 91 91 ==============================================================================
+6 -13
lua/gopher/_utils/init.lua
··· 1 + local c = require "gopher.config" 2 + local log = require "gopher._utils.log" 1 3 local utils = {} 2 4 3 - local TITLE = "gopher.nvim" 4 - 5 - ---@param t table 6 - ---@return boolean 7 - function utils.is_tbl_empty(t) 8 - if t == nil then 9 - return true 10 - end 11 - return next(t) == nil 12 - end 13 - 14 5 ---@param msg string 15 6 ---@param lvl number 16 7 function utils.deferred_notify(msg, lvl) 17 8 vim.defer_fn(function() 18 9 vim.notify(msg, lvl, { 19 - title = TITLE, 10 + title = c.___plugin_name, 20 11 }) 12 + log.debug(msg) 21 13 end, 0) 22 14 end 23 15 ··· 26 18 function utils.notify(msg, lvl) 27 19 lvl = lvl or vim.log.levels.INFO 28 20 vim.notify(msg, lvl, { 29 - title = TITLE, 21 + title = c.___plugin_name, 30 22 }) 23 + log.debug(msg) 31 24 end 32 25 33 26 -- safe require
+170
lua/gopher/_utils/log.lua
··· 1 + -- thanks https://github.com/tjdevries/vlog.nvim 2 + -- and https://github.com/williamboman/mason.nvim 3 + -- for the code i have stolen(or have inspected by idk) 4 + local c = require "gopher.config" 5 + 6 + ---@class Gopher.Logger 7 + ---@field get_outfile fun():string 8 + ---@field trace fun(...) 9 + ---@field fmt_trace fun(...) 10 + ---@field debug fun(...) 11 + ---@field fmt_debug fun(...) 12 + ---@field info fun(...) 13 + ---@field fmt_info fun(...) 14 + ---@field warn fun(...) 15 + ---@field fmt_warn fun(...) 16 + ---@field error fun(...) 17 + ---@field fmt_error fun(...) 18 + 19 + local config = { 20 + -- Name of the plugin. Prepended to log messages 21 + name = c.___plugin_name, 22 + 23 + -- Should print the output to neovim while running 24 + -- values: 'sync','async',false 25 + use_console = vim.env.GOPHER_VERBOSE_LOGS == "1", 26 + 27 + -- Should highlighting be used in console (using echohl) 28 + highlights = true, 29 + 30 + -- Should write to a file 31 + use_file = true, 32 + 33 + -- Level configuration 34 + modes = { 35 + { name = "trace", hl = "Comment", level = vim.log.levels.TRACE }, 36 + { name = "debug", hl = "Comment", level = vim.log.levels.DEBUG }, 37 + { name = "info", hl = "None", level = vim.log.levels.INFO }, 38 + { name = "warn", hl = "WarningMsg", level = vim.log.levels.WARN }, 39 + { name = "error", hl = "ErrorMsg", level = vim.log.levels.ERROR }, 40 + }, 41 + 42 + -- Can limit the number of decimals displayed for floats 43 + float_precision = 0.01, 44 + } 45 + 46 + ---@type Gopher.Logger 47 + ---@diagnostic disable-next-line: missing-fields 48 + local log = {} 49 + 50 + ---@return string 51 + function log.get_outfile() 52 + return table.concat { 53 + (vim.fn.has "nvim-0.8.0" == 1) and vim.fn.stdpath "log" or vim.fn.stdpath "cache", 54 + ("/%s.log"):format(config.name), 55 + } 56 + end 57 + 58 + -- selene: allow(incorrect_standard_library_use) 59 + local unpack = unpack or table.unpack 60 + 61 + do 62 + local round = function(x, increment) 63 + increment = increment or 1 64 + x = x / increment 65 + return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment 66 + end 67 + 68 + local tbl_has_tostring = function(tbl) 69 + local mt = getmetatable(tbl) 70 + return mt and mt.__tostring ~= nil 71 + end 72 + 73 + local make_string = function(...) 74 + local t = {} 75 + for i = 1, select("#", ...) do 76 + local x = select(i, ...) 77 + 78 + if type(x) == "number" and config.float_precision then 79 + x = tostring(round(x, config.float_precision)) 80 + elseif type(x) == "table" and not tbl_has_tostring(x) then 81 + x = vim.inspect(x) 82 + else 83 + x = tostring(x) 84 + end 85 + 86 + t[#t + 1] = x 87 + end 88 + return table.concat(t, " ") 89 + end 90 + 91 + local log_at_level = function(level_config, message_maker, ...) 92 + -- Return early if we're below the current_log_level 93 + -- 94 + -- the log level source get from config directly because otherwise it doesnt work 95 + if level_config.level < c.log_level then 96 + return 97 + end 98 + local nameupper = level_config.name:upper() 99 + 100 + local msg = message_maker(...) 101 + local info = debug.getinfo(2, "Sl") 102 + local lineinfo = info.short_src .. ":" .. info.currentline 103 + 104 + -- Output to console 105 + if config.use_console then 106 + local log_to_console = function() 107 + local console_string = 108 + string.format("[%-6s%s] %s: %s", nameupper, os.date "%H:%M:%S", lineinfo, msg) 109 + 110 + if config.highlights and level_config.hl then 111 + vim.cmd(string.format("echohl %s", level_config.hl)) 112 + end 113 + 114 + local split_console = vim.split(console_string, "\n") 115 + for _, v in ipairs(split_console) do 116 + local formatted_msg = string.format("[%s] %s", config.name, vim.fn.escape(v, [["\]])) 117 + 118 + ---@diagnostic disable-next-line: param-type-mismatch 119 + local ok = pcall(vim.cmd, string.format([[echom "%s"]], formatted_msg)) 120 + if not ok then 121 + vim.api.nvim_out_write(msg .. "\n") 122 + end 123 + end 124 + 125 + if config.highlights and level_config.hl then 126 + vim.cmd "echohl NONE" 127 + end 128 + end 129 + if config.use_console == "sync" and not vim.in_fast_event() then 130 + log_to_console() 131 + else 132 + vim.schedule(log_to_console) 133 + end 134 + end 135 + 136 + -- Output to log file 137 + if config.use_file then 138 + local fp = assert(io.open(log.get_outfile(), "a")) 139 + local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg) 140 + fp:write(str) 141 + fp:close() 142 + end 143 + end 144 + 145 + for _, x in ipairs(config.modes) do 146 + -- log.info("these", "are", "separated") 147 + log[x.name] = function(...) ---@diagnostic disable-line: assign-type-mismatch 148 + return log_at_level(x, make_string, ...) 149 + end 150 + 151 + -- log.fmt_info("These are %s strings", "formatted") 152 + log[("fmt_%s"):format(x.name)] = function(...) ---@diagnostic disable-line: assign-type-mismatch 153 + return log_at_level(x, function(...) 154 + local passed = { ... } 155 + local fmt = table.remove(passed, 1) 156 + local inspected = {} 157 + for _, v in ipairs(passed) do 158 + if type(v) == "table" and tbl_has_tostring(v) then 159 + table.insert(inspected, v) 160 + else 161 + table.insert(inspected, vim.inspect(v)) 162 + end 163 + end 164 + return string.format(fmt, unpack(inspected)) 165 + end, ...) 166 + end 167 + end 168 + end 169 + 170 + return log
+4
lua/gopher/comment.lua
··· 3 3 ---@usage Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line. 4 4 ---@text This module provides a way to generate comments for Go code. 5 5 6 + local log = require "gopher._utils.log" 7 + 6 8 local function generate(row, col) 7 9 local ts_utils = require "gopher._utils.ts" 8 10 local comment, ns = nil, nil ··· 37 39 return function() 38 40 local row, col = unpack(vim.api.nvim_win_get_cursor(0)) 39 41 local comment, ns = generate(row + 1, col + 1) 42 + 43 + log.debug("generated comment: " .. comment) 40 44 41 45 vim.api.nvim_win_set_cursor(0, { 42 46 ns.dim.s.r,
+13
lua/gopher/config.lua
··· 29 29 local default_config = { 30 30 --minidoc_replace_end 31 31 32 + -- log level, you might consider using DEBUG or TRACE for degugging the plugin 33 + ---@type number 34 + log_level = vim.log.levels.INFO, 35 + 32 36 -- user specified paths to binaries 33 37 ---@class gopher.ConfigCommand 34 38 commands = { ··· 62 66 ---@private 63 67 local _config = default_config 64 68 69 + -- I am kinda secret so don't tell anyone about me 70 + -- even dont use me 71 + -- 72 + -- if you don't belive me that i am secret see 73 + -- the line below it says @private 74 + ---@private 75 + _config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field 76 + 65 77 ---@param user_config? gopher.Config 78 + ---@private 66 79 function config.setup(user_config) 67 80 _config = vim.tbl_deep_extend("force", default_config, user_config or {}) 68 81 end
+3
lua/gopher/gotests.lua
··· 43 43 local ts_utils = require "gopher._utils.ts" 44 44 local r = require "gopher._utils.runner" 45 45 local u = require "gopher._utils" 46 + local log = require "gopher._utils.log" 46 47 local gotests = {} 47 48 48 49 ---@param args table ··· 64 65 65 66 table.insert(args, "-w") 66 67 table.insert(args, vim.fn.expand "%") 68 + 69 + log.debug("generating tests with args: ", args) 67 70 68 71 return r.sync(c.commands.gotests, { 69 72 args = args,
+2
lua/gopher/iferr.lua
··· 4 4 ---@usage execute `:GoIfErr` near any err variable to insert the check 5 5 6 6 local c = require "gopher.config" 7 + local log = require "gopher._utils.log" 7 8 local iferr = {} 8 9 9 10 -- That's Lua implementation: github.com/koron/iferr ··· 14 15 local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%") 15 16 if vim.v.shell_error ~= 0 then 16 17 error("iferr failed: " .. data) 18 + log.error("failed. output: " .. data) 17 19 end 18 20 19 21 vim.fn.append(pos, data)
+7 -1
lua/gopher/init.lua
··· 9 9 ---@tag gopher.nvim-table-of-contents 10 10 ---@toc 11 11 12 + local log = require "gopher._utils.log" 12 13 local tags = require "gopher.struct_tags" 13 14 local tests = require "gopher.gotests" 14 15 local gocmd = require("gopher._utils.runner.gocmd").run ··· 21 22 --- Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| 22 23 --- 23 24 ---@usage `require("gopher").setup {}` (replace `{}` with your `config` table) 24 - gopher.setup = require("gopher.config").setup 25 + ---@param user_config gopher.Config 26 + gopher.setup = function(user_config) 27 + log.debug "setting up config" 28 + require("gopher.config").setup(user_config) 29 + log.debug(vim.inspect(user_config)) 30 + end 25 31 26 32 ---@toc_entry Install dependencies 27 33 ---@tag gopher.nvim-install-deps
+1
plugin/gopher.vim
··· 11 11 command! GoCmt :lua require"gopher".comment() 12 12 command! GoIfErr :lua require"gopher".iferr() 13 13 command! GoInstallDeps :lua require"gopher".install_deps() 14 + command! GopherLog :lua vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
-10
spec/units/utils_spec.lua
··· 1 1 describe("gopher._utils", function() 2 2 local u = require "gopher._utils" 3 3 4 - describe(".is_tbl_empty()", function() 5 - it("it is empty", function() 6 - assert.are.same(true, u.is_tbl_empty {}) 7 - end) 8 - 9 - it("it is not empty", function() 10 - assert.are.same(false, u.is_tbl_empty { first = "1", second = 2 }) 11 - end) 12 - end) 13 - 14 4 describe(".sreq()", function() 15 5 it("can require existing module", function() 16 6 assert.are.same(require "gopher", u.sreq "gopher")