Merge pull request #64 from kevinhwang91/refactor
BREAKING CHANGE (developers only) - Deleted `jump_text` option, and made it default - Deleted the ability to override generators and granulators
This commit is contained in:
65
README.md
65
README.md
@@ -112,40 +112,31 @@ Or, if you want to use a key that's already used for completion purposes, take a
|
||||
local cmp = require('cmp')
|
||||
local neogen = require('neogen')
|
||||
|
||||
local t = function(str)
|
||||
return vim.api.nvim_replace_termcodes(str, true, true, true)
|
||||
end
|
||||
|
||||
local check_back_space = function()
|
||||
local col = vim.fn.col '.' - 1
|
||||
return col == 0 or vim.fn.getline('.'):sub(col, col):match '%s' ~= nil
|
||||
end
|
||||
|
||||
cmp.setup {
|
||||
...
|
||||
|
||||
-- You must set mapping if you want.
|
||||
mapping = {
|
||||
["<tab>"] = cmp.mapping(function(fallback)
|
||||
if neogen.jumpable() then
|
||||
vim.fn.feedkeys(t("<cmd>lua require('neogen').jump_next()<CR>"), "")
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end, {
|
||||
"i",
|
||||
"s",
|
||||
}),
|
||||
["<S-tab>"] = cmp.mapping(function(fallback)
|
||||
if neogen.jumpable(-1) then
|
||||
vim.fn.feedkeys(t("<cmd>lua require('neogen').jump_prev()<CR>"), "")
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end, {
|
||||
"i",
|
||||
"s",
|
||||
}),
|
||||
["<tab>"] = cmp.mapping(function(fallback)
|
||||
if require('neogen').jumpable() then
|
||||
require('neogen').jump_next()
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end, {
|
||||
"i",
|
||||
"s",
|
||||
}),
|
||||
["<S-tab>"] = cmp.mapping(function(fallback)
|
||||
if require('neogen').jumpable(true) then
|
||||
require('neogen').jump_prev()
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end, {
|
||||
"i",
|
||||
"s",
|
||||
}),
|
||||
},
|
||||
...
|
||||
}
|
||||
@@ -169,14 +160,14 @@ If you're not satisfied with the default configuration for a language, you can c
|
||||
```lua
|
||||
require('neogen').setup {
|
||||
enabled = true,
|
||||
languages = {
|
||||
lua = {
|
||||
template = {
|
||||
annotation_convention = "emmylua" -- for a full list of annotation_conventions, see supported-languages below,
|
||||
... -- for more template configurations, see the language's configuration file in configurations/{lang}.lua
|
||||
}
|
||||
},
|
||||
...
|
||||
languages = {
|
||||
lua = {
|
||||
template = {
|
||||
annotation_convention = "emmylua" -- for a full list of annotation_conventions, see supported-languages below,
|
||||
... -- for more template configurations, see the language's configuration file in configurations/{lang}.lua
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -95,11 +95,8 @@ Neogen provides those defaults, and you can change them to suit your needs
|
||||
-- Go to annotation after insertion, and change to insert mode
|
||||
input_after_comment = true,
|
||||
|
||||
-- Symbol to find for jumping cursor in template
|
||||
jump_text = "$1",
|
||||
|
||||
-- Configuration for default languages
|
||||
languages = {},
|
||||
languages = {}
|
||||
}
|
||||
<
|
||||
# Notes~
|
||||
@@ -112,9 +109,6 @@ Neogen provides those defaults, and you can change them to suit your needs
|
||||
}
|
||||
<
|
||||
|
||||
- `jump_text` is widely used and will certainly break most language templates.
|
||||
I'm thinking of removing it from defaults so that it can't be modified
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
*neogen.generate()*
|
||||
`neogen.generate`({opts})
|
||||
@@ -366,4 +360,4 @@ If not specified, will use this line for all types.
|
||||
{required} `(string)` If specified, is used in if the first field of the table is a `table` (example above)
|
||||
|
||||
|
||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||
|
||||
23
lua/neogen/config.lua
Normal file
23
lua/neogen/config.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
local config = {_data = {}}
|
||||
|
||||
config.get = function()
|
||||
return config._data
|
||||
end
|
||||
|
||||
config.setup = function(default, user)
|
||||
local data = vim.tbl_deep_extend("keep", user or {}, default)
|
||||
setmetatable(data.languages, {
|
||||
__index = function(langs, ft)
|
||||
local ok, ft_config = pcall(require, "neogen.configurations." .. ft)
|
||||
if not ok then
|
||||
ft_config = nil
|
||||
end
|
||||
rawset(langs, ft, ft_config)
|
||||
return ft_config
|
||||
end
|
||||
})
|
||||
config._data = data
|
||||
return data
|
||||
end
|
||||
|
||||
return config
|
||||
@@ -1,7 +1,7 @@
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local default_locator = require("neogen.locators.default")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
local i = require("neogen.types.template").item
|
||||
|
||||
local c_params = {
|
||||
@@ -149,14 +149,14 @@ local c_config = {
|
||||
return nil
|
||||
end
|
||||
|
||||
if node_info.current == nil then
|
||||
if not node_info.current then
|
||||
return result
|
||||
end
|
||||
|
||||
-- if the function happens to be a function template we want to place
|
||||
-- the annotation before the template statement and extract the
|
||||
-- template parameters names as well
|
||||
if node_info.current:parent() == nil then
|
||||
if not node_info.current:parent() then
|
||||
return result
|
||||
end
|
||||
if node_info.current:parent():type() == "template_declaration" then
|
||||
@@ -165,10 +165,6 @@ local c_config = {
|
||||
return result
|
||||
end,
|
||||
|
||||
-- Use default granulator and generator
|
||||
granulator = nil,
|
||||
generator = nil,
|
||||
|
||||
template = template:add_default_annotation("doxygen"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
local i = require("neogen.types.template").item
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
|
||||
return {
|
||||
parent = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
|
||||
local function_tree = {
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
local i = require("neogen.types.template").item
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
|
||||
local function_tree = {
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local i = require("neogen.types.template").item
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
|
||||
local function_extractor = function(node, type)
|
||||
if not vim.tbl_contains({ "local", "function" }, type) then
|
||||
@@ -145,9 +145,5 @@ return {
|
||||
-- Custom lua locator that escapes from comments
|
||||
locator = require("neogen.locators.lua"),
|
||||
|
||||
-- Use default granulator and generator
|
||||
granulator = nil,
|
||||
generator = nil,
|
||||
|
||||
template = template:config({ use_default_comment = true }):add_default_annotation("emmylua"):add_annotation("ldoc"),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
local i = require("neogen.types.template").item
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@ local ts_utils = require("nvim-treesitter.ts_utils")
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local locator = require("neogen.locators.default")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
local i = require("neogen.types.template").item
|
||||
|
||||
local parent = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local i = require("neogen.types.template").item
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
|
||||
return {
|
||||
parent = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
local extractors = require("neogen.utilities.extractors")
|
||||
local nodes_utils = require("neogen.utilities.nodes")
|
||||
local i = require("neogen.types.template").item
|
||||
local template = require("neogen.utilities.template")
|
||||
local template = require("neogen.template")
|
||||
|
||||
local construct_type_annotation = function(parameters)
|
||||
local results = parameters and {} or nil
|
||||
|
||||
227
lua/neogen/generator.lua
Normal file
227
lua/neogen/generator.lua
Normal file
@@ -0,0 +1,227 @@
|
||||
local helpers = require("neogen.utilities.helpers")
|
||||
local notify = helpers.notify
|
||||
|
||||
local ok, ts_utils = pcall(require, "nvim-treesitter.ts_utils")
|
||||
if not ok then
|
||||
notify("neogen requires nvim-treesitter to operate :(", vim.log.levels.ERROR)
|
||||
return function(_, _) end
|
||||
end
|
||||
|
||||
local conf = require("neogen.config").get()
|
||||
local granulator = require("neogen.granulator")
|
||||
|
||||
local mark = require("neogen.mark")
|
||||
local nodes = require("neogen.utilities.nodes")
|
||||
local default_locator = require("neogen.locators.default")
|
||||
local JUMP_TEXT = "$1"
|
||||
|
||||
local function get_parent_node(filetype, typ, language)
|
||||
local parser = vim.treesitter.get_parser(0, filetype)
|
||||
local tstree = parser:parse()[1]
|
||||
local tree = tstree:root()
|
||||
|
||||
language.locator = language.locator or default_locator
|
||||
-- Use the language locator to locate one of the required parent nodes above the cursor
|
||||
return language.locator({root = tree, current = ts_utils.get_node_at_cursor(0)}, language.parent[typ])
|
||||
end
|
||||
|
||||
--- Generates the prefix according to `template` options.
|
||||
--- Prefix is generated with an offset (currently spaces) repetition of `n` times.
|
||||
--- If `template.use_default_comment` is not set to false, the `commentstring` is added
|
||||
---@param template table
|
||||
---@param commentstring string
|
||||
---@param n integer
|
||||
---@return string
|
||||
local function prefix_generator(template, commentstring, n)
|
||||
local prefix = (" "):rep(n)
|
||||
|
||||
-- Do not append the comment string if not wanted
|
||||
if template.use_default_comment ~= false then
|
||||
prefix = prefix .. commentstring
|
||||
end
|
||||
return prefix
|
||||
end
|
||||
|
||||
local function get_place_pos(parent, position, append, typ)
|
||||
local row, col
|
||||
-- You can use a custom position placement
|
||||
if position and type(position) == "function" then
|
||||
row, col = position(parent, typ)
|
||||
end
|
||||
|
||||
-- If the custom placement does not return the correct row and cols, default to the node range
|
||||
-- Same if there is no custom placement
|
||||
if not row and not col then
|
||||
-- Because the file type is always at top
|
||||
if typ == "file" then
|
||||
row, col = 0, 0
|
||||
else
|
||||
row, col = ts_utils.get_node_range(parent)
|
||||
end
|
||||
end
|
||||
|
||||
append = append or {}
|
||||
|
||||
if append.position == "after" and not vim.tbl_contains(append.disabled or {}, typ) then
|
||||
local child_node = nodes:matching_child_nodes(parent, append.child_name)[1]
|
||||
if not child_node and append.fallback then
|
||||
local fallback = nodes:matching_child_nodes(parent, append.fallback)[1]
|
||||
if fallback then
|
||||
row, col = fallback:range()
|
||||
end
|
||||
else
|
||||
row, col = child_node:range()
|
||||
end
|
||||
end
|
||||
|
||||
return row, col
|
||||
end
|
||||
|
||||
--- Uses the provided template to format the annotations with data found by the granulator
|
||||
---@param parent userdata the node used to generate the annotations
|
||||
---@param data table the data from the granulator, which is a set of [type] = results
|
||||
---@param template table a template from the configuration
|
||||
---@param required_type string
|
||||
---@return table { line, content }, with line being the line to append the content
|
||||
local function generate_content(parent, data, template, required_type)
|
||||
local row, col = get_place_pos(parent, template.position, template.append, required_type)
|
||||
|
||||
local commentstring = vim.trim(vim.bo.commentstring:format(""))
|
||||
local generated_template = template[template.annotation_convention]
|
||||
|
||||
local result = {}
|
||||
local prefix = prefix_generator(template, commentstring, col)
|
||||
|
||||
local function append_str(str)
|
||||
table.insert(result, str == "" and str or prefix .. str)
|
||||
end
|
||||
|
||||
for _, values in ipairs(generated_template) do
|
||||
local inserted_type, formatted_str, opts = unpack(values)
|
||||
opts = opts or {}
|
||||
if type(opts.type) ~= "table" or vim.tbl_contains(opts.type, required_type) then
|
||||
-- Will append the item before all their nodes
|
||||
if type(opts.before_first_item) == "table" and data[inserted_type] then
|
||||
for _, s in ipairs(opts.before_first_item) do
|
||||
append_str(s)
|
||||
end
|
||||
end
|
||||
|
||||
local ins_type = type(inserted_type)
|
||||
if ins_type == "nil" then
|
||||
local no_data = vim.tbl_isempty(data)
|
||||
if opts.no_results then
|
||||
if no_data then
|
||||
append_str(formatted_str)
|
||||
end
|
||||
elseif not no_data then
|
||||
append_str(formatted_str:format(""))
|
||||
end
|
||||
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))
|
||||
if opts.after_each then
|
||||
append_str(opts.after_each:format(s))
|
||||
end
|
||||
end
|
||||
elseif ins_type == "table" and #inserted_type > 0 and type(data[opts.required]) == "table" then
|
||||
-- First item in the template item can be a table.
|
||||
-- in this case, the template will use provided types to generate the line.
|
||||
-- e.g {{ "type", "parameter"}, "* @type {%s} %s"}
|
||||
-- will replace every %s with the provided data from those types
|
||||
|
||||
-- If one item is missing, it'll use the required option to iterate
|
||||
-- and will replace the missing item with JUMP_TEXT
|
||||
for _, extracted in pairs(data[opts.required]) do
|
||||
local fmt_args = {}
|
||||
for _, typ in ipairs(inserted_type) do
|
||||
if extracted[typ] then
|
||||
table.insert(fmt_args, extracted[typ][1])
|
||||
else
|
||||
table.insert(fmt_args, JUMP_TEXT)
|
||||
end
|
||||
end
|
||||
append_str(formatted_str:format(unpack(fmt_args)))
|
||||
if opts.after_each then
|
||||
append_str(opts.after_each:format(unpack(fmt_args)))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return row, result
|
||||
end
|
||||
|
||||
return setmetatable({}, {
|
||||
__call = function(_, filetype, typ)
|
||||
if filetype == "" then
|
||||
notify("No filetype detected", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
local language = conf.languages[filetype]
|
||||
if not language then
|
||||
notify("Language " .. filetype .. " not supported.", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
typ = (type(typ) ~= "string" or typ == "") and "func" or typ -- Default type
|
||||
local template = language.template
|
||||
if not language.parent[typ] or not language.data[typ] or not template or not template.annotation_convention then
|
||||
notify("Type `" .. typ .. "` not supported", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local parent_node = get_parent_node(filetype, typ, language)
|
||||
if not parent_node then
|
||||
return
|
||||
end
|
||||
|
||||
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 content = {}
|
||||
local marks_pos = {}
|
||||
|
||||
local input_after_comment = conf.input_after_comment
|
||||
|
||||
local pattern = JUMP_TEXT .. (input_after_comment and "[|%w]*" or "|?")
|
||||
for r, line in ipairs(template_content) do
|
||||
local last_col = 0
|
||||
local len = 1
|
||||
local sects = {}
|
||||
while true do
|
||||
local s, e = line:find(pattern, last_col + 1)
|
||||
if not s then
|
||||
table.insert(sects, line:sub(last_col + 1, -1))
|
||||
break
|
||||
end
|
||||
table.insert(sects, line:sub(last_col + 1, s - 1))
|
||||
if input_after_comment then
|
||||
len = len + s - last_col - 1
|
||||
table.insert(marks_pos, {row + r - 1, len - 1})
|
||||
end
|
||||
last_col = e
|
||||
end
|
||||
table.insert(content, table.concat(sects, ""))
|
||||
end
|
||||
|
||||
-- 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
|
||||
end
|
||||
})
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
local ts_utils = require("nvim-treesitter.ts_utils")
|
||||
local nodes = require("neogen.utilities.nodes")
|
||||
|
||||
--- Generates the prefix according to `template` options.
|
||||
--- Prefix is generated with an offset (currently spaces) repetition of `n` times.
|
||||
--- If `template.use_default_comment` is not set to false, the `commentstring` is added
|
||||
--- @param template table
|
||||
--- @param commentstring string
|
||||
--- @param n integer
|
||||
--- @return string
|
||||
local function prefix_generator(template, commentstring, n)
|
||||
local prefix = (" "):rep(n)
|
||||
|
||||
-- Do not append the comment string if not wanted
|
||||
if template.use_default_comment ~= false then
|
||||
prefix = prefix .. commentstring
|
||||
end
|
||||
return prefix
|
||||
end
|
||||
|
||||
--- Does some checks within the `value` and adds the `prefix` before it if required
|
||||
--- @param prefix string
|
||||
--- @param value string
|
||||
--- @return string
|
||||
local function conditional_prefix_inserter(prefix, value)
|
||||
if value == "" then
|
||||
return value
|
||||
else
|
||||
return prefix .. value
|
||||
end
|
||||
end
|
||||
|
||||
--- Insert values from `items` in `result` and returns it
|
||||
--- @param result table
|
||||
--- @param items table
|
||||
--- @param prefix string
|
||||
--- @return table result
|
||||
local function add_values_to_result(result, items, prefix)
|
||||
for _, value in ipairs(items) do
|
||||
local inserted = conditional_prefix_inserter(prefix, value)
|
||||
table.insert(result, inserted)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---Default Generator:
|
||||
---Uses the provided template to format the annotations with data found by the granulator
|
||||
--- @param parent userdata the node used to generate the annotations
|
||||
--- @param data table the data from the granulator, which is a set of [type] = results
|
||||
--- @param template table a template from the configuration
|
||||
--- @param required_type string
|
||||
--- @return table { line, content, opts }, with line being the line to append the content
|
||||
return function(parent, data, template, required_type)
|
||||
local row_to_place, col_to_place
|
||||
-- You can use a custom position placement
|
||||
if template.position and type(template.position) == "function" then
|
||||
row_to_place, col_to_place = template.position(parent, required_type)
|
||||
end
|
||||
|
||||
-- If the custom placement does not return the correct row and cols, default to the node range
|
||||
-- Same if there is no custom placement
|
||||
if not row_to_place and not col_to_place then
|
||||
-- Because the file type is always at top
|
||||
if required_type == "file" then
|
||||
row_to_place = 0
|
||||
col_to_place = 0
|
||||
else
|
||||
row_to_place, col_to_place, _, _ = ts_utils.get_node_range(parent)
|
||||
end
|
||||
end
|
||||
|
||||
local commentstring, generated_template = vim.trim(vim.api.nvim_buf_get_option(0, "commentstring"):format(""))
|
||||
|
||||
local append = template.append or {}
|
||||
|
||||
if append.position == "after" and not vim.tbl_contains(append.disabled or {}, required_type) then
|
||||
local child_node = nodes:matching_child_nodes(parent, append.child_name)[1]
|
||||
if not child_node and append.fallback then
|
||||
local fallback = nodes:matching_child_nodes(parent, append.fallback)[1]
|
||||
if fallback then
|
||||
row_to_place, col_to_place, _, _ = fallback:range()
|
||||
end
|
||||
else
|
||||
row_to_place, col_to_place, _, _ = child_node:range()
|
||||
end
|
||||
end
|
||||
|
||||
if not template or not template.annotation_convention then
|
||||
-- Default template
|
||||
generated_template = {
|
||||
{ nil, "" },
|
||||
{ "name", " @Summary " },
|
||||
{ "parameters", " @Param " },
|
||||
{ "return", " @Return " },
|
||||
}
|
||||
elseif type(template) == "function" then
|
||||
-- You can also pass a function as a template
|
||||
generated_template = template(parent, commentstring, data)
|
||||
else
|
||||
generated_template = template[template.annotation_convention]
|
||||
end
|
||||
|
||||
local function parse_generated_template()
|
||||
local result = {}
|
||||
local prefix = prefix_generator(template, commentstring, col_to_place)
|
||||
|
||||
for _, values in ipairs(generated_template) do
|
||||
local inserted_type = values[1]
|
||||
local formatted_string = values[2]
|
||||
local opts = vim.deepcopy(values[3]) or {}
|
||||
|
||||
opts.type = opts.type or { required_type }
|
||||
|
||||
if opts.type and vim.tbl_contains(opts.type, required_type) then
|
||||
-- Will append the item before all their nodes
|
||||
if opts.before_first_item and data[inserted_type] then
|
||||
result = add_values_to_result(result, opts.before_first_item, prefix)
|
||||
end
|
||||
|
||||
-- If there is no data returned, will append the string with opts.no_results
|
||||
if opts.no_results == true and vim.tbl_isempty(data) then
|
||||
local inserted = conditional_prefix_inserter(prefix, formatted_string)
|
||||
table.insert(result, inserted)
|
||||
else
|
||||
-- append the output as is
|
||||
if inserted_type == nil and opts.no_results ~= true and not vim.tbl_isempty(data) then
|
||||
local inserted = conditional_prefix_inserter(prefix, formatted_string:format(""))
|
||||
table.insert(result, inserted)
|
||||
elseif inserted_type then
|
||||
-- Format the output with the corresponding data
|
||||
if type(inserted_type) == "string" and data[inserted_type] then
|
||||
for _, value in ipairs(data[inserted_type]) do
|
||||
local inserted = conditional_prefix_inserter(prefix, formatted_string:format(value))
|
||||
table.insert(result, inserted)
|
||||
if opts.after_each then
|
||||
table.insert(
|
||||
result,
|
||||
conditional_prefix_inserter(prefix, opts.after_each):format(value)
|
||||
)
|
||||
end
|
||||
end
|
||||
elseif type(inserted_type) == "table" and data[opts.required] then
|
||||
-- First item in the template item can be a table.
|
||||
-- in this case, the template will use provided types to generate the line.
|
||||
-- e.g {{ "type", "parameter"}, "* @type {%s} %s"}
|
||||
-- will replace every %s with the provided data from those types
|
||||
|
||||
-- If one item is missing, it'll use the required option to iterate
|
||||
-- and will replace the missing item with default jump_text
|
||||
for _, tbl in pairs(data[opts.required]) do
|
||||
local _values = {}
|
||||
for _, v in ipairs(inserted_type) do
|
||||
if tbl[v] then
|
||||
table.insert(_values, tbl[v][1])
|
||||
else
|
||||
local jump_text = neogen.configuration.jump_text
|
||||
table.insert(_values, jump_text)
|
||||
end
|
||||
end
|
||||
if not vim.tbl_isempty(_values) then
|
||||
local inserted = conditional_prefix_inserter(
|
||||
prefix,
|
||||
formatted_string:format(unpack(_values))
|
||||
)
|
||||
table.insert(result, inserted)
|
||||
if opts.after_each then
|
||||
if type(opts.after_each) == "table" then
|
||||
local _v = {}
|
||||
local index_types = opts.after_each["index_types"]
|
||||
for _, i in ipairs(index_types) do
|
||||
table.insert(_v, _values[i])
|
||||
end
|
||||
_values = _v
|
||||
table.insert(
|
||||
result,
|
||||
conditional_prefix_inserter(
|
||||
prefix,
|
||||
opts.after_each[1]:format(unpack(_values))
|
||||
)
|
||||
)
|
||||
else
|
||||
table.insert(
|
||||
result,
|
||||
conditional_prefix_inserter(
|
||||
prefix,
|
||||
opts.after_each:format(unpack(_values))
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return row_to_place, col_to_place, parse_generated_template()
|
||||
end
|
||||
45
lua/neogen/granulator.lua
Normal file
45
lua/neogen/granulator.lua
Normal file
@@ -0,0 +1,45 @@
|
||||
local ts_utils = require("nvim-treesitter.ts_utils")
|
||||
local helpers = require("neogen.utilities.helpers")
|
||||
|
||||
--- Tries to use the configuration to find all required content nodes from the parent node
|
||||
---@param parent_node userdata the node found by the locator
|
||||
---@param node_data table the data from configurations[lang].data
|
||||
return function(parent_node, node_data)
|
||||
local result = {}
|
||||
if not parent_node then
|
||||
return result
|
||||
end
|
||||
|
||||
for parent_type, child_data in pairs(node_data) do
|
||||
local matches = helpers.split(parent_type, "|", true)
|
||||
|
||||
-- Look if the parent node is one of the matches
|
||||
if vim.tbl_contains(matches, parent_node:type()) then
|
||||
-- For each child_data in the matched parent node
|
||||
for i, data in pairs(child_data) do
|
||||
local index = tonumber(i)
|
||||
assert(index, "Need a valid index")
|
||||
|
||||
local child_node = index == 0 and parent_node or parent_node:named_child(index - 1)
|
||||
|
||||
if not child_node then
|
||||
return
|
||||
end
|
||||
|
||||
if not data.match or child_node:type() == data.match then
|
||||
if type(data.extract) == "function" then
|
||||
-- Extract content from it { [type] = { data } }
|
||||
for type, extracted_data in pairs(data.extract(child_node)) do
|
||||
result[type] = extracted_data
|
||||
end
|
||||
else
|
||||
-- if not extract function, get the text from the node (required: data.type)
|
||||
result[data.type] = ts_utils.get_node_text(child_node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
@@ -1,61 +0,0 @@
|
||||
local ts_utils = require("nvim-treesitter.ts_utils")
|
||||
|
||||
--- Tries to use the configuration to find all required content nodes from the parent node
|
||||
--- @param parent_node userdata the node found by the locator
|
||||
--- @param node_data table the data from configurations[lang].data
|
||||
return function(parent_node, node_data)
|
||||
local result = {}
|
||||
|
||||
for parent_type, child_data in pairs(node_data) do
|
||||
local matches = vim.split(parent_type, "|", true)
|
||||
|
||||
-- Look if the parent node is one of the matches
|
||||
if vim.tbl_contains(matches, parent_node:type()) then
|
||||
-- For each child_data in the matched parent node
|
||||
for i, data in pairs(child_data) do
|
||||
local child_node
|
||||
|
||||
if tonumber(i) == 0 then
|
||||
child_node = parent_node
|
||||
else
|
||||
child_node = parent_node:named_child(tonumber(i) - 1)
|
||||
end
|
||||
|
||||
if not child_node then
|
||||
return
|
||||
end
|
||||
|
||||
if child_node:type() == data.match or not data.match then
|
||||
local extract = {}
|
||||
|
||||
if data.extract then
|
||||
-- Extract content from it { [type] = { data } }
|
||||
extract = data.extract(child_node)
|
||||
|
||||
-- All extracted values are created added in result, like so: [data.type] = { extract }
|
||||
if data.type then
|
||||
-- Extract information into a one-dimensional array
|
||||
local one_dimensional_arr = {}
|
||||
|
||||
for _, values in pairs(extract) do
|
||||
table.insert(one_dimensional_arr, values)
|
||||
end
|
||||
|
||||
result[data.type] = one_dimensional_arr
|
||||
else
|
||||
for type, extracted_data in pairs(extract) do
|
||||
result[type] = extracted_data
|
||||
end
|
||||
end
|
||||
else
|
||||
-- if not extract function, get the text from the node (required: data.type)
|
||||
extract = ts_utils.get_node_text(child_node)
|
||||
result[data.type] = extract
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
@@ -1,6 +1,3 @@
|
||||
local ok, ts_utils = pcall(require, "nvim-treesitter.ts_utils")
|
||||
assert(ok, "neogen requires nvim-treesitter to operate :(")
|
||||
|
||||
--- Table of contents:
|
||||
---@toc
|
||||
---@text
|
||||
@@ -22,22 +19,15 @@ assert(ok, "neogen requires nvim-treesitter to operate :(")
|
||||
--- - Written in lua (and uses Tree-sitter)
|
||||
---@tag neogen
|
||||
---@toc_entry Neogen's purpose
|
||||
|
||||
-- Requires ===================================================================
|
||||
|
||||
local neogen = {}
|
||||
|
||||
local conf
|
||||
local config = require("neogen.config")
|
||||
local helpers = require("neogen.utilities.helpers")
|
||||
local mark = require("neogen.mark")
|
||||
local notify = helpers.notify
|
||||
|
||||
local cursor = require("neogen.utilities.cursor")
|
||||
|
||||
local default_locator = require("neogen.locators.default")
|
||||
local default_granulator = require("neogen.granulators.default")
|
||||
local default_generator = require("neogen.generators.default")
|
||||
|
||||
local autocmd = require("neogen.utilities.autocmd")
|
||||
|
||||
-- Module definition ==========================================================
|
||||
|
||||
--- Module setup
|
||||
@@ -47,20 +37,10 @@ local autocmd = require("neogen.utilities.autocmd")
|
||||
---@usage `require('neogen').setup({})` (replace `{}` with your `config` table)
|
||||
---@toc_entry The setup function
|
||||
neogen.setup = function(opts)
|
||||
-- Stores the user configuration globally so that we keep his configs when switching languages
|
||||
neogen.user_configuration = opts or {}
|
||||
|
||||
neogen.configuration = vim.tbl_deep_extend("keep", neogen.user_configuration, neogen.configuration)
|
||||
|
||||
if neogen.configuration.enabled == true then
|
||||
conf = config.setup(neogen.configuration, opts)
|
||||
if conf.enabled then
|
||||
neogen.generate_command()
|
||||
end
|
||||
|
||||
-- Export module
|
||||
_G.neogen = neogen
|
||||
|
||||
-- Force configuring current language again when doing `setup` call.
|
||||
helpers.switch_language()
|
||||
end
|
||||
|
||||
--- Neogen Usage
|
||||
@@ -117,8 +97,6 @@ end
|
||||
--- }
|
||||
--- <
|
||||
---
|
||||
--- - `jump_text` is widely used and will certainly break most language templates.
|
||||
--- I'm thinking of removing it from defaults so that it can't be modified
|
||||
---@toc_entry Configure the setup
|
||||
---@tag neogen-configuration
|
||||
neogen.configuration = {
|
||||
@@ -128,9 +106,6 @@ neogen.configuration = {
|
||||
-- Go to annotation after insertion, and change to insert mode
|
||||
input_after_comment = true,
|
||||
|
||||
-- Symbol to find for jumping cursor in template
|
||||
jump_text = "$1",
|
||||
|
||||
-- Configuration for default languages
|
||||
languages = {},
|
||||
}
|
||||
@@ -149,128 +124,12 @@ neogen.configuration = {
|
||||
--- Currently supported: `func`, `class`, `type`, `file`
|
||||
---@toc_entry Generate annotations
|
||||
neogen.generate = function(opts)
|
||||
opts = opts or {}
|
||||
opts.type = (opts.type == nil or opts.type == "") and "func" or opts.type -- Default type
|
||||
|
||||
if not neogen.configuration.enabled then
|
||||
if not conf.enabled then
|
||||
notify("Neogen not enabled. Please enable it.", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
if vim.bo.filetype == "" then
|
||||
notify("No filetype detected", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local parser = vim.treesitter.get_parser(0, vim.bo.filetype)
|
||||
local tstree = parser:parse()[1]
|
||||
local tree = tstree:root()
|
||||
|
||||
local language = neogen.configuration.languages[vim.bo.filetype]
|
||||
|
||||
if not language then
|
||||
notify("Language " .. vim.bo.filetype .. " not supported.", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
language.locator = language.locator or default_locator
|
||||
language.granulator = language.granulator or default_granulator
|
||||
language.generator = language.generator or default_generator
|
||||
|
||||
if not language.parent[opts.type] or not language.data[opts.type] then
|
||||
notify("Type `" .. opts.type .. "` not supported", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
-- Use the language locator to locate one of the required parent nodes above the cursor
|
||||
local located_parent_node = language.locator({
|
||||
root = tree,
|
||||
current = ts_utils.get_node_at_cursor(0),
|
||||
}, language.parent[opts.type])
|
||||
|
||||
if not located_parent_node then
|
||||
return
|
||||
end
|
||||
|
||||
-- Use the language granulator to get the required content inside the node found with the locator
|
||||
local data = language.granulator(located_parent_node, language.data[opts.type])
|
||||
|
||||
if data then
|
||||
-- Will try to generate the documentation from a template and the data found from the granulator
|
||||
local to_place, start_column, content = language.generator(
|
||||
located_parent_node,
|
||||
data,
|
||||
language.template,
|
||||
opts.type
|
||||
)
|
||||
|
||||
if #content ~= 0 then
|
||||
cursor.del_extmarks() -- Delete previous extmarks before setting any new ones
|
||||
|
||||
local jump_text = language.jump_text or neogen.configuration.jump_text
|
||||
|
||||
--- Removes jump_text marks and keep the second part of jump_text|other_text if there is one (which is other_text)
|
||||
local delete_marks = function(v)
|
||||
local pattern = jump_text .. "[|%w]+"
|
||||
local matched = string.match(v, pattern)
|
||||
|
||||
if matched then
|
||||
local split = vim.split(matched, "|", true)
|
||||
if #split == 2 and neogen.configuration.input_after_comment == false then
|
||||
return string.gsub(v, jump_text .. "|", "")
|
||||
end
|
||||
else
|
||||
return string.gsub(v, jump_text, "")
|
||||
end
|
||||
|
||||
return string.gsub(v, pattern, "")
|
||||
end
|
||||
|
||||
local content_with_marks = vim.deepcopy(content)
|
||||
|
||||
-- delete all jump_text marks
|
||||
content = vim.tbl_map(delete_marks, content)
|
||||
|
||||
-- Append the annotation in required place
|
||||
vim.fn.append(to_place, content)
|
||||
|
||||
-- Place cursor after annotations and start editing
|
||||
-- First and last extmarks are needed to know the range of inserted content
|
||||
if neogen.configuration.input_after_comment == true then
|
||||
-- Creates extmark for the beggining of the content
|
||||
cursor.create(to_place + 1, start_column)
|
||||
-- Creates extmarks for the content
|
||||
for i, value in pairs(content_with_marks) do
|
||||
local start = 0
|
||||
local count = 0
|
||||
while true do
|
||||
start = string.find(value, jump_text, start + 1)
|
||||
if not start then
|
||||
break
|
||||
end
|
||||
cursor.create(to_place + i, start - count * #jump_text)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Create extmark to jump back to current location
|
||||
local pos = vim.api.nvim_win_get_cursor(0)
|
||||
local col = pos[2] + 2
|
||||
|
||||
-- If the line we are in is empty, it will throw an error out of bounds.
|
||||
if vim.api.nvim_get_current_line() == "" then
|
||||
col = pos[2] + 1
|
||||
end
|
||||
|
||||
cursor.create(pos[1], col)
|
||||
|
||||
-- Creates extmark for the end of the content
|
||||
cursor.create(to_place + #content + 1, 0)
|
||||
|
||||
cursor.jump({ first_time = true })
|
||||
end
|
||||
end
|
||||
end
|
||||
require("neogen.generator")(vim.bo.filetype, opts and opts.type)
|
||||
end
|
||||
|
||||
-- Expose more API ============================================================
|
||||
@@ -279,18 +138,15 @@ end
|
||||
neogen.match_commands = helpers.match_commands
|
||||
|
||||
--- Get a template for a particular filetype
|
||||
---@param filetype string
|
||||
---@param filetype? string
|
||||
---@return neogen.TemplateConfig|nil
|
||||
neogen.get_template = function(filetype)
|
||||
if not neogen.configuration.languages[filetype] then
|
||||
return
|
||||
local template
|
||||
local ft_conf = filetype and conf.languages[filetype] or conf.languages[vim.bo.filetype]
|
||||
if ft_conf and ft_conf.template then
|
||||
template = ft_conf.template
|
||||
end
|
||||
|
||||
if not neogen.configuration.languages[filetype].template then
|
||||
return
|
||||
end
|
||||
|
||||
return neogen.configuration.languages[filetype].template
|
||||
return template
|
||||
end
|
||||
|
||||
-- Required for use with completion engine =====================================
|
||||
@@ -298,24 +154,20 @@ end
|
||||
--- Jumps to the next cursor template position
|
||||
---@private
|
||||
function neogen.jump_next()
|
||||
if neogen.jumpable() then
|
||||
cursor.jump()
|
||||
end
|
||||
mark:jump()
|
||||
end
|
||||
|
||||
--- Jumps to the next cursor template position
|
||||
---@private
|
||||
function neogen.jump_prev()
|
||||
if cursor.jumpable(-1) then
|
||||
cursor.jump_prev()
|
||||
end
|
||||
mark:jump(true)
|
||||
end
|
||||
|
||||
--- Checks if the cursor can jump backwards or forwards
|
||||
--- @param reverse number? if `-1`, will try to see if can be jumped backwards
|
||||
--- @param reverse number? if `-1` or true, will try to see if can be jumped backwards
|
||||
---@private
|
||||
function neogen.jumpable(reverse)
|
||||
return cursor.jumpable(reverse)
|
||||
return mark:jumpable(reverse == -1 or reverse == true)
|
||||
end
|
||||
|
||||
-- Command and autocommands ====================================================
|
||||
@@ -323,22 +175,11 @@ end
|
||||
--- Generates the `:Neogen` command, which calls `neogen.generate()`
|
||||
---@private
|
||||
function neogen.generate_command()
|
||||
vim.api.nvim_command(
|
||||
'command! -nargs=? -complete=customlist,v:lua.neogen.match_commands -range -bar Neogen lua require("neogen").generate({ type = <q-args>})'
|
||||
)
|
||||
vim.cmd([[
|
||||
command! -nargs=? -complete=customlist,v:lua.require'neogen'.match_commands -range -bar Neogen lua require("neogen").generate({ type = <q-args>})
|
||||
]])
|
||||
end
|
||||
|
||||
autocmd.subscribe("BufEnter", function()
|
||||
helpers.switch_language()
|
||||
end)
|
||||
|
||||
vim.cmd([[
|
||||
augroup ___neogen___
|
||||
autocmd!
|
||||
autocmd BufEnter * lua require'neogen.utilities.autocmd'.emit('BufEnter')
|
||||
augroup END
|
||||
]])
|
||||
|
||||
--- Contribute to Neogen
|
||||
---
|
||||
--- * Want to add a new language?
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
--- @class Neogen.node_info
|
||||
--- @field current userdata the current node from cursor
|
||||
--- @field root? userdata the root node
|
||||
---@class Neogen.node_info
|
||||
---@field current userdata the current node from cursor
|
||||
---@field root? userdata the root node
|
||||
|
||||
--- The default locator tries to find one of the nodes to match in the current node
|
||||
--- If it does not find one, will fetch the parents until he finds one
|
||||
--- @param node_info Neogen.node_info a node informations
|
||||
--- @param nodes_to_match table a list of parent nodes to match
|
||||
--- @return userdata node one of the nodes to match directly above the given node
|
||||
---@param node_info Neogen.node_info a node informations
|
||||
---@param nodes_to_match table a list of parent nodes to match
|
||||
---@return userdata node one of the nodes to match directly above the given node
|
||||
return function(node_info, nodes_to_match)
|
||||
if not node_info.current then
|
||||
if vim.tbl_contains(nodes_to_match, node_info.root:type()) then
|
||||
|
||||
162
lua/neogen/mark.lua
Normal file
162
lua/neogen/mark.lua
Normal file
@@ -0,0 +1,162 @@
|
||||
---@class Mark
|
||||
---@field started boolean
|
||||
---@field bufnr number
|
||||
---@field winid number
|
||||
---@field index number
|
||||
---@field last_cursor number
|
||||
---@field ids number[]
|
||||
---@field range_id number
|
||||
local mark = {}
|
||||
|
||||
local api = vim.api
|
||||
local ns = api.nvim_create_namespace("neogen")
|
||||
|
||||
local function compare_pos(p1, p2)
|
||||
return p1[1] == p2[1] and p1[2] - p2[2] or p1[1] - p2[1]
|
||||
end
|
||||
|
||||
--- Start marks creation and get useful informations
|
||||
---@private
|
||||
mark.start = function(self)
|
||||
if self.started then
|
||||
mark:stop()
|
||||
end
|
||||
self.bufnr = api.nvim_get_current_buf()
|
||||
self.winid = api.nvim_get_current_win()
|
||||
self.index = 0
|
||||
local pos = api.nvim_win_get_cursor(self.winid)
|
||||
local row, col = unpack(pos)
|
||||
self.last_cursor_id = api.nvim_buf_set_extmark(self.bufnr, ns, row - 1, col, {})
|
||||
self.ids = {}
|
||||
self.range_id = nil
|
||||
self.started = true
|
||||
end
|
||||
|
||||
mark.valid = function(self)
|
||||
return self.bufnr == api.nvim_get_current_buf() and self.winid == api.nvim_get_current_win()
|
||||
end
|
||||
|
||||
--- Get a mark specified with i index
|
||||
---@param i number
|
||||
---@private
|
||||
mark.get_mark = function(self, i)
|
||||
local id = self.ids[i]
|
||||
return api.nvim_buf_get_extmark_by_id(self.bufnr, ns, id, {})
|
||||
end
|
||||
|
||||
--- Add a mark with position
|
||||
---@param pos table Position as line, col
|
||||
---@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, {})
|
||||
table.insert(mark.ids, id)
|
||||
return id
|
||||
end
|
||||
|
||||
--- Get how many marks are created
|
||||
---@return number
|
||||
---@private
|
||||
mark.mark_len = function(self)
|
||||
return #self.ids
|
||||
end
|
||||
|
||||
mark.get_range_mark = function(self)
|
||||
local d = api.nvim_buf_get_extmark_by_id(self.bufnr, ns, self.range_id, {details = true})
|
||||
local row, col, end_row, end_col = d[1], d[2], d[3].end_row, d[3].end_col
|
||||
return row, col, end_row, end_col
|
||||
end
|
||||
|
||||
mark.add_range_mark = function(self, range)
|
||||
local row, col, end_row, end_col = unpack(range)
|
||||
self.range_id = api.nvim_buf_set_extmark(self.bufnr, ns, row, col,
|
||||
{end_row = end_row, end_col = end_col})
|
||||
end
|
||||
|
||||
mark.cursor_in_range = function(self, validated)
|
||||
local ret = validated or self:valid()
|
||||
if ret and self.range_id then
|
||||
local pos = api.nvim_win_get_cursor(self.winid)
|
||||
pos[1] = pos[1] - 1
|
||||
local row, col, end_row, end_col = self:get_range_mark()
|
||||
ret = compare_pos({row, col}, pos) <= 0 and compare_pos({end_row, end_col}, pos) >= 0
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Verify if the marks can be jumpable
|
||||
---@param reverse boolean
|
||||
---@return boolean
|
||||
---@private
|
||||
mark.jumpable = function(self, reverse)
|
||||
if not self.started then
|
||||
return false
|
||||
end
|
||||
local validated = self:valid()
|
||||
if not validated then
|
||||
self:stop()
|
||||
return validated
|
||||
end
|
||||
local ret
|
||||
if reverse then
|
||||
ret = self.index > 0
|
||||
else
|
||||
ret = #self.ids >= self.index
|
||||
end
|
||||
if ret then
|
||||
ret = self:cursor_in_range(true)
|
||||
end
|
||||
|
||||
if not ret then
|
||||
self:jump_last_cursor(true)
|
||||
self:stop()
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Jump to next/previous mark if possible
|
||||
---@param reverse boolean
|
||||
---@private
|
||||
mark.jump = function(self, reverse)
|
||||
if self.started then
|
||||
self.index = reverse and self.index - 1 or self.index + 1
|
||||
end
|
||||
if mark:jumpable(reverse) then
|
||||
local line, row = unpack(self:get_mark(self.index))
|
||||
api.nvim_win_set_cursor(self.winid, {line + 1, row})
|
||||
end
|
||||
end
|
||||
|
||||
mark.jump_last_cursor = function(self, validated)
|
||||
if self:cursor_in_range(validated) then
|
||||
local winid = self.winid
|
||||
local pos = api.nvim_buf_get_extmark_by_id(self.bufnr, ns, self.last_cursor_id, {})
|
||||
local line, col = unpack(pos)
|
||||
api.nvim_win_set_cursor(winid, {line + 1, col})
|
||||
end
|
||||
end
|
||||
|
||||
--- Clear marks and stop jumping ability
|
||||
---@private
|
||||
mark.stop = function(self)
|
||||
local bufnr = self.bufnr
|
||||
if bufnr and bufnr > 0 and api.nvim_buf_is_valid(bufnr) then
|
||||
for _, id in ipairs(self.ids) do
|
||||
api.nvim_buf_del_extmark(bufnr, ns, id)
|
||||
end
|
||||
api.nvim_buf_del_extmark(bufnr, ns, self.last_cursor_id)
|
||||
if self.range_id then
|
||||
api.nvim_buf_del_extmark(bufnr, ns, self.range_id)
|
||||
end
|
||||
end
|
||||
self.bufnr = nil
|
||||
self.winid = nil
|
||||
self.index = nil
|
||||
self.last_cursor_id = nil
|
||||
self.started = false
|
||||
self.ids = {}
|
||||
self.range_id = nil
|
||||
end
|
||||
|
||||
return mark
|
||||
@@ -23,7 +23,7 @@ local autocmd = {}
|
||||
|
||||
autocmd.events = {}
|
||||
|
||||
---Subscribe autocmd
|
||||
--- Subscribe autocmd
|
||||
---@param event string
|
||||
---@param callback function
|
||||
---@return function
|
||||
@@ -40,7 +40,7 @@ autocmd.subscribe = function(event, callback)
|
||||
end
|
||||
end
|
||||
|
||||
---Emit autocmd
|
||||
--- Emit autocmd
|
||||
---@param event string
|
||||
autocmd.emit = function(event)
|
||||
autocmd.events[event] = autocmd.events[event] or {}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
cursor = {}
|
||||
|
||||
local neogen_ns = vim.api.nvim_create_namespace("neogen")
|
||||
local neogen_virt_text_ns = vim.api.nvim_create_namespace("neogen_virt_text")
|
||||
local current_position = 1
|
||||
|
||||
--- Wrapper around set_extmark with 1-based numbering for `line` and `col`, and returns the id of the created extmark
|
||||
--- @param line number
|
||||
--- @param col number
|
||||
--- @return number
|
||||
cursor.create = function(line, col)
|
||||
current_position = 1
|
||||
local new_col = col == 0 and 0 or col - 1
|
||||
return vim.api.nvim_buf_set_extmark(0, neogen_ns, line - 1, new_col, {})
|
||||
end
|
||||
|
||||
--- Find next created extmark and goes to it.
|
||||
--- It removes the extmark afterwards.
|
||||
--- First jumpable extmark is the one after the extmarks responsible of start/end of annotation
|
||||
cursor.go_next_extmark = function()
|
||||
local extm_list = vim.api.nvim_buf_get_extmarks(0, neogen_ns, 0, -1, {})
|
||||
local position = current_position + 1
|
||||
table.sort(extm_list, function(a, b)
|
||||
return a[1] < b[1]
|
||||
end)
|
||||
|
||||
if #extm_list ~= 2 then
|
||||
local pos = { extm_list[position][2] + 1, extm_list[position][3] }
|
||||
vim.api.nvim_win_set_cursor(0, pos)
|
||||
current_position = current_position + 1
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Goes to next extmark and start insert mode.
|
||||
--- If `opts.first_time` is supplied, will try to go to normal mode before going to extmark
|
||||
--- @param opts table
|
||||
cursor.jump = function(opts)
|
||||
opts = opts or {}
|
||||
|
||||
-- This is weird, the first time nvim goes to insert is not the same as when i'm already on insert mode
|
||||
-- that's why i put a first_time flag
|
||||
if opts.first_time then
|
||||
vim.api.nvim_command("startinsert")
|
||||
end
|
||||
|
||||
if cursor.go_next_extmark() then
|
||||
vim.api.nvim_command("startinsert")
|
||||
end
|
||||
end
|
||||
|
||||
cursor.jump_prev = function()
|
||||
local marks = vim.api.nvim_buf_get_extmarks(0, neogen_ns, 0, -1, {})
|
||||
table.sort(marks, function(a, b)
|
||||
return a[1] < b[1]
|
||||
end)
|
||||
|
||||
if #marks == 2 then
|
||||
return false
|
||||
end
|
||||
|
||||
local position = current_position - 1
|
||||
local pos = { marks[position][2] + 1, marks[position][3] }
|
||||
vim.api.nvim_win_set_cursor(0, pos)
|
||||
current_position = current_position - 1
|
||||
return true
|
||||
end
|
||||
|
||||
--- Delete all active extmarks
|
||||
cursor.del_extmarks = function()
|
||||
local extmarks = vim.api.nvim_buf_get_extmarks(0, neogen_ns, 0, -1, {})
|
||||
for _, v in pairs(extmarks) do
|
||||
vim.api.nvim_buf_del_extmark(0, neogen_ns, v[1])
|
||||
end
|
||||
end
|
||||
|
||||
--- Checks if there are still possible jump positions to perform
|
||||
--- Verifies if the cursor is in the last annotated part
|
||||
cursor.jumpable = function(reverse)
|
||||
local extm_list = vim.api.nvim_buf_get_extmarks(0, neogen_ns, 0, -1, {})
|
||||
if #extm_list == 0 then
|
||||
return false
|
||||
end
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
if cursor[1] > extm_list[#extm_list][2] or cursor[1] < extm_list[1][2] then
|
||||
return false
|
||||
end
|
||||
|
||||
-- We arrive at the end, we can't jump anymore
|
||||
if current_position == #extm_list then
|
||||
return false
|
||||
end
|
||||
|
||||
if reverse == -1 then
|
||||
-- Check first boundaries
|
||||
if current_position == 2 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if #extm_list > 2 then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return cursor
|
||||
@@ -16,7 +16,7 @@ return {
|
||||
local get_text = function(node)
|
||||
return ts_utils.get_node_text(node)[1]
|
||||
end
|
||||
if opts.type == true then
|
||||
if opts.type then
|
||||
result[k] = vim.tbl_map(get_type, v)
|
||||
else
|
||||
result[k] = vim.tbl_map(get_text, v)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local config = require("neogen.config")
|
||||
return {
|
||||
notify = function(msg, log_level)
|
||||
vim.notify(msg, log_level, { title = "Neogen" })
|
||||
vim.notify(msg, log_level, {title = "Neogen"})
|
||||
end,
|
||||
|
||||
--- Generates a list of possible types in the current language
|
||||
@@ -10,7 +11,7 @@ return {
|
||||
return {}
|
||||
end
|
||||
|
||||
local language = neogen.configuration.languages[vim.bo.filetype]
|
||||
local language = config.get().languages[vim.bo.filetype]
|
||||
|
||||
if not language or not language.parent then
|
||||
return {}
|
||||
@@ -18,19 +19,8 @@ return {
|
||||
|
||||
return vim.tbl_keys(language.parent)
|
||||
end,
|
||||
|
||||
switch_language = function()
|
||||
local filetype = vim.bo.filetype
|
||||
local ok, ft_configuration = pcall(require, "neogen.configurations." .. filetype)
|
||||
|
||||
if not ok then
|
||||
return
|
||||
end
|
||||
|
||||
neogen.configuration.languages[filetype] = vim.tbl_deep_extend(
|
||||
"keep",
|
||||
neogen.user_configuration.languages and neogen.user_configuration.languages[filetype] or {},
|
||||
ft_configuration
|
||||
)
|
||||
end,
|
||||
split = function(s, sep, plain)
|
||||
return vim.fn.has("nvim-0.6") == 1 and vim.split(s, sep, {plain = plain}) or
|
||||
vim.split(s, sep, plain)
|
||||
end
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
local helpers = require("neogen.utilities.helpers")
|
||||
return {
|
||||
--- Get a list of child nodes that match the provided node name
|
||||
--- @param _ any
|
||||
--- @param parent userdata the parent's node
|
||||
--- @param node_name string|nil the node type to search for (if multiple childrens, separate each one with "|")
|
||||
--- @param node_type string|nil the node type to search for (if multiple childrens, separate each one with "|")
|
||||
--- @return table a table of nodes that matched the name
|
||||
matching_child_nodes = function(_, parent, node_name)
|
||||
matching_child_nodes = function(_, parent, node_type)
|
||||
local results = {}
|
||||
-- Return all nodes if there is no node name
|
||||
if node_name == nil then
|
||||
if not node_type then
|
||||
for child in parent:iter_children() do
|
||||
if child:named() then
|
||||
table.insert(results, child)
|
||||
end
|
||||
end
|
||||
else
|
||||
local split = vim.split(node_name, "|", true)
|
||||
local types = helpers.split(node_type, "|", true)
|
||||
for child in parent:iter_children() do
|
||||
if vim.tbl_contains(split, child:type()) then
|
||||
if vim.tbl_contains(types, child:type()) then
|
||||
table.insert(results, child)
|
||||
end
|
||||
end
|
||||
@@ -45,8 +46,7 @@ return {
|
||||
break
|
||||
end
|
||||
|
||||
local found = self:recursive_find(child, node_name, { results = results })
|
||||
vim.tbl_deep_extend("keep", results, found)
|
||||
self:recursive_find(child, node_name, {results = results})
|
||||
end
|
||||
|
||||
return results
|
||||
@@ -63,41 +63,36 @@ return {
|
||||
result = result or {}
|
||||
|
||||
for _, subtree in pairs(tree) do
|
||||
if subtree.retrieve and not vim.tbl_contains({ "all", "first" }, subtree.retrieve) then
|
||||
assert(false, "Supported nodes matching: all|first")
|
||||
return
|
||||
end
|
||||
assert(not subtree.retrieve or vim.tbl_contains({"all", "first"}, subtree.retrieve),
|
||||
"Supported nodes matching: all|first")
|
||||
|
||||
-- Match all child nodes of the parent node
|
||||
local matched = self:matching_child_nodes(parent, subtree.node_type)
|
||||
|
||||
-- Only keep the node with custom position
|
||||
if subtree.retrieve == nil then
|
||||
if type(subtree.position) == "number" then
|
||||
matched = { matched[subtree.position] }
|
||||
else
|
||||
assert(false, "please require position if retrieve is nil")
|
||||
end
|
||||
if not subtree.retrieve then
|
||||
assert(type(subtree.position) == "number",
|
||||
"please require position if retrieve is nil")
|
||||
matched = {matched[subtree.position]}
|
||||
end
|
||||
|
||||
if subtree.recursive then
|
||||
local first = subtree.retrieve == "first"
|
||||
matched = self:recursive_find(parent, subtree.node_type, { first = first })
|
||||
matched = self:recursive_find(parent, subtree.node_type, {first = first})
|
||||
end
|
||||
|
||||
for _, child in pairs(matched) do
|
||||
if subtree.extract == true then
|
||||
if subtree.extract then
|
||||
local name = subtree.as and subtree.as or (subtree.node_type or "_")
|
||||
if result[name] == nil then
|
||||
if not result[name] then
|
||||
result[name] = {}
|
||||
end
|
||||
table.insert(result[name], child)
|
||||
else
|
||||
local nodes = self:matching_nodes_from(child, subtree.subtree, result)
|
||||
result = vim.tbl_deep_extend("keep", result, nodes)
|
||||
self:matching_nodes_from(child, subtree.subtree, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end,
|
||||
end
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ if _G.MiniDoc == nil then
|
||||
minidoc.setup()
|
||||
end
|
||||
|
||||
minidoc.generate({ "lua/neogen/init.lua", "lua/neogen/utilities/template.lua" }, nil, nil)
|
||||
minidoc.generate({ "lua/neogen/init.lua", "lua/neogen/template.lua" }, nil, nil)
|
||||
|
||||
Reference in New Issue
Block a user