From d5f447e5cc509d845df9fefde5b78dad9481f20b Mon Sep 17 00:00:00 2001 From: mg979 Date: Sat, 26 Feb 2022 12:48:11 +0100 Subject: [PATCH] Snippet support (#72) To know more about snippet integration, please visit `:h snippet-integration`, or https://github.com/danymat/neogen#snippet-support ! Co-authored-by: danymat --- README.md | 35 +++++++++++++++++++++++--- doc/neogen.txt | 43 ++++++++++++++++++++++++++++---- lua/neogen/generator.lua | 50 +++++++++++++++++++++++++++---------- lua/neogen/init.lua | 28 ++++++++++++++++----- lua/neogen/snippet.lua | 53 ++++++++++++++++++++++++++++++++++++++++ scripts/minidoc.lua | 2 +- 6 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 lua/neogen/snippet.lua diff --git a/README.md b/README.md index f8d5356..20da38c 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ use { end, requires = "nvim-treesitter/nvim-treesitter", -- Uncomment next line if you want to follow only stable versions - -- tag = "*" + -- tag = "*" } ``` @@ -93,11 +93,38 @@ local opts = { noremap = true, silent = true } vim.api.nvim_set_keymap("n", "nc", ":lua require('neogen').generate({ type = 'class' })", opts) ``` -### Cycle between annotations +### Snippet support -I added support passing cursor positionings in templates. That means you can now cycle your cursor between different parts of the annotation. +We added snippet support, and we provide defaults for some snippet engines. +And this is done via the `snippet_engine` option in neogen's setup: -If you want to map some keys to the cycling feature, you can do like so: +- `snippet_engine` option will use provided engine to place the annotations: + +Currently supported: `luasnip`. + +```lua +require('neogen').setup({ snippet_engine = "luasnip" }) +``` + +That's all ! You can now use your favorite snippet engine to control the annotation, like jumping between placeholders. + +Or, if you want to return the snippet as a string (to integrate with other snippet engines, for example), +you can do it by using the `return_snippet` option in the `generate` function: + +- `return_snippet` option will return the annotations as lsp snippets. + +```lua +local snippet, row, col = require('neogen').generate({ snippet_engine = "luasnip" }) +``` + +And then pass the snippet to the plugin's snippet expansion function. + +### Default cycling support + +_Note that this part is only useful if you don't use the snippets integration._ + +If you don't want to use a snippet engine with Neogen, you can leverage Neogen's native jumps between placeholders. +To map some keys to the cycling feature, you can do like so: ```lua local opts = { noremap = true, silent = true } diff --git a/doc/neogen.txt b/doc/neogen.txt index bcade8a..f6fa934 100644 --- a/doc/neogen.txt +++ b/doc/neogen.txt @@ -8,6 +8,7 @@ Table of contents: Generate annotations.....................................|neogen.generate()| Contributing................................................|neogen-develop| Changes in neogen plugin..................................|neogen-changelog| + Use popular snippet engines............................|snippet-integration| Configurations for the template table........|neogen-template-configuration| How to create/customize an annotation....................|neogen-annotation| @@ -97,18 +98,24 @@ Neogen provides those defaults, and you can change them to suit your needs -- Configuration for default languages languages = {}, + + -- Use a snippet engine to generate annotations. + snippet_engine = nil, } < # Notes~ - to configure a language, just add your configurations in the `languages` table. For example, for the `lua` lang: - > +> languages = { lua = { -- Configuration here } } - < - Default configurations for a languages can be found in `lua/neogen/configurations/.lua` +< + Default configurations for a languages can be found in `lua/neogen/configurations/.lua` + + - To know which snippet engines are supported, take a look at |snippet-integration|. + Example: `snippet_engine = "luasnip"` ------------------------------------------------------------------------------ @@ -121,9 +128,15 @@ For example, if you are inside a function, and called `generate({ type = "func" Neogen will go until the start of the function and start annotating for you. Parameters~ -{opts} `(table)` Options to change default behaviour of generation. - - {opts.type} `(string?, default: "func")` Which type we are trying to use for generating annotations. +{opts} `(table)` Optional configs to change default behaviour of generation. + - {opts.type} `(string, default: "func")` Which type we are trying to use for generating annotations. Currently supported: `func`, `class`, `type`, `file` + - {opts.return_snippet} `boolean` if true, will return 3 values from the function call. + This option is useful if you want to get the snippet to use with a unsupported snippet engine + Below are the returned values: + - 1: (type: `string[]`) the resulting lsp snippet + - 2: (type: `number`) the `row` to insert the annotations + - 3: (type: `number`) the `col` to insert the annotations ------------------------------------------------------------------------------ *neogen-develop* @@ -152,6 +165,10 @@ Here is the current Neogen version: Note: We will only document `major` and `minor` versions, not `patch` ones. +## 2.3.0~ + - Added bundled support with snippet engines ! + Check out |snippet-integration| for basic setup + For more information of possible options, check out |neogen.generate()| ## 2.2.0~ ### Python~ - Add support for `*args` and `**kwargs` @@ -167,6 +184,22 @@ Note: We will only document `major` and `minor` versions, not `patch` ones. with multiple annotation conventions. +============================================================================== +------------------------------------------------------------------------------ + *snippet-integration* + `snippet` + +To use a snippet engine, pass the option into neogen setup: +> + require('neogen').setup({ + snippet_engine = "luasnip", + ... + }) +< +Some snippet engines come out of the box bundled with neogen: +- `"luasnip"` (https://github.com/L3MON4D3/LuaSnip) + + ============================================================================== ------------------------------------------------------------------------------ *neogen-template-configuration* diff --git a/lua/neogen/generator.lua b/lua/neogen/generator.lua index 59f305d..5f04de1 100644 --- a/lua/neogen/generator.lua +++ b/lua/neogen/generator.lua @@ -13,6 +13,7 @@ local granulator = require("neogen.granulator") local mark = require("neogen.mark") local nodes = require("neogen.utilities.nodes") local default_locator = require("neogen.locators.default") +local snippet = require("neogen.snippet") local JUMP_TEXT = "$1" local function get_parent_node(filetype, typ, language) @@ -33,7 +34,7 @@ end ---@param n integer ---@return string local function prefix_generator(template, commentstring, n) - local prefix = (" "):rep(n) + local prefix = (vim.bo.expandtab and " " or "\t"):rep(n) -- Do not append the comment string if not wanted if template.use_default_comment ~= false then @@ -155,7 +156,7 @@ local function generate_content(parent, data, template, required_type) end return setmetatable({}, { - __call = function(_, filetype, typ) + __call = function(_, filetype, typ, return_snippet) if filetype == "" then notify("No filetype detected", vim.log.levels.WARN) return @@ -210,19 +211,42 @@ return setmetatable({}, { end end - -- Append content to row - vim.api.nvim_buf_set_lines(0, row, row, true, content) + if return_snippet then + -- User just wants the snippet, so we give him the snippet plus placement informations + local generated_snippet = snippet.to_snippet(content, marks_pos, { row, 0 }) + return generated_snippet, row + end - if #marks_pos > 0 then - -- Start session of marks - mark:start() - for _, pos in ipairs(marks_pos) do - mark:add_mark(pos) + local snippet_engine = conf.snippet_engine + if snippet_engine then + -- User want to use a snippet engine instead of native handling + local engines = snippet.engines + if not vim.tbl_contains(vim.tbl_keys(engines), snippet_engine) then + notify(string.format("Snippet engine '%s' not supported", snippet_engine), vim.log.levels.ERROR) + return + end + + -- Converts the content to a lsp compatible snippet + local generated_snippet = snippet.to_snippet(content, marks_pos, { row, 0 }) + -- Calls the snippet expand function for required snippet engine + engines[snippet_engine](generated_snippet, { row, 0 }) + return + else + -- We use default marks for jumping between annotations + -- Append content to row + vim.api.nvim_buf_set_lines(0, row, row, true, content) + + if #marks_pos > 0 then + -- Start session of marks + mark:start() + for _, pos in ipairs(marks_pos) do + mark:add_mark(pos) + end + vim.cmd("startinsert") + mark:jump() + -- Add range mark after first jump + mark:add_range_mark({ row, 0, row + #template_content, 1 }) end - vim.cmd("startinsert") - mark:jump() - -- Add range mark after first jump - mark:add_range_mark({ row, 0, row + #template_content, 1 }) end end, }) diff --git a/lua/neogen/init.lua b/lua/neogen/init.lua index 1e3ae40..6577686 100644 --- a/lua/neogen/init.lua +++ b/lua/neogen/init.lua @@ -91,12 +91,15 @@ end --- --- - to configure a language, just add your configurations in the `languages` table. --- For example, for the `lua` lang: ---- > +--- > --- languages = { --- lua = { -- Configuration here } --- } ---- < ---- Default configurations for a languages can be found in `lua/neogen/configurations/.lua` +--- < +--- Default configurations for a languages can be found in `lua/neogen/configurations/.lua` +--- +--- - To know which snippet engines are supported, take a look at |snippet-integration|. +--- Example: `snippet_engine = "luasnip"` --- ---@toc_entry Configure the setup ---@tag neogen-configuration @@ -109,6 +112,9 @@ neogen.configuration = { -- Configuration for default languages languages = {}, + + -- Use a snippet engine to generate annotations. + snippet_engine = nil, } --minidoc_afterlines_end @@ -120,9 +126,15 @@ neogen.configuration = { --- For example, if you are inside a function, and called `generate({ type = "func" })`, --- Neogen will go until the start of the function and start annotating for you. --- ----@param opts table Options to change default behaviour of generation. ---- - {opts.type} `(string?, default: "func")` Which type we are trying to use for generating annotations. +---@param opts table Optional configs to change default behaviour of generation. +--- - {opts.type} `(string, default: "func")` Which type we are trying to use for generating annotations. --- Currently supported: `func`, `class`, `type`, `file` +--- - {opts.return_snippet} `boolean` if true, will return 3 values from the function call. +--- This option is useful if you want to get the snippet to use with a unsupported snippet engine +--- Below are the returned values: +--- - 1: (type: `string[]`) the resulting lsp snippet +--- - 2: (type: `number`) the `row` to insert the annotations +--- - 3: (type: `number`) the `col` to insert the annotations ---@toc_entry Generate annotations neogen.generate = function(opts) if not conf or not conf.enabled then @@ -130,7 +142,7 @@ neogen.generate = function(opts) return end - require("neogen.generator")(vim.bo.filetype, opts and opts.type) + return require("neogen.generator")(vim.bo.filetype, opts and opts.type, opts and opts.return_snippet) end -- Expose more API ============================================================ @@ -204,6 +216,10 @@ end --- --- Note: We will only document `major` and `minor` versions, not `patch` ones. --- +--- ## 2.3.0~ +--- - Added bundled support with snippet engines ! +--- Check out |snippet-integration| for basic setup +--- For more information of possible options, check out |neogen.generate()| --- ## 2.2.0~ --- ### Python~ --- - Add support for `*args` and `**kwargs` diff --git a/lua/neogen/snippet.lua b/lua/neogen/snippet.lua new file mode 100644 index 0000000..b1f7b22 --- /dev/null +++ b/lua/neogen/snippet.lua @@ -0,0 +1,53 @@ +local notify = require("neogen.utilities.helpers").notify + +--- +--- To use a snippet engine, pass the option into neogen setup: +--- > +--- require('neogen').setup({ +--- snippet_engine = "luasnip", +--- ... +--- }) +--- < +--- Some snippet engines come out of the box bundled with neogen: +--- - `"luasnip"` (https://github.com/L3MON4D3/LuaSnip) +---@tag snippet-integration +---@toc_entry Use popular snippet engines +local snippet = {} +snippet.engines = {} + +--- Converts a template to a lsp-compatible snippet +---@param template string[] the generated annotations to parse +---@param marks table generated marks for the annotations +---@param pos table a tuple of row,col +---@return table resulting snippet lines +---@private +snippet.to_snippet = function(template, marks, pos) + local offset = {} + for i, m in ipairs(marks) do + local r, col = m[1] - pos[1] + 1, m[2] + if offset[r] then + offset[r] = offset[r] + tostring(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]) + end + return template +end + +--- Expand snippet for luasnip engine +---@param snip string the snippet to expand +---@param pos table a tuple of row, col +---@private +snippet.engines.luasnip = function(snip, pos) + local ok, luasnip = pcall(require, "luasnip") + if not ok then + notify("Luasnip not found, aborting...", vim.log.levels.ERROR) + return + end + vim.fn.append(pos[1], "") + luasnip.lsp_expand(table.concat(snip, "\n"), { pos = { pos[1], pos[2] } }) +end + +return snippet diff --git a/scripts/minidoc.lua b/scripts/minidoc.lua index cdc1b0e..7ee57a9 100644 --- a/scripts/minidoc.lua +++ b/scripts/minidoc.lua @@ -4,4 +4,4 @@ if _G.MiniDoc == nil then minidoc.setup() end -minidoc.generate({ "lua/neogen/init.lua", "lua/neogen/template.lua" }, nil, nil) +minidoc.generate({ "lua/neogen/init.lua", "lua/neogen/snippet.lua", "lua/neogen/template.lua" }, nil, nil)