add autorename

This commit is contained in:
windwp 2021-03-08 19:19:01 +07:00
parent f5eb38a4ed
commit 77647177ae
11 changed files with 432 additions and 84 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
test/

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
test:
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}"

View File

@ -1,7 +1,10 @@
# nvim-ts-closetag
Use treesitter to autoclose xml tag
# nvim-ts-autotag
Use treesitter to autoclose and autorename xml tag
It work with tsx,vue,svelte. it use treesitter then it only close and rename the tag match with your current cursor.
it work with tsx,vue,svelte
## Usage
@ -12,6 +15,7 @@ Before Input After
------------------------------------
```
## Setup
Neovim 0.5 with and nvim-treesitter to work

202
lua/nvim-ts-autotag.lua Normal file
View File

@ -0,0 +1,202 @@
local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils')
local M = {}
M.tbl_filetypes = {
'html', 'xml', 'javascript', 'javascriptreact', 'typescriptreact', 'svelte', 'vue', 'php'
}
M.tbl_skipTag = {
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'slot',
'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr','menuitem'
}
M.test = false
M.setup = function (opts)
opts = opts or {}
M.tbl_filetypes = opts.filetypes or M.tbl_filetypes
M.tbl_skipTag = opts.skip_tag or M.tbl_skipTag
vim.cmd[[augroup nvim_ts_xmltag]]
vim.cmd[[autocmd!]]
vim.cmd[[autocmd FileType * call v:lua.require('nvim-ts-autotag').on_file_type()]]
vim.cmd[[augroup end]]
end
local function is_in_table(tbl, val)
for _, value in pairs(tbl) do
if string.match(val, value) then return true end
end
return false
end
local function isJsX()
if is_in_table({'typescriptreact', 'javascriptreact'}, vim.bo.filetype) then
return true
end
return false
end
M.on_file_type = function ()
if is_in_table(M.tbl_filetypes,vim.bo.filetype) then
vim.cmd[[inoremap <silent> <buffer> > ><c-o>:lua require('nvim-ts-autotag').closeTag()<CR>]]
local bufnr = vim.api.nvim_get_current_buf()
vim.cmd("augroup nvim_ts_xmltag_" .. bufnr)
vim.cmd[[autocmd!]]
vim.cmd[[autocmd InsertLeave <buffer> call v:lua.require('nvim-ts-autotag').renameTag() ]]
vim.cmd[[augroup end]]
end
end
local function find_child_match(target, pattern)
for node in target:iter_children() do
local node_type = node:type()
if node_type ~= nil and node_type == pattern then
return node
end
end
end
local function find_parent_match(target, pattern,max_depth)
max_depth = max_depth or 20
local cur_depth = 0
local cur_node = target
while cur_node ~= nil do
local node_type = cur_node:type()
if node_type ~= nil and node_type== pattern then
return cur_node
elseif cur_depth < max_depth then
cur_depth = cur_depth + 1
cur_node = cur_node:parent()
else
return nil
end
end
return nil end
local function get_tag_name(node)
local tag_name = nil
if node ~=nil then
tag_name = ts_utils.get_node_text(node)[1]
end
return tag_name
end
local function find_tag_node(start_tag_pattern, name_tag_pattern)
local cur_node = ts_utils.get_node_at_cursor()
local start_tag_node = find_parent_match(cur_node, start_tag_pattern)
if(M.test and start_tag_node == nil) then
start_tag_node = find_child_match(cur_node, start_tag_pattern)
end
if start_tag_node== nil then return nil end
local tbl_name_pattern = vim.split(name_tag_pattern, '>')
local name_node = start_tag_node
for _, pattern in pairs(tbl_name_pattern) do
name_node = find_child_match(name_node, pattern)
end
return name_node
end
local function find_close_tag_node(close_tag_pattern, name_tag_pattern, cur_node)
cur_node = cur_node or ts_utils.get_node_at_cursor()
local close_tag_node = find_child_match(cur_node, close_tag_pattern)
if close_tag_node== nil then return nil end
local tbl_name_pattern = vim.split(name_tag_pattern, '>')
local name_node = close_tag_node
for _, pattern in pairs(tbl_name_pattern) do
name_node = find_child_match(name_node, pattern)
end
return name_node
end
M.closeTag = function ()
local start_tag_pattern = 'start_tag'
local name_tag_pattern = 'tag_name'
if isJsX() then
start_tag_pattern = 'jsx_element'
name_tag_pattern = 'jsx_opening_element>identifier'
end
local tag_node = find_tag_node(start_tag_pattern, name_tag_pattern)
local tag_name = get_tag_name(tag_node)
if tag_name ~= nil and not is_in_table(M.tbl_skipTag,tag_name) then
vim.cmd(string.format([[normal! a</%s>]],tag_name))
vim.cmd[[normal! T>]]
end
end
local function replaceTextNode(node, tag_name)
if node == nil then return end
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))
vim.fn.setline(start_row + 1,{newline})
end
end
local function checkStartTag()
local start_tag_pattern = 'start_tag'
local start_name_tag_pattern = 'tag_name'
local close_tag_pattern = 'erroneous_end_tag'
local close_name_tag_pattern = 'erroneous_end_tag_name'
local element_tag = 'element'
if isJsX() then
start_tag_pattern = 'jsx_opening_element'
start_name_tag_pattern = 'identifier'
close_tag_pattern = 'jsx_closing_element'
close_name_tag_pattern = 'identifier'
element_tag = 'jsx_element'
end
local tag_node = find_tag_node(start_tag_pattern, start_name_tag_pattern)
if tag_node == nil then return end
local tag_name = get_tag_name(tag_node)
tag_node = find_parent_match(tag_node, element_tag, 2)
if tag_node == nil then return end
local close_tag_node = find_close_tag_node(close_tag_pattern, close_name_tag_pattern, tag_node)
if close_tag_node ~= nil then
local close_tag_name = get_tag_name(close_tag_node)
if tag_name ~=close_tag_name then
replaceTextNode(close_tag_node, tag_name)
end
else
close_tag_node = find_child_match(tag_node,'ERROR')
if close_tag_node ~=nil then
local close_tag_name = get_tag_name(close_tag_node)
if close_tag_name=='</>' then
replaceTextNode(close_tag_node, "</"..tag_name..">")
end
end
end
end
local function checkEndTag()
local end_tag_pattern = 'erroneous_end_tag'
local end_name_tag_pattern = 'erroneous_end_tag_name'
local start_tag_pattern = 'start_tag'
local start_name_tag_pattern = 'tag_name'
local element_tag = 'element'
if isJsX() then
end_tag_pattern = 'jsx_closing_element'
end_name_tag_pattern = 'identifier'
start_tag_pattern = 'jsx_opening_element'
start_name_tag_pattern = 'identifier'
element_tag = 'jsx_element'
end
local tag_node = find_tag_node(end_tag_pattern, end_name_tag_pattern)
if tag_node == nil then return end
local tag_name = get_tag_name(tag_node)
tag_node = find_parent_match(tag_node, element_tag, 2)
if tag_node == nil then return end
local start_tag_node = find_close_tag_node(start_tag_pattern, start_name_tag_pattern, tag_node)
if start_tag_node ~= nil then
local start_tag_name = get_tag_name(start_tag_node)
if tag_name ~=start_tag_name then
replaceTextNode(start_tag_node, tag_name)
end
end
end
M.renameTag = function ()
checkStartTag()
checkEndTag()
end
return M

View File

@ -1,80 +0,0 @@
local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils')
local M= {}
M.tbl_filetypes = {
'html', 'xml', 'javascript', 'javascriptreact', 'typescriptreact', 'svelte', 'vue'
}
M.tbl_skipTag = {
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'slot',
'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr','menuitem'
}
M.setup = function (opts)
opts = opts or {}
M.tbl_filetypes = opts.filetypes or M.tbl_filetypes
M.tbl_skipTag = opts.skip_tag or M.tbl_skipTag
vim.cmd(string.format([[
autocmd FileType %s inoremap <silent> <buffer> > ><c-o>:lua require('nvim-ts-closetag').closeTag()<cr>
]],table.concat(M.tbl_filetypes,',')))
end
local function is_in_table(tbl, val)
for _, value in pairs(tbl) do
if string.match(val, value) then return true end
end
return false
end
local function find_child_match(target, pattern)
for node in target:iter_children() do
local node_type = node:type()
if node_type ~=nil and string.match(node_type,pattern) then
return node
end
end
end
local function find_parent_match(target, pattern)
local cur_node = target
while cur_node ~= nil do
local node_type = cur_node:type()
if node_type ~= nil and string.match(node_type, pattern) then
return cur_node
else
cur_node = cur_node:parent()
end
end
end
local function find_tag_name(start_tag_pattern, name_tag_pattern)
local cur_node = ts_utils.get_node_at_cursor()
local start_tag_node = find_parent_match(cur_node,start_tag_pattern)
if start_tag_node== nil then return nil end
local tag_name = nil
local tbl_name_pattern = vim.split(name_tag_pattern, '>')
local name_node = start_tag_node
for _, pattern in pairs(tbl_name_pattern) do
name_node = find_child_match(name_node, pattern)
end
if name_node ~=nil then
tag_name = ts_utils.get_node_text(name_node)[1]
end
return tag_name
end
M.closeTag = function ()
if is_in_table(M.tbl_filetypes,vim.bo.filetype) then
local start_tag_pattern = 'start_tag'
local name_tag_pattern = 'tag_name'
if is_in_table({'typescriptreact', 'javascriptreact'}, vim.bo.filetype) then
start_tag_pattern = 'jsx_element'
name_tag_pattern = 'jsx_opening_element>identifier'
end
local tag_name = find_tag_name(start_tag_pattern, name_tag_pattern)
if tag_name ~= nil and not is_in_table(M.tbl_skipTag,tag_name) then
vim.cmd(string.format([[normal! a</%s>]],tag_name))
vim.cmd[[normal! T>]]
end
end
end
return M

12
sample/index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width= device-width , initial-scale= 1.0 ">
<title> Document </title>
</head>
<body>
</body>
</html>

27
sample/index.php Normal file
View File

@ -0,0 +1,27 @@
<?php include("includes/a_config.php");?>
<!DOCTYPE html>
<html>
<head>
<?php include("includes/head-tag-contents.php");?>
</head>
<body>
<?php include("includes/design-top.php");?>
<?php include("includes/navigation.php");?>
<div class="container" id="main-content">
<h2>About Us</h2>
<p>"About Us" conten goes here. I'll stick with teh "lorem ipsum" as well, so that the footer isn't immediately following this text.</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
<?php include("includes/footer.php");?>
</body>
</html>

17
sample/index.tsx Normal file
View File

@ -0,0 +1,17 @@
import React, { useCallback, useEffect } from 'react'
const SamplePage: React.FC = () => {
const [state, setstate] = useState(initialState)
useEffect(() => {
},[])
return (
<div className="h-full">
</div>
)
}
export default SamplePage

25
sample/index.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<div class="aaa">
</div>
</template>
<script>
export default {
name: 'Sample',
data() {
},
props: {
},
mounted() {
}
};
</script>
<style scoped>
.sample{
width:`100px`;
}
</style>

24
tests/minimal.vim Normal file
View File

@ -0,0 +1,24 @@
set rtp +=.
set rtp +=~/.vim/autoload/plenary.nvim/
set rtp +=~/.vim/autoload/nvim-treesitter/
runtime! plugin/plenary.vim
runtime! plugin/nvim-treesitter.vim
set noswapfile
set nobackup
filetype indent off
set nowritebackup
set noautoindent
set nocindent
set nosmartindent
set indentexpr=
lua << EOF
local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils')
_G.T=ts_utils
require("plenary/busted")
require("nvim-ts-autotag").setup()
EOF

116
tests/tag_spec.lua Normal file
View File

@ -0,0 +1,116 @@
local ts = require 'nvim-treesitter.configs'
local helpers = {}
ts.setup {
ensure_installed = 'maintained',
highlight = {enable = true},
indent = {
enable = false
}
}
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('i' .. text, 'x')
end
local data = {
{
name = "html auto close tag" ,
filepath = './sample/index.html',
filetype = "html",
linenr = 10,
key = [[>]],
before = [[<div| ]],
after = [[<div> </div>]]
},
{
name = "html not close on input tag" ,
filepath = './sample/index.html',
filetype = "html",
linenr = 10,
key = [[>]],
before = [[<input| ]],
after = [[<input>| ]]
},
{
name = "typescriptreact auto close tag" ,
filepath = './sample/index.tsx',
filetype = "typescriptreact",
linenr = 12,
key = [[>]],
before = [[<Img| ]],
after = [[<Img>| </Img>]]
},
{
name = "typescriptreact not close on script" ,
filepath = './sample/index.tsx',
filetype = "typescriptreact",
linenr = 6,
key = [[>]],
before = [[const data:Array<string| ]],
after = [[const data:Array<string> ]]
},
{
name = "vue auto close tag" ,
filepath = './sample/index.vue',
filetype = "vue",
linenr = 4,
key = [[>]],
before = [[<Img| ]],
after = [[<Img>| </Img>]]
},
{
name = "vue not close on script",
filepath = './sample/index.vue',
filetype = "vue",
linenr = 12,
key = [[>]],
before = [[const data:Array<string| ]],
after = [[const data:Array<string> ]]
},
}
local run_data = {}
for _, value in pairs(data) do
if value.only == true then
table.insert(run_data, value)
break
end
end
if #run_data == 0 then run_data = data end
local autotag = require('nvim-ts-autotag')
autotag.test=true
local function Test(test_data)
for _, value in pairs(test_data) do
it("test "..value.name, function()
local before = string.gsub(value.before , '%|' , "")
local after = string.gsub(value.after , '%|' , "")
local p_before = string.find(value.before , '%|')
local p_after = string.find(value.after , '%|')
local line =value.linenr
vim.bo.filetype = value.filetype
if vim.fn.filereadable(vim.fn.expand(value.filepath)) == 1 then
vim.cmd(":e " .. value.filepath)
vim.fn.setline(line , before)
vim.fn.cursor(line, p_before)
helpers.insert(value.key)
helpers.feed("<esc>")
local result = vim.fn.getline(line)
eq(after, result , "\n\n text error: " .. value.name .. "\n")
vim.cmd(":bd!")
else
eq(false, true, "\n\n file not exist " .. value.filepath .. "\n")
end
end)
end
end
describe('autotag ', function()
Test(run_data)
end)