diff --git a/lua/nvim-ts-autotag/internal.lua b/lua/nvim-ts-autotag/internal.lua index 3b3e0ba..6c47478 100644 --- a/lua/nvim-ts-autotag/internal.lua +++ b/lua/nvim-ts-autotag/internal.lua @@ -1,8 +1,8 @@ -local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') -local configs = require('nvim-treesitter.configs') -local parsers = require('nvim-treesitter.parsers') -local log = require('nvim-ts-autotag._log') -local utils = require('nvim-ts-autotag.utils') +local _, ts_utils = pcall(require, "nvim-treesitter.ts_utils") +local configs = require("nvim-treesitter.configs") +local parsers = require("nvim-treesitter.parsers") +local log = require("nvim-ts-autotag._log") +local utils = require("nvim-ts-autotag.utils") local M = {} @@ -23,7 +23,7 @@ M.tbl_skipTag = { 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'menuitem' } -local ERROR_TAG = 'ERROR' +local ERROR_TAG = "ERROR" -- stylua: ignore local HTML_TAG = { @@ -99,6 +99,7 @@ local all_tag = { } M.enable_rename = true M.enable_close = true +M.enable_close_on_slash = true M.setup = function(opts) opts = opts or {} @@ -148,10 +149,10 @@ local function is_in_template_tag() local current_node = cursor_node while not (has_element and has_template_string) and current_node do - if not has_element and current_node:type() == 'element' then + if not has_element and current_node:type() == "element" then has_element = true end - if not has_template_string and current_node:type() == 'template_string' then + if not has_template_string and current_node:type() == "template_string" then has_template_string = true end current_node = current_node:parent() @@ -175,14 +176,11 @@ local function find_child_match(opts) if target == nil or pattern == nil then return nil end - local tbl_pattern = vim.split(pattern, '|') + local tbl_pattern = vim.split(pattern, "|") for _, ptn in pairs(tbl_pattern) do for node in target:iter_children() do local node_type = node:type() - if node_type ~= nil - and node_type == ptn - and not is_in_table(skip_tag_pattern, node_type) - then + if node_type ~= nil and node_type == ptn and not is_in_table(skip_tag_pattern, node_type) then return node end end @@ -197,7 +195,7 @@ local function find_parent_match(opts) if target == nil or pattern == nil then return nil end - local tbl_pattern = vim.split(pattern, '|') + local tbl_pattern = vim.split(pattern, "|") for _, ptn in pairs(tbl_pattern) do local cur_node = target local cur_depth = 0 @@ -224,7 +222,7 @@ local function get_tag_name(node) if node ~= nil then tag_name = utils.get_node_text(node)[1] if tag_name and #tag_name > 3 then - tag_name = tag_name:gsub('', ''):gsub('>', ''):gsub('<', '') + tag_name = tag_name:gsub("", ""):gsub(">", ""):gsub("<", "") end end return tag_name @@ -255,8 +253,8 @@ local function find_tag_node(opt) end local name_node = node local tbl_name_pattern = {} - if string.match(name_tag_pattern, '%|') then - tbl_name_pattern = vim.split(name_tag_pattern, '|') + if string.match(name_tag_pattern, "%|") then + tbl_name_pattern = vim.split(name_tag_pattern, "|") for _, pattern in pairs(tbl_name_pattern) do name_node = find_child_match({ target = node, @@ -268,7 +266,7 @@ local function find_tag_node(opt) end end - tbl_name_pattern = vim.split(name_tag_pattern, '>') + tbl_name_pattern = vim.split(name_tag_pattern, ">") for _, pattern in pairs(tbl_name_pattern) do name_node = find_child_match({ target = name_node, @@ -288,12 +286,52 @@ local function find_child_tag_node(opt) return find_tag_node(opt) end -local function check_close_tag() +local function find_start_tag(current) + local ts_tag = get_ts_tag() + if not ts_tag then + return nil + end + + if current:type() ~= "ERROR" then + return nil + end + + local target = nil + + target = find_child_match({ + target = current:parent(), + pattern = ts_tag.start_tag_pattern, + }) + + if target ~= nil then + return target + end + + target = find_child_match({ + target = current, + pattern = ts_tag.start_tag_pattern, + }) + + return target +end + +local function check_close_tag(close_slash_tag) local ts_tag = get_ts_tag() if not ts_tag then return false end + + local target = nil + + if close_slash_tag then + -- Find start node from non closed tag + local current = ts_utils.get_node_at_cursor() + + target = find_start_tag(current) + end + local tag_node = find_tag_node({ + target = target, tag_pattern = ts_tag.start_tag_pattern, name_tag_pattern = ts_tag.start_name_tag_pattern, skip_tag_pattern = ts_tag.skip_tag_pattern, @@ -318,9 +356,7 @@ local function check_close_tag() if close_tag_node ~= nil then local start_row = tag_node:range() local close_start_row = close_tag_node:range() - if start_row == close_start_row - and tag_name == get_tag_name(close_tag_node) - then + if start_row == close_start_row and tag_name == get_tag_name(close_tag_node) then return false end end @@ -343,7 +379,18 @@ M.close_tag = function() end end - +M.close_slash_tag = function() + local buf_parser = parsers.get_parser() + if not buf_parser then + return + end + buf_parser:parse() + local result, tag_name = check_close_tag(true) + if result == true and tag_name ~= nil then + vim.api.nvim_put({ string.format("%s>", tag_name) }, "", true, true) + vim.cmd([[normal! F>]]) + end +end local function replace_text_node(node, tag_name) if node == nil then @@ -352,9 +399,7 @@ local function replace_text_node(node, tag_name) local start_row, start_col, end_row, end_col = node:range() if start_row == end_row then local line = vim.fn.getline(start_row + 1) - local newline = line:sub(0, start_col) - .. tag_name - .. line:sub(end_col + 1, string.len(line)) + local newline = line:sub(0, start_col) .. tag_name .. line:sub(end_col + 1, string.len(line)) vim.fn.setline(start_row + 1, { newline }) end end @@ -364,9 +409,7 @@ local function validate_tag_regex(node, start_regex, end_regex) return false end local texts = utils.get_node_text(node) - if string.match(texts[1], start_regex) - and string.match(texts[#texts], end_regex) - then + if string.match(texts[1], start_regex) and string.match(texts[#texts], end_regex) then return true end return false @@ -377,11 +420,11 @@ end -- end local function validate_start_tag(node) - return validate_tag_regex(node, '^%<%w', '%>$') + return validate_tag_regex(node, "^%<%w", "%>$") end local function validate_close_tag(node) - return validate_tag_regex(node, '^%<%/%w', '%>$') + return validate_tag_regex(node, "^%<%/%w", "%>$") end local function rename_start_tag() @@ -392,7 +435,7 @@ local function rename_start_tag() local tag_node = find_tag_node({ tag_pattern = ts_tag.start_tag_pattern, name_tag_pattern = ts_tag.start_name_tag_pattern, - skip_tag_pattern = ts_tag.skip_tag_pattern + skip_tag_pattern = ts_tag.skip_tag_pattern, }) if tag_node == nil then @@ -407,7 +450,7 @@ local function rename_start_tag() tag_node = find_parent_match({ target = parent_node, - pattern = ts_tag.element_tag .. '|' .. ERROR_TAG, + pattern = ts_tag.element_tag .. "|" .. ERROR_TAG, max_depth = 2, }) @@ -435,7 +478,7 @@ local function rename_start_tag() pattern = ERROR_TAG, }) if error_node == nil then - log.debug('do replace') + log.debug("do replace") local close_tag_name = get_tag_name(close_tag_node) log.debug(close_tag_name) @@ -443,10 +486,8 @@ local function rename_start_tag() if close_tag_node ~= nil and tag_node ~= nil then local tag_parent = get_tag_name(tag_node:parent()) -- log.debug(utils.dump_node(tag_node:parent())) - if tag_parent == close_tag_name - and not utils.verify_node(tag_node:parent(), close_tag_name) - then - log.debug('skip it have same') + if tag_parent == close_tag_name and not utils.verify_node(tag_node:parent(), close_tag_name) then + log.debug("skip it have same") return end end @@ -457,12 +498,12 @@ local function rename_start_tag() else local error_tag = get_tag_name(error_node) -- tsx node is empty - if error_tag == '>' then - replace_text_node(error_node, '' .. tag_name .. '>') + if error_tag == ">" then + replace_text_node(error_node, "" .. tag_name .. ">") end -- have both parent node and child node is error if close_tag_node:type() == ERROR_TAG then - replace_text_node(error_node, '' .. tag_name .. '>') + replace_text_node(error_node, "" .. tag_name .. ">") end end end @@ -483,8 +524,7 @@ local function rename_end_tag() end -- we check if that node text match > - if not (validate_close_tag(tag_node:parent()) or validate_close_tag(tag_node)) - then + if not (validate_close_tag(tag_node:parent()) or validate_close_tag(tag_node)) then return end @@ -518,7 +558,7 @@ local function validate_rename() local line = vim.api.nvim_get_current_line() local char = line:sub(cursor[2] + 1, cursor[2] + 1) -- only rename when last character is a word - if string.match(char, '%w') then + if string.match(char, "%w") then return true end return false @@ -534,27 +574,40 @@ end M.attach = function(bufnr, lang) M.lang = lang - local config = configs.get_module('autotag') + local config = configs.get_module("autotag") M.setup(config) if is_in_table(M.tbl_filetypes, vim.bo.filetype) then setup_ts_tag() if M.enable_close == true then - vim.api.nvim_buf_set_keymap(bufnr or 0, 'i', ">", ">", { + vim.api.nvim_buf_set_keymap(bufnr or 0, "i", ">", ">", { noremap = true, silent = true, callback = function() local row, col = unpack(vim.api.nvim_win_get_cursor(0)) - vim.api.nvim_buf_set_text(bufnr or 0, row - 1, col, row - 1, col, { '>' }) + vim.api.nvim_buf_set_text(bufnr or 0, row - 1, col, row - 1, col, { ">" }) M.close_tag() vim.api.nvim_win_set_cursor(0, { row, col + 1 }) - end + end, + }) + end + if M.enable_close_on_slash == true then + vim.api.nvim_buf_set_keymap(bufnr or 0, "i", "/", "/", { + noremap = true, + silent = true, + callback = function() + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + vim.api.nvim_buf_set_text(bufnr or 0, row - 1, col, row - 1, col, { "/" }) + M.close_slash_tag() + local new_row, new_col = unpack(vim.api.nvim_win_get_cursor(0)) + vim.api.nvim_win_set_cursor(0, { new_row, new_col + 1 }) + end, }) end if M.enable_rename == true then bufnr = bufnr or vim.api.nvim_get_current_buf() - vim.api.nvim_create_autocmd('InsertLeave', { + vim.api.nvim_create_autocmd("InsertLeave", { buffer = bufnr, - callback = M.rename_tag + callback = M.rename_tag, }) end end diff --git a/tests/close_slash_tag_spec.lua b/tests/close_slash_tag_spec.lua new file mode 100644 index 0000000..56d8ded --- /dev/null +++ b/tests/close_slash_tag_spec.lua @@ -0,0 +1,198 @@ +if not _G.test_close then + return +end + +local ts = require('nvim-treesitter.configs') +local helpers = {} +ts.setup({ + ensure_installed = _G.ts_filetypes, + highlight = { enable = true }, +}) +local eq = assert.are.same + +function helpers.feed(text, feed_opts) + feed_opts = feed_opts or 'n' + local to_feed = vim.api.nvim_replace_termcodes(text, true, false, true) + vim.api.nvim_feedkeys(to_feed, feed_opts, true) +end + +function helpers.insert(text) + helpers.feed('a' .. text, 'x') +end + +local data = { + { + name = '1 html close tag after inputting /', + filepath = './sample/index.html', + filetype = 'html', + linenr = 10, + key = [[/]], + before = [[