mirror of
https://github.com/windwp/nvim-ts-autotag.git
synced 2024-12-29 13:39:15 -06:00
add autorename
This commit is contained in:
parent
f5eb38a4ed
commit
77647177ae
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
||||
test/
|
2
Makefile
Normal file
2
Makefile
Normal file
@ -0,0 +1,2 @@
|
||||
test:
|
||||
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}"
|
10
README.md
10
README.md
@ -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
202
lua/nvim-ts-autotag.lua
Normal 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
|
@ -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
12
sample/index.html
Normal 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
27
sample/index.php
Normal 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
17
sample/index.tsx
Normal 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
25
sample/index.vue
Normal 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
24
tests/minimal.vim
Normal 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
116
tests/tag_spec.lua
Normal 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)
|
Loading…
Reference in New Issue
Block a user