From c9ac5a6fd8ef3bc8d5da15990228052fc831b4f5 Mon Sep 17 00:00:00 2001 From: mg979 Date: Sun, 13 Mar 2022 16:05:04 +0100 Subject: [PATCH] Placeholder support for snippet engines (#76) - Add support for placeholders in snippet insertion ! None: placeholders are automatically set when using a bundled snippet engine. - Add `enable_placeholders` option (see |neogen-configuration|) - Add `placeholders_text` option (see |neogen-configuration|) Co-authored-by: danymat --- doc/neogen.txt | 43 +++++++++++++++++++++++++++++++- lua/neogen/generator.lua | 42 +++++++++++++++++++++++++++---- lua/neogen/init.lua | 35 +++++++++++++++++++++++++- lua/neogen/mark.lua | 3 +-- lua/neogen/snippet.lua | 45 +++++++++++++++++++++++++++++----- lua/neogen/templates/godoc.lua | 4 +-- 6 files changed, 155 insertions(+), 17 deletions(-) diff --git a/doc/neogen.txt b/doc/neogen.txt index 9c5c580..28add25 100644 --- a/doc/neogen.txt +++ b/doc/neogen.txt @@ -101,6 +101,27 @@ Neogen provides those defaults, and you can change them to suit your needs -- Use a snippet engine to generate annotations. snippet_engine = nil, + + -- Enables placeholders when inserting annotation + enable_placeholders = true, + + -- Placeholders used during annotation expansion + placeholders_text = { + ["description"] = "[TODO:description]", + ["tparam"] = "[TODO:tparam]", + ["parameter"] = "[TODO:parameter]", + ["return"] = "[TODO:return]", + ["class"] = "[TODO:class]", + ["throw"] = "[TODO:throw]", + ["varargs"] = "[TODO:varargs]", + ["type"] = "[TODO:type]", + ["attribute"] = "[TODO:attribute]", + ["args"] = "[TODO:args]", + ["kwargs"] = "[TODO:kwargs]", + }, + + -- Placeholders highlights to use. If you don't want custom highlight, pass "None" + placeholders_hl = "DiagnosticHint", } < # Notes~ @@ -159,12 +180,23 @@ Feel free to submit a PR, I will be happy to help you ! We use semantic versioning ! (https://semver.org) Here is the current Neogen version: > - neogen.version = "2.5.0" + neogen.version = "2.6.0" < # Changelog~ Note: We will only document `major` and `minor` versions, not `patch` ones. +## 2.6.0~ + - Add support for placeholders in snippet insertion ! + - Add `enable_placeholders` option (see |neogen-configuration|) + - Add `placeholders_text` option (see |neogen-configuration|) + + Example placeholders: +> + --- [TODO:description] + ---@param param1 [TODO:parameter] [TODO:description] + function test(param1) end +< ## 2.5.0~ - Ruby: Add support for `tomdoc` (http://tomdoc.org) ## 2.4.0~ @@ -202,6 +234,15 @@ To use a snippet engine, pass the option into neogen setup: Some snippet engines come out of the box bundled with neogen: - `"luasnip"` (https://github.com/L3MON4D3/LuaSnip) +If you want to customize the placeholders, you can use `placeholders_text` option: +> + require('neogen').setup({ + placeholders_text = { + ['description'] = "[description]", + } + }) +< + # Add support for snippet engines~ To add support to a snippet engine, go to `lua/neogen/snippet.lua`. diff --git a/lua/neogen/generator.lua b/lua/neogen/generator.lua index 0dc9ca4..b762176 100644 --- a/lua/neogen/generator.lua +++ b/lua/neogen/generator.lua @@ -16,6 +16,27 @@ local default_locator = require("neogen.locators.default") local snippet = require("neogen.snippet") local JUMP_TEXT = "$1" +local function todo_text(type) + local i = require("neogen.types.template").item + local todo = conf.placeholders_text + return ({ + [i.Tparam] = todo["tparam"], + [i.Parameter] = todo["parameter"], + [i.Return] = todo["return"], + [i.ReturnTypeHint] = todo["return"], + [i.ReturnAnonym] = todo["return"], + [i.ClassName] = todo["class"], + [i.Throw] = todo["throw"], + [i.Vararg] = todo["varargs"], + [i.Type] = todo["type"], + [i.ClassAttribute] = todo["attribute"], + [i.HasParameter] = todo["parameter"], + [i.HasReturn] = todo["return"], + [i.ArbitraryArgs] = todo["args"], + [i.Kwargs] = todo["kwargs"], + })[type] or todo["description"] +end + local function get_parent_node(filetype, typ, language) local parser = vim.treesitter.get_parser(0, filetype) local tstree = parser:parse()[1] @@ -91,10 +112,19 @@ local function generate_content(parent, data, template, required_type) local generated_template = template[template.annotation_convention] local result = {} + local default_text = {} local prefix = prefix_generator(template, commentstring, col) - local function append_str(str) + local n = 0 + + local function append_str(str, inserted_type) table.insert(result, str == "" and str or prefix .. str) + local x -- placeholders in the same line after the first are 'descriptions' + for _ in string.gmatch(str, "%$1") do + n = n + 1 + default_text[n] = not x and todo_text(inserted_type) or todo_text() + x = true + end end for _, values in ipairs(generated_template) do @@ -121,7 +151,7 @@ local function generate_content(parent, data, template, required_type) elseif ins_type == "string" and type(data[inserted_type]) == "table" then -- Format the output with the corresponding data for _, s in ipairs(data[inserted_type]) do - append_str(formatted_str:format(s)) + append_str(formatted_str:format(s), inserted_type) if opts.after_each then append_str(opts.after_each:format(s)) end @@ -152,7 +182,7 @@ local function generate_content(parent, data, template, required_type) end end - return row, result + return row, result, default_text end return setmetatable({}, { @@ -181,7 +211,7 @@ return setmetatable({}, { local data = granulator(parent_node, language.data[typ]) -- Will try to generate the documentation from a template and the data found from the granulator - local row, template_content = generate_content(parent_node, data, template, typ) + local row, template_content, default_text = generate_content(parent_node, data, template, typ) local content = {} local marks_pos = {} @@ -205,7 +235,9 @@ return setmetatable({}, { end if input_after_comment then len = len + s - last_col - 1 - table.insert(marks_pos, { row + r - 1, len - 1 }) + local m = { row = row + r - 1, col = len - 1 } + table.insert(marks_pos, m) + m.text = default_text[#marks_pos] end last_col = e end diff --git a/lua/neogen/init.lua b/lua/neogen/init.lua index 84881df..8ed9c97 100644 --- a/lua/neogen/init.lua +++ b/lua/neogen/init.lua @@ -115,6 +115,27 @@ neogen.configuration = { -- Use a snippet engine to generate annotations. snippet_engine = nil, + + -- Enables placeholders when inserting annotation + enable_placeholders = true, + + -- Placeholders used during annotation expansion + placeholders_text = { + ["description"] = "[TODO:description]", + ["tparam"] = "[TODO:tparam]", + ["parameter"] = "[TODO:parameter]", + ["return"] = "[TODO:return]", + ["class"] = "[TODO:class]", + ["throw"] = "[TODO:throw]", + ["varargs"] = "[TODO:varargs]", + ["type"] = "[TODO:type]", + ["attribute"] = "[TODO:attribute]", + ["args"] = "[TODO:args]", + ["kwargs"] = "[TODO:kwargs]", + }, + + -- Placeholders highlights to use. If you don't want custom highlight, pass "None" + placeholders_hl = "DiagnosticHint", } --minidoc_afterlines_end @@ -216,6 +237,18 @@ end --- --- Note: We will only document `major` and `minor` versions, not `patch` ones. --- +--- ## 2.6.0~ +--- - Add support for placeholders in snippet insertion ! +--- None: placeholders are automatically set when using a bundled snippet engine. +--- - Add `enable_placeholders` option (see |neogen-configuration|) +--- - Add `placeholders_text` option (see |neogen-configuration|) +--- +--- Example placeholders: +--- > +--- --- [TODO:description] +--- ---@param param1 [TODO:parameter] [TODO:description] +--- function test(param1) end +--- < --- ## 2.5.0~ --- - Ruby: Add support for `tomdoc` (http://tomdoc.org) --- ## 2.4.0~ @@ -238,7 +271,7 @@ end --- with multiple annotation conventions. ---@tag neogen-changelog ---@toc_entry Changes in neogen plugin -neogen.version = "2.5.0" +neogen.version = "2.6.0" --minidoc_afterlines_end return neogen diff --git a/lua/neogen/mark.lua b/lua/neogen/mark.lua index b4ea8d8..82e6896 100644 --- a/lua/neogen/mark.lua +++ b/lua/neogen/mark.lua @@ -49,8 +49,7 @@ end ---@return number the id of the inserted mark ---@private mark.add_mark = function(self, pos) - local line, col = unpack(pos) - local id = api.nvim_buf_set_extmark(self.bufnr, ns, line, col, {}) + local id = api.nvim_buf_set_extmark(self.bufnr, ns, pos.row, pos.col, {}) table.insert(mark.ids, id) return id end diff --git a/lua/neogen/snippet.lua b/lua/neogen/snippet.lua index 6142565..492213c 100644 --- a/lua/neogen/snippet.lua +++ b/lua/neogen/snippet.lua @@ -1,4 +1,5 @@ local notify = require("neogen.utilities.helpers").notify +local conf = require("neogen.config").get() --- --- To use a snippet engine, pass the option into neogen setup: @@ -11,6 +12,15 @@ local notify = require("neogen.utilities.helpers").notify --- Some snippet engines come out of the box bundled with neogen: --- - `"luasnip"` (https://github.com/L3MON4D3/LuaSnip) --- +--- If you want to customize the placeholders, you can use `placeholders_text` option: +--- > +--- require('neogen').setup({ +--- placeholders_text = { +--- ['description'] = "[description]", +--- } +--- }) +--- < +--- --- # Add support for snippet engines~ --- --- To add support to a snippet engine, go to `lua/neogen/snippet.lua`. @@ -33,16 +43,17 @@ snippet.engines = {} ---@return table resulting snippet lines ---@private snippet.to_snippet = function(template, marks, pos) - local offset = {} + local offset, ph = {}, {} for i, m in ipairs(marks) do - local r, col = m[1] - pos[1] + 1, m[2] + local r, col = m.row - pos[1] + 1, m.col + ph[i] = (m.text and conf.enable_placeholders) and string.format("${%d:%s}", i, m.text) or "$" .. i if offset[r] then - offset[r] = offset[r] + tostring(i - 1):len() + 1 + offset[r] = offset[r] + ph[i - 1]:len() + 1 else offset[r] = 0 end local pre = template[r]:sub(1, col + offset[r]) - template[r] = pre .. "$" .. i .. template[r]:sub(col + 1 + offset[r]) + template[r] = pre .. ph[i] .. template[r]:sub(col + 1 + offset[r]) end return template end @@ -52,13 +63,35 @@ end ---@param pos table a tuple of row, col ---@private snippet.engines.luasnip = function(snip, pos) - local ok, luasnip = pcall(require, "luasnip") + local ok, ls = pcall(require, "luasnip") if not ok then notify("Luasnip not found, aborting...", vim.log.levels.ERROR) return end + + local types = require("luasnip.util.types") + + -- Append a new line to create the snippet vim.fn.append(pos[1], "") - luasnip.lsp_expand(table.concat(snip, "\n"), { pos = { pos[1], pos[2] } }) + + -- Convert the snippet to string + local _snip = table.concat(snip, "\n") + + ls.snip_expand( + ls.s("", ls.parser.parse_snippet(nil, _snip), { + + child_ext_opts = { + -- for highlighting the placeholders + [types.insertNode] = { + -- when outside placeholder, but in snippet + passive = { hl_group = conf.placeholders_hl }, + }, + }, + -- prevent mixing styles + merge_child_ext_opts = true, + }), + { pos = pos } + ) end return snippet diff --git a/lua/neogen/templates/godoc.lua b/lua/neogen/templates/godoc.lua index 8175a2e..4073d35 100644 --- a/lua/neogen/templates/godoc.lua +++ b/lua/neogen/templates/godoc.lua @@ -1,5 +1,5 @@ return { { nil, " $1", { no_results = true } }, - { "func_name", " %s $1", { type = { "func" }}}, - { "type_name", " %s $1", { type = { "type" }}}, + { "func_name", " %s $1", { type = { "func" } } }, + { "type_name", " %s $1", { type = { "type" } } }, }