├── .gitignore ├── tests ├── endwise │ ├── ruby.rb │ ├── main.rs │ ├── javascript.js │ ├── sample.md │ ├── init.lua │ └── sample.lua ├── minimal.vim ├── utils_spec.lua ├── fastwrap_spec.lua ├── endwise_spec.lua ├── afterquote_spec.lua ├── treesitter_spec.lua ├── test_utils.lua └── nvim-autopairs_spec.lua ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.yml ├── FUNDING.yml ├── generated-files-bot.yml ├── stale.yml └── workflows │ ├── sponsors.yml │ ├── ci.yml │ └── docs.yml ├── style.toml ├── .luarc.json ├── Makefile ├── .editorconfig ├── lua ├── nvim-autopairs │ ├── rules │ │ ├── endwise-elixir.lua │ │ ├── endwise-lua.lua │ │ ├── ts_basic.lua │ │ ├── endwise-ruby.lua │ │ └── basic.lua │ ├── _log.lua │ ├── ts-rule.lua │ ├── ts-utils.lua │ ├── completion │ │ ├── cmp.lua │ │ ├── handlers.lua │ │ └── compe.lua │ ├── utils.lua │ ├── ts-conds.lua │ ├── rule.lua │ ├── fastwrap.lua │ └── conds.lua └── nvim-autopairs.lua ├── LICENSE ├── README.md └── doc ├── nvim-autopairs.txt └── nvim-autopairs-rules.txt /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /tests/endwise/ruby.rb: -------------------------------------------------------------------------------- 1 | module MyFirstModule 2 | 3 | 4 | end 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ['windwp'] 2 | custom: https://paypal.me/trieule1vn 3 | -------------------------------------------------------------------------------- /style.toml: -------------------------------------------------------------------------------- 1 | column_width = 85 2 | indent_type = "Spaces" 3 | quote_style = "AutoPreferSingle" 4 | indent_width = 4 5 | -------------------------------------------------------------------------------- /tests/endwise/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tests/endwise/javascript.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/endwise/sample.md: -------------------------------------------------------------------------------- 1 | # Example Markdown File 2 | 3 | ```javascript 4 | let; 5 | let; 6 | let; 7 | let; 8 | let; 9 | ``` 10 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", 3 | "Lua.diagnostics.disable": [ 4 | "undefined-field" 5 | ] 6 | } -------------------------------------------------------------------------------- /tests/endwise/init.lua: -------------------------------------------------------------------------------- 1 | local data1, data2 2 | local function wind() 3 | 4 | vim.api.nvim_get_current_buf() 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | end 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}" 3 | 4 | test-file: 5 | nvim --headless --noplugin -u tests/minimal.vim -c "lua require(\"plenary.busted\").run(\"$(FILE)\")" 6 | -------------------------------------------------------------------------------- /.github/generated-files-bot.yml: -------------------------------------------------------------------------------- 1 | generatedFiles: 2 | - path: "doc/nvim-autopairs.txt" 3 | - path: "doc/nvim-autopairs-rules.txt" 4 | message: "`nvim-autopairs.txt` is generated from README.md. Make changes there instead." 5 | ignoreAuthors: 6 | - 'github-actions[bot]' 7 | - 'windwp' 8 | -------------------------------------------------------------------------------- /tests/endwise/sample.lua: -------------------------------------------------------------------------------- 1 | local M={} 2 | 3 | M.autopairs_bs = function(rules) 4 | for _, rule in pairs(rules) do 5 | 6 | if rule.start_pair then 7 | end 8 | 9 | 10 | if rule.start_pair then 11 | 12 | end 13 | 14 | 15 | 16 | end 17 | end 18 | return M 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | 8 | [*.lua] 9 | indent_style = space 10 | indent_size = 4 11 | 12 | 13 | [*.vim] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.snippets] 18 | indent_style = tab 19 | indent_size = 4 20 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/rules/endwise-elixir.lua: -------------------------------------------------------------------------------- 1 | local endwise = require('nvim-autopairs.ts-rule').endwise 2 | 3 | local rules = { 4 | endwise('%sdo$', 'end', 'elixir', nil), 5 | endwise('fn$', 'end', 'elixir', nil), 6 | endwise('fn.*->$', 'end', 'elixir', nil), 7 | } 8 | 9 | return rules 10 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/rules/endwise-lua.lua: -------------------------------------------------------------------------------- 1 | local endwise = require('nvim-autopairs.ts-rule').endwise 2 | 3 | local rules = { 4 | endwise('then$', 'end', 'lua', 'if_statement'), 5 | endwise('function.*%(.*%)$', 'end', 'lua', {'function_declaration', 'local_function', 'function'}), 6 | } 7 | 8 | 9 | return rules 10 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/_log.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: undefined-field 2 | local empty = { 3 | debug = function(_) end, 4 | info = function(_) end, 5 | error = function(_) end, 6 | } 7 | if _G.__is_log then 8 | return require('plenary.log').new { 9 | plugin = 'nvim-autopairs', 10 | level = (_G.__is_log == true and 'debug') or 'warn', 11 | } or empty 12 | end 13 | return empty 14 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/ts-rule.lua: -------------------------------------------------------------------------------- 1 | local Rule = require('nvim-autopairs.rule') 2 | local cond = require('nvim-autopairs.conds') 3 | local ts_conds = require('nvim-autopairs.ts-conds') 4 | 5 | return { 6 | endwise = function (...) 7 | local params = {...} 8 | local rule = Rule(...) 9 | :use_regex(true) 10 | :end_wise(cond.is_end_line()) 11 | if params[4] then 12 | -- rule:with_cr(ts_conds.is_endwise_node(params[4])) 13 | rule:with_cr(ts_conds.is_ts_node(params[4])) 14 | end 15 | return rule 16 | end 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/rules/ts_basic.lua: -------------------------------------------------------------------------------- 1 | local basic = require('nvim-autopairs.rules.basic') 2 | local utils = require('nvim-autopairs.utils') 3 | local ts_conds = require('nvim-autopairs.ts-conds') 4 | local ts_extend = { 5 | "'", 6 | '"', 7 | '(', 8 | '[', 9 | '{', 10 | '`', 11 | } 12 | return { 13 | setup = function (config) 14 | local rules=basic.setup(config) 15 | for _, rule in pairs(rules) do 16 | if utils.is_in_table(ts_extend, rule.start_pair) then 17 | rule:with_pair(ts_conds.is_not_ts_node_comment()) 18 | end 19 | end 20 | return rules 21 | end 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tests/minimal.vim: -------------------------------------------------------------------------------- 1 | set rtp +=. 2 | set rtp +=../plenary.nvim/ 3 | set rtp +=../nvim-treesitter 4 | set rtp +=../playground/ 5 | 6 | lua _G.__is_log = true 7 | lua vim.fn.setenv("DEBUG_PLENARY", true) 8 | runtime! plugin/plenary.vim 9 | runtime! plugin/nvim-treesitter.vim 10 | runtime! plugin/playground.vim 11 | runtime! plugin/nvim-autopairs.vim 12 | 13 | set noswapfile 14 | set nobackup 15 | 16 | filetype indent off 17 | set expandtab 18 | set shiftwidth=4 19 | set nowritebackup 20 | set noautoindent 21 | set nocindent 22 | set nosmartindent 23 | set indentexpr= 24 | 25 | lua << EOF 26 | require("plenary/busted") 27 | require("nvim-treesitter").setup() 28 | vim.cmd[[luafile ./tests/test_utils.lua]] 29 | require("nvim-autopairs").setup() 30 | EOF 31 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | 19 | only: issues 20 | -------------------------------------------------------------------------------- /.github/workflows/sponsors.yml: -------------------------------------------------------------------------------- 1 | name: Generate Sponsors README 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: 0 0 1 */3 * 6 | permissions: 7 | contents: write 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 🛎️ 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup Git Config 16 | run: | 17 | git config --global user.name 'github-actions[bot]' 18 | git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' 19 | 20 | - name: Generate Sponsors 💖 21 | uses: JamesIves/github-sponsors-readme-action@v1 22 | with: 23 | token: ${{ secrets.PAT }} 24 | file: 'README.md' 25 | 26 | - name: Deploy to GitHub Pages 🚀 27 | uses: JamesIves/github-pages-deploy-action@v4 28 | with: 29 | branch: master 30 | folder: '.' 31 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/rules/endwise-ruby.lua: -------------------------------------------------------------------------------- 1 | local endwise = require('nvim-autopairs.ts-rule').endwise 2 | 3 | local rules = { 4 | endwise('%sdo$', 'end', 'ruby', nil), 5 | endwise('%sdo%s|.*|$', 'end', 'ruby', nil), 6 | endwise('begin$', 'end', 'ruby', nil), 7 | endwise('def%s.+$', 'end', 'ruby', nil), 8 | endwise('module%s.+$', 'end', 'ruby', nil), 9 | endwise('class%s.+$', 'end', 'ruby', nil), 10 | endwise('[%s=]%sif%s.+$', 'end', 'ruby', nil), 11 | endwise('[%s=]%sunless%s.+$', 'end', 'ruby', nil), 12 | endwise('[%s=]%scase%s.+$', 'end', 'ruby', nil), 13 | endwise('[%s=]%swhile%s.+$', 'end', 'ruby', nil), 14 | endwise('[%s=]%suntil%s.+$', 'end', 'ruby', nil), 15 | endwise('^if%s.+$', 'end', 'ruby', nil), 16 | endwise('^unless%s.+$', 'end', 'ruby', nil), 17 | endwise('^case%s.+$', 'end', 'ruby', nil), 18 | endwise('^while%s.+$', 'end', 'ruby', nil), 19 | endwise('^until%s.+$', 'end', 'ruby', nil), 20 | } 21 | 22 | return rules 23 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/ts-utils.lua: -------------------------------------------------------------------------------- 1 | local ts_get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text 2 | local M = {} 3 | 4 | local flatten = (function() 5 | if vim.fn.has "nvim-0.11" == 1 then 6 | return function(t) 7 | return vim.iter(t):flatten():totable() 8 | end 9 | else 10 | return function(t) 11 | return vim.tbl_flatten(t) 12 | end 13 | end 14 | end)() 15 | 16 | 17 | --- Returns the language tree at the given position. 18 | ---@return vim.treesitter.LanguageTree|nil 19 | function M.get_language_tree_at_position(position) 20 | local language_tree = vim.treesitter.get_parser() 21 | if language_tree == nil then return nil end 22 | language_tree:for_each_tree(function(_, tree) 23 | if tree:contains(flatten({ position, position })) then 24 | language_tree = tree 25 | end 26 | end) 27 | return language_tree 28 | end 29 | 30 | function M.get_tag_name(node) 31 | local tag_name = nil 32 | if node ~=nil then 33 | tag_name = ts_get_node_text(node) 34 | end 35 | return tag_name 36 | end 37 | 38 | return M 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 windwp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tests/utils_spec.lua: -------------------------------------------------------------------------------- 1 | local utils = require('nvim-autopairs.utils') 2 | local log = require('nvim-autopairs._log') 3 | 4 | local eq = assert.are.same 5 | 6 | local data = { 7 | { 8 | text = "add normal bracket", 9 | start = 2, 10 | num = 2, 11 | result = 'dd' 12 | }, 13 | 14 | 15 | { 16 | text = "iood", 17 | start = 1, 18 | num = 2, 19 | result = 'io' 20 | }, 21 | { 22 | text = "add normal bracket", 23 | start = 0, 24 | num = -2, 25 | result = '' 26 | }, 27 | 28 | { 29 | text = "add normal bracket", 30 | start = 3, 31 | num = -2, 32 | result = 'dd' 33 | }, 34 | { 35 | text = [["""]], 36 | start = 3, 37 | num = -3, 38 | result = '"""' 39 | }, 40 | 41 | { 42 | text = [["""]], 43 | start = 3, 44 | num = 3, 45 | result = '"' 46 | }, 47 | 48 | { 49 | text = [["""]], 50 | start = 2, 51 | num = 2, 52 | result = '""' 53 | }, 54 | } 55 | 56 | describe('utils test substring ', function() 57 | for _, value in pairs(data) do 58 | it('test sub: ' .. value.text, function() 59 | local result = utils.text_sub_char(value.text, value.start, value.num) 60 | eq(value.result, result, 'start ' .. value.start .. ' num' .. value.num) 61 | end) 62 | end 63 | end) 64 | 65 | vim.wait(100) 66 | -------------------------------------------------------------------------------- /tests/fastwrap_spec.lua: -------------------------------------------------------------------------------- 1 | -- local npairs = require('nvim-autopairs') 2 | 3 | -- _G.npairs = npairs 4 | 5 | -- npairs.setup({ 6 | -- enable_afterquote = true, 7 | -- fast_wrap = true, 8 | -- }) 9 | -- local data = { 10 | -- { 11 | -- only = true, 12 | -- name = 'move end wise after quote ', 13 | -- filepath = './tests/endwise/init.lua', 14 | -- filetype = 'lua', 15 | -- linenr = 5, 16 | -- key = [[]], 17 | -- before = [[const abc=(|"test",data ]], 18 | -- after = [[const abc=(|"test"),data ]], 19 | -- }, 20 | -- { 21 | -- name = 'move end wise after quote ', 22 | -- filepath = './tests/endwise/init.lua', 23 | -- filetype = 'lua', 24 | -- linenr = 5, 25 | -- key = [[]], 26 | -- before = [[const abc=(|"test"),data ]], 27 | -- after = [[const abc=(|"test",data) ]], 28 | -- }, 29 | -- { 30 | -- name = 'move end wise after quote ', 31 | -- filepath = './tests/endwise/init.lua', 32 | -- filetype = 'lua', 33 | -- linenr = 5, 34 | -- key = [[]], 35 | -- before = [[const abc=(|"test",data),dadas ]], 36 | -- after = [[const abc=(|"test",data,dadas) ]], 37 | -- }, 38 | -- { 39 | -- name = 'move end wise after quote ', 40 | -- filepath = './tests/endwise/init.lua', 41 | -- filetype = 'lua', 42 | -- linenr = 5, 43 | -- key = [[]], 44 | -- before = [[Plug {(|'dsfdsa',) on = 'aaa'} ]], 45 | -- after = [[Plug {('dsfdsa', on = 'aaa')} ]], 46 | -- }, 47 | -- } 48 | 49 | -- local run_data = _G.Test_filter(data) 50 | 51 | -- describe('[endwise tag]', function() 52 | -- _G.Test_withfile(run_data, {}) 53 | -- end) 54 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | x64-ubuntu: 7 | name: X64-ubuntu 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - os: ubuntu-20.04 14 | url: https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz 15 | manager: sudo apt-get 16 | packages: -y fd-find 17 | steps: 18 | - uses: actions/checkout@v2 19 | - run: date +%F > todays-date 20 | - name: Restore from todays cache 21 | uses: actions/cache@v2 22 | with: 23 | path: _neovim 24 | key: ${{ runner.os }}-${{ matrix.url }}-${{ hashFiles('todays-date') }} 25 | - name: Prepare 26 | run: | 27 | ${{ matrix.manager }} update 28 | ${{ matrix.manager }} install ${{ matrix.packages }} 29 | test -d _neovim || { 30 | mkdir -p _neovim 31 | curl -sL ${{ matrix.url }} | tar xzf - --strip-components=1 -C "${PWD}/_neovim" 32 | } 33 | mkdir -p ~/.local/share/nvim/site/pack/vendor/start 34 | git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim 35 | git clone --depth 1 https://github.com/nvim-treesitter/nvim-treesitter ~/.local/share/nvim/site/pack/vendor/start/nvim-treesitter 36 | git clone --depth 1 https://github.com/nvim-treesitter/playground ~/.local/share/nvim/site/pack/vendor/start/playground 37 | ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start 38 | - name: Run tests 39 | run: | 40 | export PATH="${PWD}/_neovim/bin:${PATH}" 41 | export VIM="${PWD}/_neovim/share/nvim/runtime" 42 | nvim --headless -u tests/minimal.vim -c "TSInstallSync all" -c "q" 43 | make test 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: panvimdoc 2 | 3 | on: 4 | push: 5 | paths: 6 | - README.md 7 | branches: 8 | - master 9 | jobs: 10 | docs: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | name: markdown to vimdoc 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Setup git 21 | run: | 22 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 23 | git config --local user.name "github-actions[bot]" 24 | printf 'VIMDOC_BRANCH=bot/vimdoc/%s\n' ${GITHUB_REF#refs/heads/} >> $GITHUB_ENV 25 | - name: Checkout to vimdoc branch 26 | run: git checkout -b ${VIMDOC_BRANCH} 27 | - name: panvimdoc 28 | uses: kdheepak/panvimdoc@v2.7.1 29 | with: 30 | vimdoc: nvim-autopairs 31 | description: A super powerful autopair for Neovim. 32 | - name: clone rules api docs 33 | run: | 34 | git clone --depth 1 https://github.com/windwp/nvim-autopairs.wiki.git ../nvim-autopairs.wiki 35 | cp ../nvim-autopairs.wiki/Rules-API.md ./Rules-API.md 36 | - name: panvimdoc 37 | uses: kdheepak/panvimdoc@v2.7.1 38 | with: 39 | vimdoc: nvim-autopairs-rules 40 | description: nvim-autopairs rules 41 | pandoc: "Rules-API.md" 42 | toc: true 43 | - name: Create PR 44 | run: | 45 | if ! [[ -z $(git status -s) ]]; then 46 | git add doc/nvim-autopairs.txt 47 | git add doc/nvim-autopairs-rules.txt 48 | git commit -m "chore: generated vimdoc" 49 | git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${VIMDOC_BRANCH} 50 | gh pr create --fill --base ${GITHUB_REF#refs/heads/} --head ${VIMDOC_BRANCH} || true 51 | fi 52 | -------------------------------------------------------------------------------- /tests/endwise_spec.lua: -------------------------------------------------------------------------------- 1 | local npairs = require('nvim-autopairs') 2 | local ts = require('nvim-treesitter.configs') 3 | local log = require('nvim-autopairs._log') 4 | 5 | ts.setup({ 6 | ensure_installed = { 'lua' }, 7 | highlight = { enable = true }, 8 | }) 9 | _G.npairs = npairs 10 | vim.api.nvim_set_keymap( 11 | 'i', 12 | '', 13 | 'v:lua.npairs.autopairs_cr()', 14 | { expr = true, noremap = true } 15 | ) 16 | 17 | local data = { 18 | { 19 | name = 'lua function add endwise', 20 | filepath = './tests/endwise/init.lua', 21 | filetype = 'lua', 22 | linenr = 5, 23 | key = [[]], 24 | before = [[function a()| ]], 25 | after = { 26 | [[function a() ]], 27 | [[| ]], 28 | [[ end ]], 29 | }, 30 | }, 31 | { 32 | name = 'lua function add endwise', 33 | filepath = './tests/endwise/init.lua', 34 | filetype = 'lua', 35 | linenr = 5, 36 | key = [[]], 37 | before = [[function a()|x ab ]], 38 | after = { 39 | [[function a() ]], 40 | [[|x ab]], 41 | }, 42 | }, 43 | { 44 | name = 'add if endwise', 45 | filepath = './tests/endwise/init.lua', 46 | filetype = 'lua', 47 | linenr = 5, 48 | key = [[]], 49 | before = [[if data== 'fdsafdsa' then| ]], 50 | after = { 51 | [[if data== 'fdsafdsa' then ]], 52 | [[|]], 53 | [[end ]], 54 | }, 55 | }, 56 | { 57 | name = 'undo on key', 58 | filepath = './tests/endwise/init.lua', 59 | filetype = 'lua', 60 | linenr = 5, 61 | key = [[{u]], 62 | before = [[local abc = | ]], 63 | after = { 64 | [[local abc = {|} ]], 65 | [[]], 66 | [[]], 67 | }, 68 | }, 69 | } 70 | 71 | local run_data = _G.Test_filter(data) 72 | 73 | describe('[endwise tag]', function() 74 | _G.Test_withfile(run_data, { 75 | -- need to understand this ??? new line make change cursor zzz 76 | cursor_add = 1, 77 | before_each = function(value) 78 | npairs.add_rules( 79 | require('nvim-autopairs.rules.endwise-' .. value.filetype) 80 | ) 81 | end, 82 | }) 83 | end) 84 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/completion/cmp.lua: -------------------------------------------------------------------------------- 1 | local autopairs = require('nvim-autopairs') 2 | local handlers = require('nvim-autopairs.completion.handlers') 3 | local cmp = require('cmp') 4 | 5 | local Kind = cmp.lsp.CompletionItemKind 6 | 7 | local M = {} 8 | 9 | M.filetypes = { 10 | -- Alias to all filetypes 11 | ["*"] = { 12 | ["("] = { 13 | kind = { Kind.Function, Kind.Method }, 14 | handler = handlers["*"] 15 | } 16 | }, 17 | python = { 18 | ["("] = { 19 | kind = { Kind.Function, Kind.Method }, 20 | handler = handlers.python 21 | } 22 | }, 23 | clojure = { 24 | ["("] = { 25 | kind = { Kind.Function, Kind.Method }, 26 | handler = handlers.lisp 27 | } 28 | }, 29 | clojurescript = { 30 | ["("] = { 31 | kind = { Kind.Function, Kind.Method }, 32 | handler = handlers.lisp 33 | } 34 | }, 35 | fennel = { 36 | ["("] = { 37 | kind = { Kind.Function, Kind.Method }, 38 | handler = handlers.lisp 39 | } 40 | }, 41 | janet = { 42 | ["("] = { 43 | kind = { Kind.Function, Kind.Method }, 44 | handler = handlers.lisp 45 | } 46 | }, 47 | tex = false, 48 | plaintex = false, 49 | context = false, 50 | haskell = false, 51 | purescript = false, 52 | sh = false, 53 | bash = false, 54 | nix = false, 55 | helm = false 56 | } 57 | 58 | M.on_confirm_done = function(opts) 59 | opts = vim.tbl_deep_extend('force', { 60 | filetypes = M.filetypes 61 | }, opts or {}) 62 | 63 | return function(evt) 64 | if evt.commit_character then 65 | return 66 | end 67 | 68 | local entry = evt.entry 69 | local commit_character = entry:get_commit_characters() 70 | local bufnr = vim.api.nvim_get_current_buf() 71 | local filetype = vim.api.nvim_buf_get_option(bufnr, 'filetype') 72 | local item = entry:get_completion_item() 73 | 74 | -- Without options and fallback 75 | if not opts.filetypes[filetype] and not opts.filetypes["*"] then 76 | return 77 | end 78 | 79 | if opts.filetypes[filetype] == false then 80 | return 81 | end 82 | 83 | -- If filetype is nil then use * 84 | local completion_options = opts.filetypes[filetype] or opts.filetypes["*"] 85 | 86 | local rules = vim.tbl_filter(function(rule) 87 | return completion_options[rule.key_map] 88 | end, autopairs.get_buf_rules(bufnr)) 89 | 90 | for char, value in pairs(completion_options) do 91 | if vim.tbl_contains(value.kind, item.kind) then 92 | value.handler(char, item, bufnr, rules, commit_character) 93 | end 94 | end 95 | end 96 | end 97 | 98 | return M 99 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/completion/handlers.lua: -------------------------------------------------------------------------------- 1 | local autopairs = require('nvim-autopairs') 2 | local utils = require('nvim-autopairs.utils') 3 | 4 | local M = {} 5 | 6 | ---@param char string 7 | ---@param item table 8 | ---@param bufnr number 9 | ---@param rules table 10 | ---@param commit_character table 11 | M["*"] = function(char, item, bufnr, rules, _) 12 | local line = utils.text_get_current_line(bufnr) 13 | local _, col = utils.get_cursor() 14 | local char_before, char_after = utils.text_cusor_line(line, col, 1, 1, false) 15 | 16 | if char == '' or char_before == char or char_after == char 17 | or (item.data and type(item.data) == 'table' and item.data.funcParensDisabled) 18 | or (item.textEdit and item.textEdit.newText and item.textEdit.newText:match "[%(%[%$]") 19 | or (item.insertText and item.insertText:match "[%(%[%$]") 20 | then 21 | return 22 | end 23 | 24 | if vim.tbl_isempty(rules) then 25 | return 26 | end 27 | 28 | local new_text = '' 29 | local add_char = 1 30 | 31 | for _, rule in pairs(rules) do 32 | if rule.start_pair then 33 | local prev_char, next_char = utils.text_cusor_line( 34 | new_text, 35 | col + add_char, 36 | #rule.start_pair, 37 | #rule.end_pair, 38 | rule.is_regex 39 | ) 40 | local cond_opt = { 41 | ts_node = autopairs.state.ts_node, 42 | text = new_text, 43 | rule = rule, 44 | bufnr = bufnr, 45 | col = col + 1, 46 | char = char, 47 | line = line, 48 | prev_char = prev_char, 49 | next_char = next_char, 50 | } 51 | if rule.key_map and rule:can_pair(cond_opt) then 52 | vim.api.nvim_feedkeys(rule.key_map, "i", true) 53 | return 54 | end 55 | end 56 | end 57 | end 58 | 59 | ---Handler with "clojure", "clojurescript", "fennel", "janet 60 | M.lisp = function (char, item, bufnr, _, _) 61 | local line = utils.text_get_current_line(bufnr) 62 | local _, col = utils.get_cursor() 63 | local char_before, char_after = utils.text_cusor_line(line, col, 1, 1, false) 64 | local length = #item.label 65 | 66 | if char == '' or char_before == char or char_after == char 67 | or (item.data and item.data.funcParensDisabled) 68 | or (item.textEdit and item.textEdit.newText and item.textEdit.newText:match "[%(%[%$]") 69 | or (item.insertText and item.insertText:match "[%(%[%$]") 70 | then 71 | return 72 | end 73 | 74 | if utils.text_sub_char(line, col - length, 1) == "(" then 75 | utils.feed("") 76 | return 77 | end 78 | utils.feed(utils.key.left, length) 79 | utils.feed(char) 80 | utils.feed(utils.key.right, length) 81 | utils.feed("") 82 | end 83 | 84 | M.python = function(char, item, bufnr, rules, _) 85 | if item.data then 86 | item.data.funcParensDisabled = false 87 | end 88 | M["*"](char,item,bufnr,rules) 89 | end 90 | 91 | return M 92 | -------------------------------------------------------------------------------- /tests/afterquote_spec.lua: -------------------------------------------------------------------------------- 1 | local npairs = require('nvim-autopairs') 2 | 3 | _G.npairs = npairs 4 | 5 | npairs.setup({ 6 | enable_afterquote = true, 7 | }) 8 | 9 | local data = { 10 | { 11 | name = 'add bracket after quote ', 12 | filepath = './tests/endwise/init.lua', 13 | filetype = 'lua', 14 | linenr = 5, 15 | key = [[(]], 16 | before = [[const abc=|"test" ]], 17 | after = [[const abc=(|"test") ]], 18 | }, 19 | { 20 | name = 'add bracket after quote ', 21 | filepath = './tests/endwise/init.lua', 22 | filetype = 'lua', 23 | linenr = 5, 24 | key = [[(]], 25 | before = [[|"test"]], 26 | after = [[(|"test")]], 27 | }, 28 | { 29 | name = 'check quote without any text on end similar', 30 | filepath = './tests/endwise/init.lua', 31 | filetype = 'lua', 32 | linenr = 5, 33 | key = [[(]], 34 | before = [[ const [template, setTemplate] = useState|'')]], 35 | after = [[ const [template, setTemplate] = useState(|'')]], 36 | }, 37 | 38 | { 39 | name = 'add bracket after quote ', 40 | filepath = './tests/endwise/init.lua', 41 | filetype = 'lua', 42 | linenr = 5, 43 | key = [[{]], 44 | before = [[(|"test") ]], 45 | after = [[({|"test"}) ]], 46 | }, 47 | { 48 | name = 'add bracket after quote ', 49 | filepath = './tests/endwise/init.lua', 50 | filetype = 'lua', 51 | linenr = 5, 52 | key = [[(]], 53 | before = [[const abc=|"visu\"dsa" ]], 54 | after = [[const abc=(|"visu\"dsa") ]], 55 | }, 56 | { 57 | name = 'not add on exist quote', 58 | filepath = './tests/endwise/init.lua', 59 | filetype = 'lua', 60 | linenr = 5, 61 | key = [[(]], 62 | before = [[const abc=|"visu\"dsa") ]], 63 | after = [[const abc=(|"visu\"dsa") ]], 64 | }, 65 | 66 | { 67 | name = 'test add close quote on match', 68 | filepath = './tests/endwise/init.lua', 69 | filetype = 'lua', 70 | linenr = 5, 71 | key = [[(]], 72 | before = [[const abc=|"visu\"dsa" ]], 73 | after = [[const abc=(|"visu\"dsa") ]], 74 | }, 75 | { 76 | name = 'not add bracket with quote have comma', 77 | filepath = './tests/endwise/init.lua', 78 | filetype = 'lua', 79 | linenr = 5, 80 | key = [[(]], 81 | before = [[|"data", abcdef]], 82 | after = [[(|"data", abcdef]], 83 | }, 84 | { 85 | name = 'not add bracket with quote have comma', 86 | filepath = './tests/endwise/init.lua', 87 | filetype = 'lua', 88 | linenr = 5, 89 | key = [[(]], 90 | before = [[|"data", "abcdef"]], 91 | after = { [[(|"data", "abcdef"]] }, 92 | }, 93 | } 94 | 95 | local run_data = _G.Test_filter(data) 96 | 97 | describe('[afterquote tag]', function() 98 | _G.Test_withfile(run_data, {}) 99 | end) 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a problem with nvim-autopairs 3 | labels: [bug] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Before reporting: search [existing issues](https://github.com/windwp/nvim-autopairs/issues) and make sure that both nvim-autopairs and its dependencies are updated to the latest version. 9 | - type: textarea 10 | attributes: 11 | label: "Description" 12 | description: "A short description of the problem you are reporting." 13 | validations: 14 | required: true 15 | - type: textarea 16 | attributes: 17 | label: "Mapping bug" 18 | description: "report a bug about mapping `` key and completion plugin" 19 | value: | 20 | 1.If you report a bug about indent. Please remember that plugin doesn't do anything about indent. 21 | It just trigger the indent of your vim config so if you have wrong indent config then it will do wrong indent. 22 | You can check by select a block of code and press `==` 23 | 2. provide result of command `:verbose imap `. 24 | validations: 25 | required: false 26 | - type: textarea 27 | attributes: 28 | label: "Steps to reproduce" 29 | description: "Steps to reproduce using the minimal config provided below." 30 | placeholder: | 31 | - It will beter if you can provide a video of gif 32 | validations: 33 | required: false 34 | - type: textarea 35 | attributes: 36 | label: "Minimal config" 37 | description: "Minimal(!) configuration necessary to reproduce the issue. Save this as `minimal.lua`. If _absolutely_ necessary, add plugins and config options from your `init.lua` at the indicated lines." 38 | render: Lua 39 | value: | 40 | vim.cmd [[set runtimepath=$VIMRUNTIME]] 41 | vim.cmd [[set packpath=/tmp/nvim/site]] 42 | local package_root = '/tmp/nvim/site/pack' 43 | local install_path = package_root .. '/packer/start/packer.nvim' 44 | local function load_plugins() 45 | require('packer').startup { 46 | { 47 | 'wbthomason/packer.nvim', 48 | { 49 | 'windwp/nvim-autopairs', 50 | }, 51 | -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE 52 | }, 53 | config = { 54 | package_root = package_root, 55 | compile_path = install_path .. '/plugin/packer_compiled.lua', 56 | display = { non_interactive = true }, 57 | }, 58 | } 59 | end 60 | _G.load_config = function() 61 | require('nvim-autopairs').setup() 62 | end 63 | if vim.fn.isdirectory(install_path) == 0 then 64 | print("Installing nvim-autopairs and dependencies.") 65 | vim.fn.system { 'git', 'clone', '--depth=1', 'https://github.com/wbthomason/packer.nvim', install_path } 66 | end 67 | load_plugins() 68 | require('packer').sync() 69 | vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua load_config()]] 70 | validations: 71 | required: true 72 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/completion/compe.lua: -------------------------------------------------------------------------------- 1 | local npairs = require('nvim-autopairs') 2 | local Completion = require('compe.completion') 3 | local utils = require('nvim-autopairs.utils') 4 | 5 | local method_kind = nil 6 | local function_kind = nil 7 | 8 | local options = {} 9 | 10 | local M = {} 11 | M.completion_done = function() 12 | local line = utils.text_get_current_line(0) 13 | local _, col = utils.get_cursor() 14 | local prev_char, next_char = utils.text_cusor_line(line, col, 1, 1, false) 15 | 16 | local filetype = vim.bo.filetype 17 | local char = options.map_char[filetype] or options.map_char["all"] or '(' 18 | if char == '' then return end 19 | 20 | if prev_char ~= char and next_char ~= char then 21 | if method_kind == nil then 22 | method_kind = require('vim.lsp.protocol').CompletionItemKind[2] 23 | function_kind = require('vim.lsp.protocol').CompletionItemKind[3] 24 | end 25 | local item = Completion._confirm_item 26 | if item.kind == method_kind or item.kind == function_kind then 27 | -- check insert text have ( from snippet 28 | local completion_item = item.user_data.compe.completion_item 29 | if 30 | ( 31 | completion_item.textEdit 32 | and completion_item.textEdit.newText 33 | and completion_item.textEdit.newText:match('[%(%[%$]') 34 | ) 35 | or (completion_item.insertText and completion_item.insertText:match('[%(%[%$]')) 36 | then 37 | return 38 | end 39 | vim.api.nvim_feedkeys(char, 'i', true) 40 | end 41 | end 42 | end 43 | 44 | M.setup = function(opt) 45 | opt = opt or { map_cr = true, map_complete = true, auto_select = false, map_char = {all = '('}} 46 | if not opt.map_char then opt.map_char = {} end 47 | options = opt 48 | local map_cr = opt.map_cr 49 | local map_complete = opt.map_complete 50 | vim.g.completion_confirm_key = '' 51 | if map_cr then 52 | vim.api.nvim_set_keymap( 53 | 'i', 54 | '', 55 | '', 56 | { callback = M.completion_confirm, expr = true, noremap = true } 57 | ) 58 | end 59 | if opt.auto_select then 60 | M.completion_confirm = function() 61 | if vim.fn.pumvisible() ~= 0 then 62 | return vim.fn['compe#confirm']({ keys = '', select = true }) 63 | else 64 | return npairs.autopairs_cr() 65 | end 66 | end 67 | else 68 | M.completion_confirm = function() 69 | if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info()['selected'] ~= -1 then 70 | return vim.fn['compe#confirm'](npairs.esc('')) 71 | else 72 | return npairs.autopairs_cr() 73 | end 74 | end 75 | end 76 | 77 | if map_complete then 78 | vim.cmd([[ 79 | augroup autopairs_compe 80 | autocmd! 81 | autocmd User CompeConfirmDone lua require'nvim-autopairs.completion.compe'.completion_done() 82 | augroup end 83 | ]]) 84 | end 85 | end 86 | return M 87 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/rules/basic.lua: -------------------------------------------------------------------------------- 1 | local Rule = require("nvim-autopairs.rule") 2 | local cond = require("nvim-autopairs.conds") 3 | local utils = require('nvim-autopairs.utils') 4 | 5 | local function quote_creator(opt) 6 | local quote = function(...) 7 | local move_func = opt.enable_moveright and cond.move_right or cond.none 8 | ---@type Rule 9 | local rule = Rule(...) 10 | :with_move(move_func()) 11 | :with_pair(cond.not_add_quote_inside_quote()) 12 | 13 | if #opt.ignored_next_char > 1 then 14 | rule:with_pair(cond.not_after_regex(opt.ignored_next_char)) 15 | end 16 | rule:use_undo(opt.break_undo) 17 | return rule 18 | end 19 | return quote 20 | end 21 | 22 | local function bracket_creator(opt) 23 | local quote = quote_creator(opt) 24 | local bracket = function(...) 25 | local rule = quote(...) 26 | if opt.enable_check_bracket_line == true then 27 | rule:with_pair(cond.is_bracket_line()) 28 | :with_move(cond.is_bracket_line_move()) 29 | end 30 | if opt.enable_bracket_in_quote then 31 | -- still add bracket if text is quote "|" and next_char have " 32 | rule:with_pair(cond.is_bracket_in_quote(), 1) 33 | end 34 | return rule 35 | end 36 | return bracket 37 | end 38 | 39 | local function setup(opt) 40 | local quote = quote_creator(opt) 41 | local bracket = bracket_creator(opt) 42 | local rules = { 43 | Rule("", { "html", "markdown" }):with_cr(cond.none()), 44 | Rule("```", "```", { "markdown", "vimwiki", "rmarkdown", "rmd", "pandoc", "quarto", "typst", "gitcommit" }) 45 | :with_pair(cond.not_before_char('`', 3)), 46 | Rule("```.*$", "```", { "markdown", "vimwiki", "rmarkdown", "rmd", "pandoc", "quarto", "typst" }):only_cr():use_regex(true), 47 | Rule('"""', '"""', { "python", "elixir", "julia", "kotlin", "scala", "sbt", "toml" }):with_pair(cond.not_before_char('"', 3)), 48 | Rule("'''", "'''", { "python", "toml" }):with_pair(cond.not_before_char("'", 3)), 49 | quote("'", "'", { "-rust", "-nix" }) 50 | :with_pair(function(opts) 51 | -- python literals string 52 | local str = utils.text_sub_char(opts.line, opts.col - 1, 1) 53 | if vim.bo.filetype == 'python' and str:match("[frbuFRBU]") then 54 | return true 55 | end 56 | end) 57 | :with_pair(cond.not_before_regex("%w")), 58 | quote("'", "'", "rust"):with_pair(cond.not_before_regex("[%w<&]")):with_pair(cond.not_after_text(">")), 59 | Rule("''", "''", 'nix'):with_move(cond.after_text("'")), 60 | quote("`", "`"), 61 | quote('"', '"', "-vim"), 62 | quote('"', '"', "vim"):with_pair(cond.not_before_regex("^%s*$", -1)), 63 | bracket("(", ")"), 64 | bracket("[", "]"), 65 | bracket("{", "}"), 66 | Rule( 67 | ">[%w%s]*$", 68 | "^%s*', 11 | 'v:lua.npairs.check_break_line_char()', 12 | { expr = true, noremap = true } 13 | ) 14 | 15 | ts.setup({ 16 | ensure_installed = { 'lua', 'javascript', 'rust', 'markdown', 'markdown_inline' }, 17 | highlight = { enable = true }, 18 | autopairs = { enable = true }, 19 | }) 20 | 21 | local data = { 22 | { 23 | name = 'treesitter lua quote', 24 | filepath = './tests/endwise/init.lua', 25 | filetype = 'lua', 26 | linenr = 5, 27 | key = [["]], 28 | before = { 29 | [[ [[ aaa| ]], 30 | [[ ]], 31 | ']]', 32 | }, 33 | after = [[ [[ aaa"| ]], 34 | }, 35 | 36 | { 37 | setup_func = function() 38 | npairs.add_rules({ 39 | Rule('%', '%', 'lua'):with_pair( 40 | ts_conds.is_ts_node({ 'string', 'comment', 'string_content' }) 41 | ), 42 | }) 43 | end, 44 | name = 'ts_conds is_ts_node quote', 45 | filepath = './tests/endwise/init.lua', 46 | filetype = 'lua', 47 | linenr = 5, 48 | key = [[%]], 49 | before = { 50 | [[ [[ abcde | ]], 51 | [[ ]], 52 | ']]', 53 | }, 54 | after = [[ [[ abcde %|% ]], 55 | }, 56 | { 57 | name = 'ts_conds is_ts_node failed', 58 | filepath = './tests/endwise/init.lua', 59 | linenr = 5, 60 | filetype = 'lua', 61 | key = '%', 62 | before = { [[local abcd| = ' visual ']] }, 63 | after = [[local abcd%| = ' visual ']], 64 | }, 65 | { 66 | setup_func = function() 67 | npairs.add_rules({ 68 | Rule('<', '>', 'rust'):with_pair(ts_conds.is_ts_node({ 69 | 'type_identifier', 70 | 'let_declaration', 71 | 'parameters', 72 | })), 73 | }) 74 | end, 75 | name = 'ts_conds is_ts_node failed', 76 | filepath = './tests/endwise/main.rs', 77 | linenr = 5, 78 | filetype = 'rust', 79 | key = '<', 80 | before = [[pub fn noop(_inp: Vec|) {]], 81 | after = [[pub fn noop(_inp: Vec<|>) {]], 82 | }, 83 | { 84 | setup_func = function() 85 | npairs.add_rules({ 86 | Rule('*', '*', { 'markdown', 'markdown_inline' }) 87 | :with_pair(ts_conds.is_not_in_context()), 88 | }) 89 | end, 90 | name = 'ts_context markdown `*` success md_context', 91 | filepath = './tests/endwise/sample.md', 92 | linenr = 2, 93 | filetype = 'markdown', 94 | key = '*', 95 | before = [[|]], 96 | after = [[*|*]], 97 | }, 98 | { 99 | setup_func = function() 100 | npairs.add_rules({ 101 | Rule('*', '*', { 'markdown', 'markdown_inline' }) 102 | :with_pair(ts_conds.is_not_in_context()), 103 | }) 104 | end, 105 | name = 'ts_context codeblock `*` fail js_context', 106 | filepath = './tests/endwise/sample.md', 107 | linenr = 6, 108 | filetype = 'markdown', 109 | key = '*', 110 | before = [[let calc = 1 |]], 111 | after = [[let calc = 1 *|]], 112 | }, 113 | } 114 | 115 | local run_data = _G.Test_filter(data) 116 | 117 | describe('[treesitter check]', function() 118 | _G.Test_withfile(run_data, { 119 | before_each = function(value) 120 | npairs.setup({ 121 | check_ts = true, 122 | ts_config = { 123 | javascript = { 'template_string', 'comment' }, 124 | }, 125 | }) 126 | if value.setup_func then 127 | value.setup_func() 128 | end 129 | end, 130 | }) 131 | end) 132 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local api = vim.api 3 | local log = require('nvim-autopairs._log') 4 | 5 | M.key = { 6 | del = "", 7 | bs = "", 8 | c_h = "", 9 | left = "", 10 | right = "", 11 | join_left = "U", 12 | join_right = "U", 13 | undo_sequence = "u", 14 | noundo_sequence = "U", 15 | abbr = "" 16 | } 17 | 18 | M.set_vchar = function(text) 19 | text = text:gsub('"', '\\"') 20 | vim.v.char = text 21 | end 22 | 23 | 24 | M.is_quote = function(char) 25 | return char == "'" or char == '"' or char == '`' 26 | end 27 | 28 | M.is_bracket = function(char) 29 | return char == "(" or char == '[' or char == '{' or char == '<' 30 | end 31 | 32 | 33 | M.is_close_bracket = function(char) 34 | return char == ")" or char == ']' or char == '}' or char == '>' 35 | end 36 | 37 | M.compare = function(value, text, is_regex) 38 | if is_regex and string.match(text, value) then 39 | return true 40 | elseif text == value then 41 | return true 42 | end 43 | return false 44 | end 45 | 46 | ---check cursor is inside a quote 47 | ---@param line string 48 | ---@param pos number position in line 49 | ---@param quote_type nil|string specify a quote 50 | ---@return boolean 51 | M.is_in_quotes = function(line, pos, quote_type) 52 | local cIndex = 0 53 | local result = false 54 | local last_char = quote_type or '' 55 | 56 | while cIndex < string.len(line) and cIndex < pos do 57 | cIndex = cIndex + 1 58 | local char = line:sub(cIndex, cIndex) 59 | local prev_char = line:sub(cIndex - 1, cIndex - 1) 60 | if 61 | result == true 62 | and char == last_char 63 | and prev_char ~= "\\" 64 | then 65 | result = false 66 | last_char = quote_type or '' 67 | elseif 68 | result == false 69 | and M.is_quote(char) 70 | and (not quote_type or char == quote_type) 71 | --a single quote with a word before is not count unless it is a 72 | -- prefixed string in python (e.g. f'string {with_brackets}') 73 | and not ( 74 | char == "'" 75 | and prev_char:match('%w') 76 | and (vim.bo.filetype ~= 'python' or prev_char:match('[^frbuFRBU]')) 77 | ) 78 | then 79 | last_char = quote_type or char 80 | result = true 81 | end 82 | end 83 | return result 84 | end 85 | 86 | M.is_attached = function(bufnr) 87 | local _, check = pcall(api.nvim_buf_get_var, bufnr or 0, "nvim-autopairs") 88 | return check == 1 89 | end 90 | 91 | 92 | M.set_attach = function(bufnr, value) 93 | api.nvim_buf_set_var(bufnr or 0, "nvim-autopairs", value) 94 | end 95 | 96 | M.is_in_table = function(tbl, val) 97 | if tbl == nil then return false end 98 | for _, value in pairs(tbl) do 99 | if val == value then return true end 100 | end 101 | return false 102 | end 103 | 104 | M.check_filetype = function(tbl, filetype) 105 | if tbl == nil then return true end 106 | return M.is_in_table(tbl, filetype) 107 | end 108 | 109 | M.check_not_filetype = function(tbl, filetype) 110 | if tbl == nil then return true end 111 | return not M.is_in_table(tbl, filetype) 112 | end 113 | 114 | M.is_in_range = function(row, col, range) 115 | local start_row, start_col, end_row, end_col = unpack(range) 116 | 117 | return (row > start_row or (start_row == row and col >= start_col)) 118 | and (row < end_row or (row == end_row and col <= end_col)) 119 | end 120 | 121 | M.get_cursor = function(bufnr) 122 | local row, col = unpack(api.nvim_win_get_cursor(bufnr or 0)) 123 | return row - 1, col 124 | end 125 | M.text_get_line = function(bufnr, lnum) 126 | return api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, false)[1] or '' 127 | end 128 | 129 | M.text_get_current_line = function(bufnr) 130 | local row = unpack(api.nvim_win_get_cursor(0)) or 1 131 | return M.text_get_line(bufnr, row - 1) 132 | end 133 | 134 | M.repeat_key = function(key, num) 135 | local text = '' 136 | for _ = 1, num, 1 do 137 | text = text .. key 138 | end 139 | return text 140 | end 141 | --- cut text from position with number character 142 | ---@param line string text 143 | ---@param col number position of text 144 | ---@param prev_count number number char previous 145 | ---@param next_count number number char next 146 | ---@param is_regex boolean if it is regex then will cut all 147 | M.text_cusor_line = function(line, col, prev_count, next_count, is_regex) 148 | if is_regex then 149 | prev_count = col 150 | next_count = #line - col 151 | end 152 | local prev = M.text_sub_char(line, col, -prev_count) 153 | local next = M.text_sub_char(line, col + 1, next_count) 154 | return prev, next 155 | end 156 | 157 | M.text_sub_char = function(line, start, num) 158 | local finish = start 159 | if num < 0 then 160 | start = start + num + 1 161 | else 162 | finish = start + num - 1 163 | end 164 | 165 | if start + finish < 0 then 166 | return "" 167 | end 168 | return string.sub(line, start, finish) 169 | end 170 | 171 | -- P(M.text_sub_char("aa'' aaa", 3, -1)) 172 | M.insert_char = function(text) 173 | api.nvim_put({ text }, "c", false, true) 174 | end 175 | 176 | M.feed = function(text, num) 177 | num = num or 1 178 | if num < 1 then num = 1 end 179 | local result = '' 180 | for _ = 1, num, 1 do 181 | result = result .. text 182 | end 183 | log.debug("result" .. result) 184 | api.nvim_feedkeys(api.nvim_replace_termcodes( 185 | result, true, false, true), 186 | "n", true) 187 | end 188 | 189 | M.esc = function(cmd) 190 | return vim.api.nvim_replace_termcodes(cmd, true, false, true) 191 | end 192 | 193 | M.is_block_wise_mode = function() 194 | return vim.fn.visualmode() == '' 195 | end 196 | 197 | --- get prev_char with out key_map 198 | M.get_prev_char = function(opt) 199 | return opt.line:sub(opt.col - 1, opt.col + #opt.rule.start_pair - 2) 200 | end 201 | 202 | return M 203 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/ts-conds.lua: -------------------------------------------------------------------------------- 1 | local log = require('nvim-autopairs._log') 2 | local utils = require('nvim-autopairs.utils') 3 | local ts_get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text 4 | 5 | local conds = {} 6 | 7 | conds.is_endwise_node = function(nodes) 8 | if nodes == nil then return function() return true end end 9 | if type(nodes) == 'string' then nodes = {nodes} end 10 | 11 | return function (opts) 12 | log.debug('is_endwise_node') 13 | if not opts.check_endwise_ts then return true end 14 | if nodes == nil then return true end 15 | if #nodes == 0 then return true end 16 | 17 | local parser = vim.treesitter.get_parser(nil, nil, { error = false }) 18 | if parser == nil then return end 19 | parser:parse() 20 | local target = vim.treesitter.get_node({ ignore_injections = false }) 21 | if target ~= nil and utils.is_in_table(nodes, target:type()) then 22 | local text = ts_get_node_text(target) or {""} 23 | local last = text[#text]:match(opts.rule.end_pair) 24 | -- check last character is match with end_pair 25 | if last == nil then 26 | return true 27 | end 28 | -- log.debug('last:' .. last) 29 | -- if match then we need tocheck parent node 30 | -- some time treesiter is group 2 node then we need check that 31 | local begin_target,_, end_target = target:range() 32 | local begin_parent,_, end_parent = target:parent():range() 33 | -- log.debug(target:range()) 34 | -- log.debug(ts_get_node_text(target)) 35 | -- log.debug(target:parent():range()) 36 | -- log.debug(ts_get_node_text(target:parent())) 37 | if 38 | ( 39 | begin_target ~= begin_parent 40 | and end_target == end_parent 41 | ) 42 | or 43 | (end_parent - end_target == 1) 44 | then 45 | return true 46 | end 47 | -- return true 48 | else 49 | end 50 | return false 51 | end 52 | end 53 | 54 | conds.is_in_range = function(callback, position) 55 | assert( 56 | type(callback) == 'function' and type(position) == 'function', 57 | 'callback and position should be a function' 58 | ) 59 | return function(opts) 60 | log.debug('is_in_range') 61 | local cursor = position() 62 | assert( 63 | type(cursor) == 'table' and #cursor == 2, 64 | 'position should be return a table like {line, col}' 65 | ) 66 | local line = cursor[1] 67 | local col = cursor[2] 68 | 69 | local bufnr = 0 70 | local root_lang_tree = vim.treesitter.get_parser(bufnr, nil, { error = false }) 71 | if root_lang_tree == nil then return end 72 | local lang_tree = root_lang_tree:language_for_range({ line, col, line, col }) 73 | 74 | local result 75 | 76 | for _, tree in ipairs(lang_tree:trees()) do 77 | local root = tree:root() 78 | if root and vim.treesitter.is_in_node_range(root, line, col) then 79 | local node = root:named_descendant_for_range(line, col, line, col) 80 | local anonymous_node = root:descendant_for_range( 81 | line, 82 | col, 83 | line, 84 | col 85 | ) 86 | 87 | result = { 88 | node = node, 89 | lang = lang_tree:lang(), 90 | type = node:type(), 91 | cursor = vim.api.nvim_win_get_cursor(0), 92 | line = vim.api.nvim_buf_get_lines(bufnr, line, line + 1, true)[1], 93 | range = { node:range() }, 94 | anonymous = anonymous_node:type(), 95 | } 96 | end 97 | end 98 | 99 | return callback(result) 100 | end 101 | end 102 | 103 | conds.is_ts_node = function(nodes) 104 | if type(nodes) == 'string' then nodes = {nodes} end 105 | assert(nodes ~= nil, "ts nodes should be string or table") 106 | return function (opts) 107 | log.debug('is_ts_node') 108 | if #nodes == 0 then return end 109 | 110 | local parser = vim.treesitter.get_parser(nil, nil, { error = false }) 111 | if parser == nil then return end 112 | parser:parse() 113 | local target = vim.treesitter.get_node({ ignore_injections = false }) 114 | if target ~= nil and utils.is_in_table(nodes, target:type()) then 115 | return true 116 | end 117 | return false 118 | end 119 | end 120 | 121 | conds.is_not_ts_node = function(nodes) 122 | if type(nodes) == 'string' then nodes = {nodes} end 123 | assert(nodes ~= nil, "ts nodes should be string or table") 124 | return function (opts) 125 | log.debug('is_not_ts_node') 126 | if #nodes == 0 then return end 127 | 128 | local parser = vim.treesitter.get_parser(nil, nil, { error = false }) 129 | if parser == nil then return end 130 | parser:parse() 131 | local target = vim.treesitter.get_node({ ignore_injections = false }) 132 | if target ~= nil and utils.is_in_table(nodes, target:type()) then 133 | return false 134 | end 135 | end 136 | end 137 | 138 | conds.is_not_ts_node_comment = function() 139 | return function(opts) 140 | log.debug('not_in_ts_node_comment') 141 | if not opts.ts_node then return end 142 | 143 | local parser = vim.treesitter.get_parser(nil, nil, { error = false }) 144 | if parser == nil then return end 145 | parser:parse() 146 | local target = vim.treesitter.get_node({ ignore_injections = false }) 147 | if target ~= nil and utils.is_in_table(opts.ts_node, target:type()) then 148 | return false 149 | end 150 | end 151 | end 152 | 153 | conds.is_not_in_context = function() 154 | return function(opts) 155 | local context = require("nvim-autopairs.ts-utils") 156 | .get_language_tree_at_position({ utils.get_cursor() }) 157 | if context == nil then return end 158 | if not vim.tbl_contains(opts.rule.filetypes, context:lang()) then 159 | return false 160 | end 161 | end 162 | end 163 | 164 | return conds 165 | -------------------------------------------------------------------------------- /tests/test_utils.lua: -------------------------------------------------------------------------------- 1 | local utils = require('nvim-autopairs.utils') 2 | local log = require('nvim-autopairs._log') 3 | local api = vim.api 4 | local ts_get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text 5 | 6 | local helpers = {} 7 | 8 | function helpers.feed(text, feed_opts, is_replace) 9 | feed_opts = feed_opts or 'n' 10 | if not is_replace then 11 | text = vim.api.nvim_replace_termcodes(text, true, false, true) 12 | end 13 | vim.api.nvim_feedkeys(text, feed_opts, true) 14 | end 15 | 16 | function helpers.insert(text, is_replace) 17 | helpers.feed('i' .. text, 'x', is_replace) 18 | end 19 | 20 | utils.insert_char = function(text) 21 | api.nvim_put({ text }, 'c', true, true) 22 | end 23 | 24 | utils.feed = function(text, num) 25 | local result = '' 26 | for _ = 1, num, 1 do 27 | result = result .. text 28 | end 29 | api.nvim_feedkeys( 30 | ---@diagnostic disable-next-line: param-type-mismatch 31 | api.nvim_replace_termcodes(result, true, false, true), 32 | 'x', 33 | true 34 | ) 35 | end 36 | 37 | _G.eq = assert.are.same 38 | 39 | _G.Test_filter = function(data) 40 | local run_data = {} 41 | for _, value in pairs(data) do 42 | if value.only == true then 43 | table.insert(run_data, value) 44 | break 45 | end 46 | end 47 | if #run_data == 0 then 48 | run_data = data 49 | end 50 | return run_data 51 | end 52 | 53 | local compare_text = function(linenr, text_after, name, cursor_add, end_cursor) 54 | cursor_add = cursor_add or 0 55 | local new_text = vim.api.nvim_buf_get_lines( 56 | 0, 57 | linenr - 1, 58 | linenr + #text_after - 1, 59 | true 60 | ) 61 | for i = 1, #text_after, 1 do 62 | local t = string.gsub(text_after[i], '%|', '') 63 | if t 64 | and new_text[i] 65 | and t:gsub('%s+$', '') ~= new_text[i]:gsub('%s+$', '') 66 | then 67 | eq(t, new_text[i], '\n\n text error: ' .. name .. '\n') 68 | end 69 | local p_after = string.find(text_after[i], '%|') 70 | if p_after then 71 | local row, col = utils.get_cursor() 72 | if end_cursor then 73 | eq(row, linenr + i - 2, '\n\n cursor row error: ' .. name .. '\n') 74 | eq( 75 | col + 1, 76 | end_cursor, 77 | '\n\n end cursor column error : ' .. name .. '\n' 78 | ) 79 | else 80 | eq(row, linenr + i - 2, '\n\n cursor row error: ' .. name .. '\n') 81 | p_after = p_after + cursor_add 82 | eq( 83 | col, 84 | math.max(p_after - 2, 0), 85 | '\n\n cursor column error : ' .. name .. '\n' 86 | ) 87 | end 88 | end 89 | end 90 | return true 91 | end 92 | 93 | _G.Test_withfile = function(test_data, cb) 94 | for _, value in pairs(test_data) do 95 | it('test ' .. value.name, function(_) 96 | local text_before = {} 97 | value.linenr = value.linenr or 1 98 | local pos_before = { 99 | linenr = value.linenr, 100 | colnr = 0, 101 | } 102 | if not vim.tbl_islist(value.before) then 103 | value.before = { value.before } 104 | end 105 | for index, text in pairs(value.before) do 106 | local txt = string.gsub(tostring(text), '%|', '') 107 | table.insert(text_before, txt) 108 | if string.match(tostring(text), '%|') then 109 | if string.find(tostring(text), '%|') then 110 | pos_before.colnr = string.find(tostring(text), '%|') 111 | pos_before.linenr = value.linenr + index - 1 112 | end 113 | end 114 | end 115 | if not vim.tbl_islist(value.after) then 116 | value.after = { value.after } 117 | end 118 | vim.bo.filetype = value.filetype or 'text' 119 | vim.cmd(':bd!') 120 | if cb.before_each then 121 | cb.before_each(value) 122 | end 123 | ---@diagnostic disable-next-line: missing-parameter 124 | if vim.fn.filereadable(vim.fn.expand(value.filepath)) == 1 then 125 | vim.cmd(':e ' .. value.filepath) 126 | if value.filetype then 127 | vim.bo.filetype = value.filetype 128 | end 129 | vim.cmd(':e') 130 | else 131 | vim.cmd(':new') 132 | if value.filetype then 133 | vim.bo.filetype = value.filetype 134 | end 135 | end 136 | local parser = vim.treesitter.get_parser(0, nil, { error = false }) 137 | if parser ~= nil then 138 | parser:parse(true) 139 | end 140 | vim.api.nvim_buf_set_lines( 141 | 0, 142 | value.linenr - 1, 143 | value.linenr + #text_before, 144 | false, 145 | text_before 146 | ) 147 | vim.api.nvim_win_set_cursor( 148 | 0, 149 | { pos_before.linenr, pos_before.colnr - 1 } 150 | ) 151 | if type(value.key) == "function" then 152 | log.debug("call key") 153 | value.key() 154 | else 155 | log.debug('insert:' .. value.key) 156 | helpers.insert(value.key, value.not_replace_term_code) 157 | vim.wait(2) 158 | helpers.feed('') 159 | end 160 | compare_text( 161 | value.linenr, 162 | value.after, 163 | value.name, 164 | cb.cursor_add, 165 | value.end_cursor 166 | ) 167 | if cb.after_each then 168 | cb.after_each(value) 169 | end 170 | vim.cmd(':bd!') 171 | end) 172 | end 173 | end 174 | 175 | _G.dump_node = function(node) 176 | local text = ts_get_node_text(node) 177 | for _, txt in pairs(text) do 178 | print(txt) 179 | end 180 | end 181 | 182 | _G.dump_node_text = function(target) 183 | for node in target:iter_children() do 184 | local node_type = node:type() 185 | local text = ts_get_node_text(node) 186 | log.debug('type:' .. node_type .. ' ') 187 | log.debug(text) 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/rule.lua: -------------------------------------------------------------------------------- 1 | local Cond = require('nvim-autopairs.conds') 2 | 3 | --- @class Rule 4 | --- @field start_pair string 5 | --- @field end_pair string 6 | --- @field end_pair_func function dynamic change end_pair 7 | --- @field map_cr_func function dynamic change mapping_cr 8 | --- @field end_pair_length number change end_pair length for key map like 9 | --- @field key_map string|nil equal nil mean it will skip on autopairs map 10 | --- @field filetypes table|nil 11 | --- @field not_filetypes table|nil 12 | --- @field is_regex boolean use regex to compare 13 | --- @field is_multibyte boolean 14 | --- @field is_endwise boolean only use on end_wise 15 | --- @field is_undo boolean add break undo sequence 16 | local Rule = setmetatable({}, { 17 | __call = function(self, ...) 18 | return self.new(...) 19 | end, 20 | }) 21 | 22 | ---@return Rule 23 | function Rule.new(...) 24 | local params = { ... } 25 | local opt = {} 26 | if type(params[1]) == 'table' then 27 | opt = params[1] 28 | else 29 | opt.start_pair = params[1] 30 | opt.end_pair = params[2] 31 | if type(params[3]) == 'string' then 32 | opt.filetypes = { params[3] } 33 | else 34 | opt.filetypes = params[3] 35 | end 36 | end 37 | opt = vim.tbl_extend('force', { 38 | key_map = "", 39 | start_pair = nil, 40 | end_pair = nil, 41 | end_pair_func = false, 42 | filetypes = nil, 43 | not_filetypes = nil, 44 | move_cond = nil, 45 | del_cond = {}, 46 | cr_cond = {}, 47 | pair_cond = {}, 48 | is_endwise = false, 49 | is_regex = false, 50 | is_multibyte = false, 51 | end_pair_length = nil, 52 | }, opt) or {} 53 | 54 | ---@param rule Rule 55 | local function constructor(rule) 56 | -- check multibyte 57 | if #rule.start_pair ~= vim.api.nvim_strwidth(rule.start_pair) then 58 | rule:use_multibyte() 59 | end 60 | -- check filetypes and not_filetypes 61 | -- if have something like "-vim" it will add to not_filetypes 62 | if rule.filetypes then 63 | local ft, not_ft = {}, {} 64 | for _, value in pairs(rule.filetypes) do 65 | if value:sub(1, 1) == '-' then 66 | table.insert(not_ft, value:sub(2, #value)) 67 | else 68 | table.insert(ft, value) 69 | end 70 | end 71 | rule.filetypes = #ft > 0 and ft or nil 72 | rule.not_filetypes = #not_ft > 0 and not_ft or nil 73 | end 74 | return rule 75 | end 76 | 77 | local r = setmetatable(opt, { __index = Rule }) 78 | return constructor(r) 79 | end 80 | 81 | function Rule:use_regex(value, key_map) 82 | self.is_regex = value 83 | self.key_map = key_map or '' 84 | return self 85 | end 86 | 87 | function Rule:use_key(key_map) 88 | self.key_map = key_map or '' 89 | return self 90 | end 91 | 92 | function Rule:use_undo(value) 93 | self.is_undo = value 94 | return self 95 | end 96 | 97 | function Rule:use_multibyte() 98 | self.is_multibyte = true 99 | self.end_pair_length = vim.fn.strdisplaywidth(self.end_pair) 100 | self.key_map = string.match(self.start_pair, "[^\128-\191][\128-\191]*$") 101 | self.key_end = string.match(self.end_pair, "[%z\1-\127\194-\244][\128-\191]*") 102 | return self 103 | end 104 | 105 | function Rule:get_end_pair(opts) 106 | if self.end_pair_func then 107 | return self.end_pair_func(opts) 108 | end 109 | return self.end_pair 110 | end 111 | 112 | function Rule:get_map_cr(opts) 113 | if self.map_cr_func then 114 | return self.map_cr_func(opts) 115 | end 116 | return 'unormal! ====' 117 | end 118 | function Rule:replace_map_cr(value) 119 | self.map_cr_func = value 120 | return self 121 | end 122 | 123 | function Rule:get_end_pair_length(opts) 124 | if self.end_pair_length then 125 | return self.end_pair_length 126 | end 127 | if type(opts) == 'string' then 128 | return #opts 129 | end 130 | return #self.get_end_pair(opts) 131 | end 132 | 133 | function Rule:replace_endpair(value, check_pair) 134 | self.end_pair_func = value 135 | if check_pair ~= nil then 136 | if check_pair == true then 137 | self:with_pair(Cond.after_text(self.end_pair)) 138 | else 139 | self:with_pair(check_pair) 140 | end 141 | end 142 | return self 143 | end 144 | 145 | function Rule:set_end_pair_length(length) 146 | self.end_pair_length = length 147 | return self 148 | end 149 | 150 | function Rule:with_move(cond) 151 | if self.move_cond == nil then self.move_cond = {} end 152 | table.insert(self.move_cond, cond) 153 | return self 154 | end 155 | 156 | function Rule:with_del(cond) 157 | if self.del_cond == nil then self.del_cond = {} end 158 | table.insert(self.del_cond, cond) 159 | return self 160 | end 161 | 162 | function Rule:with_cr(cond) 163 | if self.cr_cond == nil then self.cr_cond = {} end 164 | table.insert(self.cr_cond, cond) 165 | return self 166 | end 167 | 168 | ---add condition to rule 169 | ---@param cond any 170 | ---@param pos number|nil = 1. It have higher priority to another condition 171 | ---@return Rule 172 | function Rule:with_pair(cond, pos) 173 | if self.pair_cond == nil then self.pair_cond = {} end 174 | if pos then 175 | table.insert(self.pair_cond, pos, cond) 176 | else 177 | table.insert(self.pair_cond, cond) 178 | end 179 | return self 180 | end 181 | 182 | function Rule:only_cr(cond) 183 | self.key_map = nil 184 | self.pair_cond = false 185 | self.move_cond = false 186 | self.del_cond = false 187 | if cond then return self:with_cr(cond) end 188 | return self 189 | end 190 | 191 | function Rule:end_wise(cond) 192 | self.is_endwise = true 193 | return self:only_cr(cond) 194 | end 195 | 196 | local function can_do(conds, opt) 197 | if type(conds) == 'table' then 198 | for _, cond in pairs(conds) do 199 | local result = cond(opt) 200 | if result ~= nil then 201 | return result 202 | end 203 | end 204 | return true 205 | elseif type(conds) == 'function' then 206 | return conds(opt) == true 207 | end 208 | return false 209 | end 210 | 211 | function Rule:can_pair(opt) 212 | return can_do(self.pair_cond, opt) 213 | end 214 | 215 | function Rule:can_move(opt) 216 | return can_do(self.move_cond, opt) 217 | end 218 | 219 | function Rule:can_del(opt) 220 | return can_do(self.del_cond, opt) 221 | end 222 | 223 | function Rule:can_cr(opt) 224 | return can_do(self.cr_cond, opt) 225 | end 226 | 227 | return Rule 228 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/fastwrap.lua: -------------------------------------------------------------------------------- 1 | local utils = require('nvim-autopairs.utils') 2 | local log = require('nvim-autopairs._log') 3 | local npairs = require('nvim-autopairs') 4 | local M = {} 5 | 6 | local default_config = { 7 | map = '', 8 | chars = { '{', '[', '(', '"', "'" }, 9 | pattern = [=[[%'%"%>%]%)%}%,%`]]=], 10 | end_key = '$', 11 | avoid_move_to_end = true, -- choose your move behaviour for non-alphabetical end_keys' 12 | before_key = 'h', 13 | after_key = 'l', 14 | cursor_pos_before = true, 15 | keys = 'qwertyuiopzxcvbnmasdfghjkl', 16 | highlight = 'Search', 17 | highlight_grey = 'Comment', 18 | manual_position = true, 19 | use_virt_lines = true 20 | } 21 | 22 | M.ns_fast_wrap = vim.api.nvim_create_namespace('autopairs_fastwrap') 23 | 24 | local config = {} 25 | 26 | M.setup = function(cfg) 27 | if config.chars == nil then 28 | config = vim.tbl_extend('force', default_config, cfg or {}) or {} 29 | npairs.config.fast_wrap = config 30 | end 31 | end 32 | 33 | function M.getchar_handler() 34 | local ok, key = pcall(vim.fn.getchar) 35 | if not ok then 36 | return nil 37 | end 38 | if key ~= 27 and type(key) == 'number' then 39 | local key_str = vim.fn.nr2char(key) 40 | return key_str 41 | end 42 | return nil 43 | end 44 | 45 | M.show = function(line) 46 | line = line or utils.text_get_current_line(0) 47 | log.debug(line) 48 | local row, col = utils.get_cursor() 49 | local prev_char = utils.text_cusor_line(line, col, 1, 1, false) 50 | local end_pair = '' 51 | if utils.is_in_table(config.chars, prev_char) then 52 | local rules = npairs.get_buf_rules() 53 | for _, rule in pairs(rules) do 54 | if rule.start_pair == prev_char then 55 | end_pair = rule.end_pair 56 | end 57 | end 58 | if end_pair == '' then 59 | return 60 | end 61 | local list_pos = {} --holds target locations 62 | local index = 1 63 | local str_length = #line 64 | local offset = -1 65 | for i = col + 2, #line, 1 do 66 | local char = line:sub(i, i) 67 | local char2 = line:sub(i - 1, i) 68 | if string.match(char, config.pattern) 69 | or (char == ' ' and string.match(char2, '%w')) 70 | then 71 | local key = config.keys:sub(index, index) 72 | index = index + 1 73 | if not config.manual_position and ( 74 | utils.is_quote(char) 75 | or ( 76 | utils.is_close_bracket(char) 77 | and utils.is_in_quotes(line, col, prev_char) 78 | ) 79 | ) 80 | then 81 | offset = 0 82 | end 83 | 84 | if config.manual_position and i == str_length then 85 | key = config.end_key 86 | end 87 | 88 | table.insert( 89 | list_pos, 90 | { col = i + offset, key = key, char = char, pos = i } 91 | ) 92 | end 93 | end 94 | log.debug(list_pos) 95 | 96 | local end_col, end_pos 97 | if config.manual_position then 98 | end_col = str_length + offset 99 | end_pos = str_length 100 | else 101 | end_col = str_length + 1 102 | end_pos = str_length + 1 103 | end 104 | -- add end_key to list extmark 105 | if #list_pos == 0 or list_pos[#list_pos].key ~= config.end_key then 106 | table.insert( 107 | list_pos, 108 | { col = end_col, key = config.end_key, pos = end_pos, char = config.end_key } 109 | ) 110 | end 111 | 112 | -- Create a whitespace string for the current line which replaces every non whitespace 113 | -- character with a space and preserves tabs, so we can use it for highlighting with 114 | -- virtual lines so that highlighting lines up correctly. 115 | -- The string is limited to the last position in list_pos 116 | local whitespace_line = line:sub(1, list_pos[#list_pos].end_pos):gsub("[^ \t]", " ") 117 | 118 | M.highlight_wrap(list_pos, row, col, #line, whitespace_line) 119 | vim.defer_fn(function() 120 | -- get the first char 121 | local char = #list_pos == 1 and config.end_key or M.getchar_handler() 122 | vim.api.nvim_buf_clear_namespace(0, M.ns_fast_wrap, row, row + 1) 123 | 124 | for _, pos in pairs(list_pos) do 125 | -- handle end_key specially 126 | if char == config.end_key and char == pos.key then 127 | vim.print("Run to end!") 128 | -- M.highlight_wrap({pos = pos.pos, key = config.end_key}, row, col, #line, whitespace_line) 129 | local move_end_key = (not config.avoid_move_to_end and char == string.upper(config.end_key)) 130 | M.move_bracket(line, pos.col+1, end_pair, move_end_key) 131 | break 132 | end 133 | local hl_mark = { 134 | { pos = pos.pos - 1, key = config.before_key }, 135 | { pos = pos.pos + 1, key = config.after_key }, 136 | } 137 | if config.manual_position and (char == pos.key or char == string.upper(pos.key)) then 138 | M.highlight_wrap(hl_mark, row, col, #line, whitespace_line) 139 | M.choose_pos(row, line, pos, end_pair) 140 | break 141 | end 142 | if char == pos.key then 143 | M.move_bracket(line, pos.col, end_pair, false) 144 | break 145 | end 146 | if char == string.upper(pos.key) then 147 | M.move_bracket(line, pos.col, end_pair, true) 148 | break 149 | end 150 | end 151 | vim.cmd('startinsert') 152 | end, 10) 153 | return 154 | end 155 | vim.cmd('startinsert') 156 | end 157 | 158 | M.choose_pos = function(row, line, pos, end_pair) 159 | vim.defer_fn(function() 160 | -- select a second key 161 | local char = 162 | pos.char == nil and config.before_key 163 | or pos.char == config.end_key and config.after_key 164 | or M.getchar_handler() 165 | vim.api.nvim_buf_clear_namespace(0, M.ns_fast_wrap, row, row + 1) 166 | if not char then return end 167 | local change_pos = false 168 | local col = pos.col 169 | if char == string.upper(config.before_key) or char == string.upper(config.after_key) then 170 | change_pos = true 171 | end 172 | if char == config.after_key or char == string.upper(config.after_key) then 173 | col = pos.col + 1 174 | end 175 | M.move_bracket(line, col, end_pair, change_pos) 176 | vim.cmd('startinsert') 177 | end, 10) 178 | end 179 | 180 | M.move_bracket = function(line, target_pos, end_pair, change_pos) 181 | log.debug(target_pos) 182 | line = line or utils.text_get_current_line(0) 183 | local row, col = utils.get_cursor() 184 | local _, next_char = utils.text_cusor_line(line, col, 1, 1, false) 185 | -- remove an autopairs if that exist 186 | if next_char == end_pair then 187 | line = line:sub(1, col) .. line:sub(col + 2, #line) 188 | target_pos = target_pos - 1 189 | end 190 | 191 | line = line:sub(1, target_pos) .. end_pair .. line:sub(target_pos + 1, #line) 192 | vim.api.nvim_set_current_line(line) 193 | if change_pos then 194 | vim.api.nvim_win_set_cursor(0, { row + 1, target_pos + (config.cursor_pos_before and 0 or 1) }) 195 | end 196 | end 197 | 198 | M.highlight_wrap = function(tbl_pos, row, col, end_col, whitespace_line) 199 | local bufnr = vim.api.nvim_win_get_buf(0) 200 | if config.use_virt_lines then 201 | local virt_lines = {} 202 | local start = 0 203 | local left_col = vim.fn.winsaveview().leftcol 204 | if left_col > 0 then 205 | vim.fn.winrestview({ leftcol = 0 }) 206 | end 207 | for _, pos in ipairs(tbl_pos) do 208 | virt_lines[#virt_lines + 1] = { whitespace_line:sub(start + 1, pos.pos - 1), 'Normal' } 209 | virt_lines[#virt_lines + 1] = { pos.key, config.highlight } 210 | start = pos.pos 211 | end 212 | vim.api.nvim_buf_set_extmark(bufnr, M.ns_fast_wrap, row, 0, { 213 | virt_lines = { virt_lines }, 214 | hl_mode = 'blend', 215 | }) 216 | else 217 | if config.highlight_grey then 218 | (vim.hl or vim.highlight).range( 219 | bufnr, 220 | M.ns_fast_wrap, 221 | config.highlight_grey, 222 | { row, col }, 223 | { row, end_col }, 224 | {} 225 | ) 226 | end 227 | for _, pos in ipairs(tbl_pos) do 228 | vim.api.nvim_buf_set_extmark(bufnr, M.ns_fast_wrap, row, pos.pos - 1, { 229 | virt_text = { { pos.key, config.highlight } }, 230 | virt_text_pos = 'overlay', 231 | hl_mode = 'blend', 232 | }) 233 | end 234 | end 235 | end 236 | 237 | return M 238 | -------------------------------------------------------------------------------- /lua/nvim-autopairs/conds.lua: -------------------------------------------------------------------------------- 1 | local utils = require('nvim-autopairs.utils') 2 | local log = require('nvim-autopairs._log') 3 | ---@class CondOpts 4 | ---@field ts_node table 5 | ---@field text string 6 | ---@field rule table 7 | ---@field bufnr number 8 | ---@field col number 9 | ---@field char string 10 | ---@field line string 11 | ---@field prev_char string 12 | ---@field next_char string 13 | ---@field is_endwise string 14 | 15 | local cond = {} 16 | 17 | -- cond 18 | -- @return false when it is not correct 19 | -- true when it is correct 20 | -- nil when it is not determine 21 | -- stylua: ignore 22 | cond.none = function() 23 | return function() return false end 24 | end 25 | -- stylua: ignore 26 | cond.done = function() 27 | return function() return true end 28 | end 29 | 30 | cond.invert = function(func) 31 | return function(...) 32 | local result = func(...) 33 | if result ~= nil then 34 | return not result 35 | end 36 | return nil 37 | end 38 | end 39 | 40 | cond.before_regex = function(regex, length) 41 | length = length or 1 42 | if length < 0 then length = nil end 43 | length = length and -length 44 | ---@param opts CondOpts 45 | return function(opts) 46 | log.debug('before_regex') 47 | local str = utils.text_sub_char(opts.line, opts.col - 1, length or -opts.col) 48 | if str:match(regex) then 49 | return true 50 | end 51 | return false 52 | end 53 | end 54 | 55 | cond.before_text = function(text) 56 | local length = #text 57 | ---@param opts CondOpts 58 | return function(opts) 59 | log.debug('before_text') 60 | local str = utils.text_sub_char(opts.line, opts.col - 1, -length) 61 | if str == text then 62 | return true 63 | end 64 | return false 65 | end 66 | end 67 | 68 | cond.after_text = function(text) 69 | local length = #text 70 | ---@param opts CondOpts 71 | return function(opts) 72 | log.debug('after_text') 73 | local str = utils.text_sub_char(opts.line, opts.col, length) 74 | if str == text then 75 | return true 76 | end 77 | return false 78 | end 79 | end 80 | 81 | cond.after_regex = function(regex, length) 82 | length = length or 1 83 | if length < 0 then length = nil end 84 | ---@param opts CondOpts 85 | return function(opts) 86 | log.debug('after_regex') 87 | local str = utils.text_sub_char(opts.line, opts.col, length or #opts.line) 88 | if str:match(regex) then 89 | return true 90 | end 91 | return false 92 | end 93 | end 94 | 95 | cond.not_before_text = function(text) 96 | local length = #text 97 | return function(opts) 98 | log.debug('not_before_text') 99 | local str = utils.text_sub_char(opts.line, opts.col - 1, -length) 100 | if str == text then 101 | return false 102 | end 103 | end 104 | end 105 | 106 | cond.not_after_text = function(text) 107 | local length = #text 108 | ---@param opts CondOpts 109 | return function(opts) 110 | log.debug('not_after_text') 111 | local str = utils.text_sub_char(opts.line, opts.col, length) 112 | if str == text then 113 | return false 114 | end 115 | end 116 | end 117 | 118 | cond.not_before_regex = function(regex, length) 119 | length = length or 1 120 | if length < 0 then length = nil end 121 | length = length and -length 122 | ---@param opts CondOpts 123 | return function(opts) 124 | log.debug('not_before_regex') 125 | log.debug(length) 126 | local str = utils.text_sub_char(opts.line, opts.col - 1, length or -opts.col) 127 | if str:match(regex) then 128 | return false 129 | end 130 | end 131 | end 132 | 133 | cond.not_after_regex = function(regex, length) 134 | length = length or 1 135 | if length < 0 then length = nil end 136 | ---@param opts CondOpts 137 | return function(opts) 138 | log.debug('not_after_regex') 139 | local str = utils.text_sub_char(opts.line, opts.col, length or #opts.line) 140 | if str:match(regex) then 141 | return false 142 | end 143 | end 144 | end 145 | 146 | local function count_bracket_char(line, prev_char, next_char) 147 | local count_prev_char = 0 148 | local count_next_char = 0 149 | for i = 1, #line, 1 do 150 | local c = line:sub(i, i) 151 | if c == prev_char then 152 | count_prev_char = count_prev_char + 1 153 | elseif c == next_char then 154 | count_next_char = count_next_char + 1 155 | end 156 | end 157 | return count_prev_char, count_next_char 158 | end 159 | 160 | -- Checks if bracket chars are balanced around specific postion. 161 | ---@param line string 162 | ---@param open_char string 163 | ---@param close_char string 164 | ---@param col integer position 165 | local function is_brackets_balanced_around_position(line, open_char, close_char, col) 166 | local balance = 0 167 | for i = 1, #line, 1 do 168 | local c = line:sub(i, i) 169 | if c == open_char then 170 | balance = balance + 1 171 | elseif balance > 0 and c == close_char then 172 | balance = balance - 1 173 | if col <= i and balance == 0 then 174 | break 175 | end 176 | end 177 | end 178 | return balance == 0 179 | end 180 | 181 | cond.is_bracket_line = function() 182 | ---@param opts CondOpts 183 | return function(opts) 184 | log.debug('is_bracket_line') 185 | if utils.is_bracket(opts.char) and 186 | (opts.next_char == opts.rule.end_pair 187 | or opts.next_char == opts.rule.start_pair) 188 | then 189 | -- (( many char |)) => add 190 | -- ( many char |)) => not add 191 | local count_prev_char, count_next_char = count_bracket_char( 192 | opts.line, 193 | opts.rule.start_pair, 194 | opts.rule.end_pair 195 | ) 196 | if count_prev_char ~= count_next_char then 197 | return false 198 | end 199 | end 200 | end 201 | end 202 | 203 | cond.is_bracket_line_move = function() 204 | ---@param opts CondOpts 205 | return function(opts) 206 | log.debug('is_bracket_line_move') 207 | if utils.is_close_bracket(opts.char) 208 | and opts.char == opts.rule.end_pair 209 | then 210 | -- (( many char |)) => move 211 | -- (( many char |) => not move 212 | local is_balanced = is_brackets_balanced_around_position( 213 | opts.line, 214 | opts.rule.start_pair, 215 | opts.char, 216 | opts.col 217 | ) 218 | return is_balanced 219 | end 220 | end 221 | end 222 | 223 | cond.not_inside_quote = function() 224 | ---@param opts CondOpts 225 | return function(opts) 226 | log.debug('not_inside_quote') 227 | if utils.is_in_quotes(opts.text, opts.col - 1) then 228 | return false 229 | end 230 | end 231 | end 232 | 233 | cond.is_inside_quote = function() 234 | ---@param opts CondOpts 235 | return function(opts) 236 | log.debug('is_inside_quote') 237 | if utils.is_in_quotes(opts.text, opts.col - 1) then 238 | return true 239 | end 240 | end 241 | end 242 | 243 | cond.not_add_quote_inside_quote = function() 244 | ---@param opts CondOpts 245 | return function(opts) 246 | log.debug('not_add_quote_inside_quote') 247 | if utils.is_quote(opts.char) 248 | and utils.is_in_quotes(opts.text, opts.col - 1) 249 | then 250 | return false 251 | end 252 | end 253 | end 254 | 255 | cond.move_right = function() 256 | ---@param opts CondOpts 257 | return function(opts) 258 | log.debug('move_right') 259 | if opts.next_char == opts.char then 260 | if utils.is_close_bracket(opts.char) then 261 | return 262 | end 263 | -- move right when have quote on end line or in quote 264 | -- situtaion |" => "| 265 | if utils.is_quote(opts.char) then 266 | if opts.col == string.len(opts.line) then 267 | return 268 | end 269 | -- ("|") => (""|) 270 | -- "" |" " => "" "| " 271 | if utils.is_in_quotes(opts.line, opts.col - 1, opts.char) then 272 | return 273 | end 274 | end 275 | end 276 | return false 277 | end 278 | end 279 | 280 | cond.is_end_line = function() 281 | ---@param opts CondOpts 282 | return function(opts) 283 | log.debug('is_end_line') 284 | local end_text = opts.line:sub(opts.col + 1) 285 | -- end text is blank 286 | if end_text ~= '' and end_text:match('^%s+$') == nil then 287 | return false 288 | end 289 | end 290 | end 291 | 292 | --- Check the next char is quote and cursor is inside quote 293 | cond.is_bracket_in_quote = function() 294 | ---@param opts CondOpts 295 | return function(opts) 296 | log.debug("is_bracket_in_quote") 297 | if utils.is_bracket(opts.char) 298 | and utils.is_quote(opts.next_char) 299 | and utils.is_in_quotes(opts.line, opts.col - 1, opts.next_char) 300 | then 301 | return true 302 | end 303 | end 304 | end 305 | 306 | cond.not_filetypes = function(filetypes) 307 | return function() 308 | log.debug('not_filetypes') 309 | for _, filetype in pairs(filetypes) do 310 | if vim.bo.filetype == filetype then 311 | return false 312 | end 313 | end 314 | end 315 | end 316 | 317 | --- Check the character before the cursor is not equal 318 | ---@param char string character to compare 319 | ---@param index number the position of character before current curosr 320 | cond.not_before_char = function(char, index) 321 | index = index or 1 322 | ---@param opts CondOpts 323 | return function(opts) 324 | log.debug('not_before_char') 325 | local match_char = #opts.line > index 326 | and opts.line:sub(#opts.line - index, #opts.line - index) or '' 327 | if match_char == char and match_char ~= "" then 328 | return false 329 | end 330 | end 331 | end 332 | 333 | ---@deprecated 334 | cond.not_after_regex_check = cond.not_after_regex 335 | ---@deprecated 336 | cond.after_regex_check = cond.after_regex 337 | ---@deprecated 338 | cond.before_regex_check = cond.before_regex 339 | ---@deprecated 340 | cond.not_before_regex_check = cond.not_before_regex 341 | ---@deprecated 342 | cond.after_text_check = cond.after_text 343 | ---@deprecated 344 | cond.not_after_text_check = cond.not_after_text 345 | ---@deprecated 346 | cond.before_text_check = cond.before_text 347 | ---@deprecated 348 | cond.not_before_text_check = cond.not_before_text 349 | 350 | return cond 351 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## nvim-autopairs 2 | 3 | A super powerful autopair plugin for Neovim that supports multiple characters. 4 | 5 | Requires neovim 0.7 6 | 7 | ## Installation 8 | 9 | Install the plugin with your preferred package manager: 10 | 11 | ### [lazy.nvim](https://github.com/folke/lazy.nvim) 12 | 13 | ```lua 14 | { 15 | 'windwp/nvim-autopairs', 16 | event = "InsertEnter", 17 | config = true 18 | -- use opts = {} for passing setup options 19 | -- this is equivalent to setup({}) function 20 | } 21 | ``` 22 | 23 | ### [vim-plug](https://github.com/junegunn/vim-plug) 24 | 25 | ```vim 26 | Plug 'windwp/nvim-autopairs' 27 | 28 | lua << EOF 29 | require("nvim-autopairs").setup {} 30 | EOF 31 | ``` 32 | 33 | ### [packer](https://github.com/wbthomason/packer.nvim) 34 | 35 | ```lua 36 | use { 37 | "windwp/nvim-autopairs", 38 | event = "InsertEnter", 39 | config = function() 40 | require("nvim-autopairs").setup {} 41 | end 42 | } 43 | ``` 44 | 45 | ## Default values 46 | 47 | ``` lua 48 | { 49 | enabled = function(bufnr) return true end, -- control if auto-pairs should be enabled when attaching to a buffer 50 | disable_filetype = { "TelescopePrompt", "spectre_panel", "snacks_picker_input" }, 51 | disable_in_macro = true, -- disable when recording or executing a macro 52 | disable_in_visualblock = false, -- disable when insert after visual block mode 53 | disable_in_replace_mode = true, 54 | ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=], 55 | enable_moveright = true, 56 | enable_afterquote = true, -- add bracket pairs after quote 57 | enable_check_bracket_line = true, --- check bracket in same line 58 | enable_bracket_in_quote = true, -- 59 | enable_abbr = false, -- trigger abbreviation 60 | break_undo = true, -- switch for basic rule break undo sequence 61 | check_ts = false, 62 | map_cr = true, 63 | map_bs = true, -- map the key 64 | map_c_h = false, -- Map the key to delete a pair 65 | map_c_w = false, -- map to delete a pair if possible 66 | } 67 | ``` 68 | 69 | ### Override default values 70 | 71 | ``` lua 72 | require('nvim-autopairs').setup({ 73 | disable_filetype = { "TelescopePrompt" , "vim" }, 74 | }) 75 | ``` 76 | 77 | 78 | #### Mapping `` 79 | ``` 80 | Before Input After 81 | ------------------------------------ 82 | {|} { 83 | | 84 | } 85 | ------------------------------------ 86 | ``` 87 | 88 |
89 | nvim-cmp 90 |

91 | You need to add mapping `CR` on nvim-cmp setup. 92 | Check readme.md on nvim-cmp repo. 93 |

94 | 95 | ``` lua 96 | -- If you want insert `(` after select function or method item 97 | local cmp_autopairs = require('nvim-autopairs.completion.cmp') 98 | local cmp = require('cmp') 99 | cmp.event:on( 100 | 'confirm_done', 101 | cmp_autopairs.on_confirm_done() 102 | ) 103 | ``` 104 | 105 | You can customize the kind of completion to add `(` or any character. 106 | 107 | ```lua 108 | local handlers = require('nvim-autopairs.completion.handlers') 109 | 110 | cmp.event:on( 111 | 'confirm_done', 112 | cmp_autopairs.on_confirm_done({ 113 | filetypes = { 114 | -- "*" is a alias to all filetypes 115 | ["*"] = { 116 | ["("] = { 117 | kind = { 118 | cmp.lsp.CompletionItemKind.Function, 119 | cmp.lsp.CompletionItemKind.Method, 120 | }, 121 | handler = handlers["*"] 122 | } 123 | }, 124 | lua = { 125 | ["("] = { 126 | kind = { 127 | cmp.lsp.CompletionItemKind.Function, 128 | cmp.lsp.CompletionItemKind.Method 129 | }, 130 | ---@param char string 131 | ---@param item table item completion 132 | ---@param bufnr number buffer number 133 | ---@param rules table 134 | ---@param commit_character table 135 | handler = function(char, item, bufnr, rules, commit_character) 136 | -- Your handler function. Inspect with print(vim.inspect{char, item, bufnr, rules, commit_character}) 137 | end 138 | } 139 | }, 140 | -- Disable for tex 141 | tex = false 142 | } 143 | }) 144 | ) 145 | ``` 146 | 147 | Don't use `nil` to disable a filetype. If a filetype is `nil` then `*` is used as fallback. 148 | 149 |
150 |
151 | coq_nvim 152 | 153 | ``` lua 154 | local remap = vim.api.nvim_set_keymap 155 | local npairs = require('nvim-autopairs') 156 | 157 | npairs.setup({ map_bs = false, map_cr = false }) 158 | 159 | vim.g.coq_settings = { keymap = { recommended = false } } 160 | 161 | -- these mappings are coq recommended mappings unrelated to nvim-autopairs 162 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 163 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 164 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 165 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 166 | 167 | -- skip it, if you use another global object 168 | _G.MUtils= {} 169 | 170 | MUtils.CR = function() 171 | if vim.fn.pumvisible() ~= 0 then 172 | if vim.fn.complete_info({ 'selected' }).selected ~= -1 then 173 | return npairs.esc('') 174 | else 175 | return npairs.esc('') .. npairs.autopairs_cr() 176 | end 177 | else 178 | return npairs.autopairs_cr() 179 | end 180 | end 181 | remap('i', '', 'v:lua.MUtils.CR()', { expr = true, noremap = true }) 182 | 183 | MUtils.BS = function() 184 | if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info({ 'mode' }).mode == 'eval' then 185 | return npairs.esc('') .. npairs.autopairs_bs() 186 | else 187 | return npairs.autopairs_bs() 188 | end 189 | end 190 | remap('i', '', 'v:lua.MUtils.BS()', { expr = true, noremap = true }) 191 | ``` 192 |
193 |
194 | without completion plugin 195 | 196 | ```lua 197 | -- add option map_cr 198 | npairs.setup({ map_cr = true }) 199 | ``` 200 |
201 | 202 | [another completion plugin](https://github.com/windwp/nvim-autopairs/wiki/Completion-plugin) 203 | 204 | If you have a problem with indent after you press ` ` 205 | please check the settings of treesitter indent or install a plugin that has indent support for your filetype. 206 | 207 | ### Rule 208 | 209 | nvim-autopairs uses rules with conditions to check pairs. 210 | 211 | ``` lua 212 | local Rule = require('nvim-autopairs.rule') 213 | local npairs = require('nvim-autopairs') 214 | 215 | npairs.add_rule(Rule("$$","$$","tex")) 216 | 217 | -- you can use some built-in conditions 218 | 219 | local cond = require('nvim-autopairs.conds') 220 | print(vim.inspect(cond)) 221 | 222 | npairs.add_rules({ 223 | Rule("$", "$",{"tex", "latex"}) 224 | -- don't add a pair if the next character is % 225 | :with_pair(cond.not_after_regex("%%")) 226 | -- don't add a pair if the previous character is xxx 227 | :with_pair(cond.not_before_regex("xxx", 3)) 228 | -- don't move right when repeat character 229 | :with_move(cond.none()) 230 | -- don't delete if the next character is xx 231 | :with_del(cond.not_after_regex("xx")) 232 | -- disable adding a newline when you press 233 | :with_cr(cond.none()) 234 | }, 235 | -- disable for .vim files, but it work for another filetypes 236 | Rule("a","a","-vim") 237 | ) 238 | 239 | npairs.add_rules({ 240 | Rule("$$","$$","tex") 241 | :with_pair(function(opts) 242 | print(vim.inspect(opts)) 243 | if opts.line=="aa $$" then 244 | -- don't add pair on that line 245 | return false 246 | end 247 | end) 248 | } 249 | ) 250 | 251 | -- you can use regex 252 | -- press u1234 => u1234number 253 | npairs.add_rules({ 254 | Rule("u%d%d%d%d$", "number", "lua") 255 | :use_regex(true) 256 | }) 257 | 258 | 259 | 260 | -- press x1234 => x12341234 261 | npairs.add_rules({ 262 | Rule("x%d%d%d%d$", "number", "lua") 263 | :use_regex(true) 264 | :replace_endpair(function(opts) 265 | -- print(vim.inspect(opts)) 266 | return opts.prev_char:sub(#opts.prev_char - 3,#opts.prev_char) 267 | end) 268 | }) 269 | 270 | 271 | -- you can do anything with regex +special key 272 | -- example press tab to uppercase text: 273 | -- press b1234s => B1234S1234S 274 | 275 | npairs.add_rules({ 276 | Rule("b%d%d%d%d%w$", "", "vim") 277 | :use_regex(true,"") 278 | :replace_endpair(function(opts) 279 | return 280 | opts.prev_char:sub(#opts.prev_char - 4,#opts.prev_char) 281 | .."viwU" 282 | end) 283 | }) 284 | 285 | -- you can exclude filetypes 286 | npairs.add_rule( 287 | Rule("$$","$$") 288 | :with_pair(cond.not_filetypes({"lua"})) 289 | ) 290 | --- check ./lua/nvim-autopairs/rules/basic.lua 291 | 292 | ``` 293 | [Rules API](https://github.com/windwp/nvim-autopairs/wiki/Rules-API) 294 | 295 | ### Treesitter 296 | You can use treesitter to check for a pair. 297 | 298 | ```lua 299 | local npairs = require("nvim-autopairs") 300 | local Rule = require('nvim-autopairs.rule') 301 | 302 | npairs.setup({ 303 | check_ts = true, 304 | ts_config = { 305 | lua = {'string'},-- it will not add a pair on that treesitter node 306 | javascript = {'template_string'}, 307 | java = false,-- don't check treesitter on java 308 | } 309 | }) 310 | 311 | local ts_conds = require('nvim-autopairs.ts-conds') 312 | 313 | 314 | -- press % => %% only while inside a comment or string 315 | npairs.add_rules({ 316 | Rule("%", "%", "lua") 317 | :with_pair(ts_conds.is_ts_node({'string','comment'})), 318 | Rule("$", "$", "lua") 319 | :with_pair(ts_conds.is_not_ts_node({'function'})) 320 | }) 321 | ``` 322 | 323 | ### Don't add pairs if it already has a close pair in the same line 324 | if **next character** is a close pair and it doesn't have an open pair in same line, then it will not add a close pair 325 | 326 | ``` text 327 | Before Input After 328 | ------------------------------------ 329 | ( |)) ( ( (|)) 330 | 331 | ``` 332 | 333 | ``` lua 334 | require('nvim-autopairs').setup({ 335 | enable_check_bracket_line = false 336 | }) 337 | ``` 338 | 339 | ### Don't add pairs if the next char is alphanumeric 340 | 341 | You can customize how nvim-autopairs will behave if it encounters a specific 342 | character 343 | ``` lua 344 | require('nvim-autopairs').setup({ 345 | ignored_next_char = "[%w%.]" -- will ignore alphanumeric and `.` symbol 346 | }) 347 | ``` 348 | 349 | ``` text 350 | Before Input After 351 | ------------------------------------ 352 | |foobar ( (|foobar 353 | |.foobar ( (|.foobar 354 | ``` 355 | 356 | ### Plugin Integration 357 | ``` lua 358 | require('nvim-autopairs').disable() 359 | require('nvim-autopairs').enable() 360 | require('nvim-autopairs').toggle() 361 | require('nvim-autopairs').remove_rule('(') -- remove rule ( 362 | require('nvim-autopairs').clear_rules() -- clear all rules 363 | require('nvim-autopairs').get_rules('"') 364 | ``` 365 | 366 | * Sample 367 | ```lua 368 | -- remove add single quote on filetype scheme or lisp 369 | require("nvim-autopairs").get_rules("'")[1].not_filetypes = { "scheme", "lisp" } 370 | require("nvim-autopairs").get_rules("'")[1]:with_pair(cond.not_after_text("[")) 371 | ``` 372 | 373 | ### FastWrap 374 | 375 | ``` text 376 | Before Input After Note 377 | ----------------------------------------------------------------- 378 | (|foobar then press $ (|foobar) 379 | (|)(foobar) then press q (|(foobar)) 380 | (|foo bar then press qh (|foo) bar 381 | (|foo bar then press qH (foo|) bar 382 | (|foo bar then press qH (foo)| bar if cursor_pos_before = false 383 | ``` 384 | 385 | ```lua 386 | -- put this to setup function and press to use fast_wrap 387 | npairs.setup({ 388 | fast_wrap = {}, 389 | }) 390 | 391 | -- change default fast_wrap 392 | npairs.setup({ 393 | fast_wrap = { 394 | map = '', 395 | chars = { '{', '[', '(', '"', "'" }, 396 | pattern = [=[[%'%"%>%]%)%}%,]]=], 397 | end_key = '$', 398 | before_key = 'h', 399 | after_key = 'l', 400 | cursor_pos_before = true, 401 | keys = 'qwertyuiopzxcvbnmasdfghjkl', 402 | manual_position = true, 403 | highlight = 'Search', 404 | highlight_grey='Comment' 405 | }, 406 | }) 407 | ``` 408 | 409 | ### autotag html and tsx 410 | 411 | [autotag](https://github.com/windwp/nvim-ts-autotag) 412 | 413 | ### Endwise 414 | 415 | [endwise](https://github.com/windwp/nvim-autopairs/wiki/Endwise) 416 | 417 | ### Custom rules 418 | [rules](https://github.com/windwp/nvim-autopairs/wiki/Custom-rules) 419 | 420 | ## Sponsors 421 | 422 | Thanks to everyone who sponsors my projects and makes continued development maintenance possible! 423 | 424 | 425 | -------------------------------------------------------------------------------- /doc/nvim-autopairs.txt: -------------------------------------------------------------------------------- 1 | *nvim-autopairs.txt* A super powerful autopair for Neovim. 2 | 3 | ============================================================================== 4 | Table of Contents *nvim-autopairs-table-of-contents* 5 | 6 | - nvim-autopairs |nvim-autopairs-nvim-autopairs| 7 | - Installation |nvim-autopairs-installation| 8 | - Default values |nvim-autopairs-default-values| 9 | - Sponsors |nvim-autopairs-sponsors| 10 | 11 | NVIM-AUTOPAIRS *nvim-autopairs-nvim-autopairs* 12 | 13 | A super powerful autopair plugin for Neovim that supports multiple characters. 14 | 15 | Requires neovim 0.7 16 | 17 | INSTALLATION *nvim-autopairs-installation* 18 | 19 | Install the plugin with your preferred package manager: 20 | 21 | LAZY.NVIM ~ 22 | 23 | > 24 | { 25 | 'windwp/nvim-autopairs', 26 | event = "InsertEnter", 27 | config = true 28 | -- use opts = {} for passing setup options 29 | -- this is equivalent to setup({}) function 30 | } 31 | < 32 | 33 | 34 | VIM-PLUG ~ 35 | 36 | > 37 | Plug 'windwp/nvim-autopairs' 38 | 39 | lua << EOF 40 | require("nvim-autopairs").setup {} 41 | EOF 42 | < 43 | 44 | 45 | PACKER ~ 46 | 47 | > 48 | use { 49 | "windwp/nvim-autopairs", 50 | event = "InsertEnter", 51 | config = function() 52 | require("nvim-autopairs").setup {} 53 | end 54 | } 55 | < 56 | 57 | 58 | DEFAULT VALUES *nvim-autopairs-default-values* 59 | 60 | > 61 | { 62 | enabled = function(bufnr) return true end, -- control if auto-pairs should be enabled when attaching to a buffer 63 | disable_filetype = { "TelescopePrompt", "spectre_panel" } 64 | disable_in_macro = true -- disable when recording or executing a macro 65 | disable_in_visualblock = false -- disable when insert after visual block mode 66 | disable_in_replace_mode = true 67 | ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=] 68 | enable_moveright = true 69 | enable_afterquote = true -- add bracket pairs after quote 70 | enable_check_bracket_line = true --- check bracket in same line 71 | enable_bracket_in_quote = true -- 72 | enable_abbr = false -- trigger abbreviation 73 | break_undo = true -- switch for basic rule break undo sequence 74 | check_ts = false 75 | map_cr = true 76 | map_bs = true -- map the key 77 | map_c_h = false -- Map the key to delete a pair 78 | map_c_w = false -- map to delete a pair if possible 79 | } 80 | < 81 | 82 | 83 | OVERRIDE DEFAULT VALUES ~ 84 | 85 | > 86 | require('nvim-autopairs').setup({ 87 | disable_filetype = { "TelescopePrompt" , "vim" }, 88 | }) 89 | < 90 | 91 | 92 | *nvim-autopairs-Mapping-``* 93 | 94 | > 95 | Before Input After 96 | ------------------------------------ 97 | {|} { 98 | | 99 | } 100 | ------------------------------------ 101 | < 102 | 103 | 104 | nvim-cmp ~ 105 | 106 |

107 | 108 | You need to add mapping `CR` on nvim-cmp setup. 109 | Check readme.md on nvim-cmp repo. 110 | 111 |

112 | 113 | > 114 | -- If you want insert `(` after select function or method item 115 | local cmp_autopairs = require('nvim-autopairs.completion.cmp') 116 | local cmp = require('cmp') 117 | cmp.event:on( 118 | 'confirm_done', 119 | cmp_autopairs.on_confirm_done() 120 | ) 121 | < 122 | 123 | 124 | Mapping `` You can customize the kind of completion 125 | to add `(` or any character. 126 | 127 | 128 | > 129 | local handlers = require('nvim-autopairs.completion.handlers') 130 | 131 | cmp.event:on( 132 | 'confirm_done', 133 | cmp_autopairs.on_confirm_done({ 134 | filetypes = { 135 | -- "*" is a alias to all filetypes 136 | ["*"] = { 137 | ["("] = { 138 | kind = { 139 | cmp.lsp.CompletionItemKind.Function, 140 | cmp.lsp.CompletionItemKind.Method, 141 | }, 142 | handler = handlers["*"] 143 | } 144 | }, 145 | lua = { 146 | ["("] = { 147 | kind = { 148 | cmp.lsp.CompletionItemKind.Function, 149 | cmp.lsp.CompletionItemKind.Method 150 | }, 151 | ---@param char string 152 | ---@param item table item completion 153 | ---@param bufnr number buffer number 154 | ---@param rules table 155 | ---@param commit_character table 156 | handler = function(char, item, bufnr, rules, commit_character) 157 | -- Your handler function. Inspect with print(vim.inspect{char, item, bufnr, rules, commit_character}) 158 | end 159 | } 160 | }, 161 | -- Disable for tex 162 | tex = false 163 | } 164 | }) 165 | ) 166 | < 167 | 168 | 169 | Don’t use `nil` to disable a filetype. If a filetype is `nil` then `*` is 170 | used as fallback. 171 | 172 | coq_nvim ~ 173 | 174 | > 175 | local remap = vim.api.nvim_set_keymap 176 | local npairs = require('nvim-autopairs') 177 | 178 | npairs.setup({ map_bs = false, map_cr = false }) 179 | 180 | vim.g.coq_settings = { keymap = { recommended = false } } 181 | 182 | -- these mappings are coq recommended mappings unrelated to nvim-autopairs 183 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 184 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 185 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 186 | remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) 187 | 188 | -- skip it, if you use another global object 189 | _G.MUtils= {} 190 | 191 | MUtils.CR = function() 192 | if vim.fn.pumvisible() ~= 0 then 193 | if vim.fn.complete_info({ 'selected' }).selected ~= -1 then 194 | return npairs.esc('') 195 | else 196 | return npairs.esc('') .. npairs.autopairs_cr() 197 | end 198 | else 199 | return npairs.autopairs_cr() 200 | end 201 | end 202 | remap('i', '', 'v:lua.MUtils.CR()', { expr = true, noremap = true }) 203 | 204 | MUtils.BS = function() 205 | if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info({ 'mode' }).mode == 'eval' then 206 | return npairs.esc('') .. npairs.autopairs_bs() 207 | else 208 | return npairs.autopairs_bs() 209 | end 210 | end 211 | remap('i', '', 'v:lua.MUtils.BS()', { expr = true, noremap = true }) 212 | < 213 | 214 | 215 | without completion plugin ~ 216 | 217 | > 218 | -- add option map_cr 219 | npairs.setup({ map_cr = true }) 220 | < 221 | 222 | 223 | another completion plugin 224 | 225 | 226 | If you have a problem with indent after you press `` please check the 227 | settings of treesitter indent or install a plugin that has indent support for 228 | your filetype. 229 | 230 | RULE ~ 231 | 232 | nvim-autopairs uses rules with conditions to check pairs. 233 | 234 | > 235 | local Rule = require('nvim-autopairs.rule') 236 | local npairs = require('nvim-autopairs') 237 | 238 | npairs.add_rule(Rule("$$","$$","tex")) 239 | 240 | -- you can use some built-in conditions 241 | 242 | local cond = require('nvim-autopairs.conds') 243 | print(vim.inspect(cond)) 244 | 245 | npairs.add_rules({ 246 | Rule("$", "$",{"tex", "latex"}) 247 | -- don't add a pair if the next character is % 248 | :with_pair(cond.not_after_regex("%%")) 249 | -- don't add a pair if the previous character is xxx 250 | :with_pair(cond.not_before_regex("xxx", 3)) 251 | -- don't move right when repeat character 252 | :with_move(cond.none()) 253 | -- don't delete if the next character is xx 254 | :with_del(cond.not_after_regex("xx")) 255 | -- disable adding a newline when you press 256 | :with_cr(cond.none()) 257 | }, 258 | -- disable for .vim files, but it work for another filetypes 259 | Rule("a","a","-vim") 260 | ) 261 | 262 | npairs.add_rules({ 263 | Rule("$$","$$","tex") 264 | :with_pair(function(opts) 265 | print(vim.inspect(opts)) 266 | if opts.line=="aa $$" then 267 | -- don't add pair on that line 268 | return false 269 | end 270 | end) 271 | } 272 | ) 273 | 274 | -- you can use regex 275 | -- press u1234 => u1234number 276 | npairs.add_rules({ 277 | Rule("u%d%d%d%d$", "number", "lua") 278 | :use_regex(true) 279 | }) 280 | 281 | 282 | 283 | -- press x1234 => x12341234 284 | npairs.add_rules({ 285 | Rule("x%d%d%d%d$", "number", "lua") 286 | :use_regex(true) 287 | :replace_endpair(function(opts) 288 | -- print(vim.inspect(opts)) 289 | return opts.prev_char:sub(#opts.prev_char - 3,#opts.prev_char) 290 | end) 291 | }) 292 | 293 | 294 | -- you can do anything with regex +special key 295 | -- example press tab to uppercase text: 296 | -- press b1234s => B1234S1234S 297 | 298 | npairs.add_rules({ 299 | Rule("b%d%d%d%d%w$", "", "vim") 300 | :use_regex(true,"") 301 | :replace_endpair(function(opts) 302 | return 303 | opts.prev_char:sub(#opts.prev_char - 4,#opts.prev_char) 304 | .."viwU" 305 | end) 306 | }) 307 | 308 | -- you can exclude filetypes 309 | npairs.add_rule( 310 | Rule("$$","$$") 311 | :with_pair(cond.not_filetypes({"lua"})) 312 | ) 313 | --- check ./lua/nvim-autopairs/rules/basic.lua 314 | < 315 | 316 | 317 | Rules API 318 | 319 | TREESITTER ~ 320 | 321 | You can use treesitter to check for a pair. 322 | 323 | > 324 | local npairs = require("nvim-autopairs") 325 | local Rule = require('nvim-autopairs.rule') 326 | 327 | npairs.setup({ 328 | check_ts = true, 329 | ts_config = { 330 | lua = {'string'},-- it will not add a pair on that treesitter node 331 | javascript = {'template_string'}, 332 | java = false,-- don't check treesitter on java 333 | } 334 | }) 335 | 336 | local ts_conds = require('nvim-autopairs.ts-conds') 337 | 338 | 339 | -- press % => %% only while inside a comment or string 340 | npairs.add_rules({ 341 | Rule("%", "%", "lua") 342 | :with_pair(ts_conds.is_ts_node({'string','comment'})), 343 | Rule("$", "$", "lua") 344 | :with_pair(ts_conds.is_not_ts_node({'function'})) 345 | }) 346 | < 347 | 348 | 349 | DON’T ADD PAIRS IF IT ALREADY HAS A CLOSE PAIR IN THE SAME LINE ~ 350 | 351 | if **next character** is a close pair and it doesn’t have an open pair in 352 | same line, then it will not add a close pair 353 | 354 | > 355 | Before Input After 356 | ------------------------------------ 357 | ( |)) ( ( (|)) 358 | < 359 | 360 | 361 | > 362 | require('nvim-autopairs').setup({ 363 | enable_check_bracket_line = false 364 | }) 365 | < 366 | 367 | 368 | DON’T ADD PAIRS IF THE NEXT CHAR IS ALPHANUMERIC ~ 369 | 370 | You can customize how nvim-autopairs will behave if it encounters a specific 371 | character 372 | 373 | > 374 | require('nvim-autopairs').setup({ 375 | ignored_next_char = "[%w%.]" -- will ignore alphanumeric and `.` symbol 376 | }) 377 | < 378 | 379 | 380 | > 381 | Before Input After 382 | ------------------------------------ 383 | |foobar ( (|foobar 384 | |.foobar ( (|.foobar 385 | < 386 | 387 | 388 | PLUGIN INTEGRATION ~ 389 | 390 | > 391 | require('nvim-autopairs').disable() 392 | require('nvim-autopairs').enable() 393 | require('nvim-autopairs').toggle() 394 | require('nvim-autopairs').remove_rule('(') -- remove rule ( 395 | require('nvim-autopairs').clear_rules() -- clear all rules 396 | require('nvim-autopairs').get_rules('"') 397 | < 398 | 399 | 400 | 401 | - Sample 402 | 403 | 404 | > 405 | -- remove add single quote on filetype scheme or lisp 406 | require("nvim-autopairs").get_rules("'")[1].not_filetypes = { "scheme", "lisp" } 407 | require("nvim-autopairs").get_rules("'")[1]:with_pair(cond.not_after_text("[")) 408 | < 409 | 410 | 411 | FASTWRAP ~ 412 | 413 | > 414 | Before Input After Note 415 | ----------------------------------------------------------------- 416 | (|foobar then press $ (|foobar) 417 | (|)(foobar) then press q (|(foobar)) 418 | (|foo bar then press qh (|foo) bar 419 | (|foo bar then press qH (foo|) bar 420 | (|foo bar then press qH (foo)| bar if cursor_pos_before = false 421 | < 422 | 423 | 424 | > 425 | -- put this to setup function and press to use fast_wrap 426 | npairs.setup({ 427 | fast_wrap = {}, 428 | }) 429 | 430 | -- change default fast_wrap 431 | npairs.setup({ 432 | fast_wrap = { 433 | map = '', 434 | chars = { '{', '[', '(', '"', "'" }, 435 | pattern = [=[[%'%"%>%]%)%}%,]]=], 436 | end_key = '$', 437 | before_key = 'h', 438 | after_key = 'l', 439 | cursor_pos_before = true, 440 | avoid_move_to_end = true, -- stay for direct end_key use 441 | keys = 'qwertyuiopzxcvbnmasdfghjkl', 442 | manual_position = true, 443 | highlight = 'Search', 444 | highlight_grey='Comment' 445 | }, 446 | }) 447 | < 448 | 449 | 450 | AUTOTAG HTML AND TSX ~ 451 | 452 | autotag 453 | 454 | ENDWISE ~ 455 | 456 | endwise 457 | 458 | CUSTOM RULES ~ 459 | 460 | rules 461 | 462 | SPONSORS *nvim-autopairs-sponsors* 463 | 464 | Thanks to everyone who sponsors my projects and makes continued development 465 | maintenance possible! 466 | 467 | george looshch 469 | 470 | Generated by panvimdoc 471 | 472 | vim:tw=78:ts=8:noet:ft=help:norl: 473 | -------------------------------------------------------------------------------- /doc/nvim-autopairs-rules.txt: -------------------------------------------------------------------------------- 1 | *nvim-autopairs-rules.txt* nvim-autopairs rules 2 | 3 | ============================================================================== 4 | Table of Contents *nvim-autopairs-rules-table-of-contents* 5 | 6 | 1. Rule Basics |nvim-autopairs-rules-rule-basics| 7 | 2. Controlling rule behavior |nvim-autopairs-rules-controlling-rule-behavior| 8 | - Method Overview |nvim-autopairs-rules-method-overview| 9 | - Conditions |nvim-autopairs-rules-conditions| 10 | 3. Method Explanations |nvim-autopairs-rules-method-explanations| 11 | - The `with_*` methods |nvim-autopairs-rules-the-`with_*`-methods| 12 | - The `use_*` methods |nvim-autopairs-rules-the-`use_*`-methods| 13 | - Shorthand methods |nvim-autopairs-rules-shorthand-methods| 14 | - Advanced methods |nvim-autopairs-rules-advanced-methods| 15 | 16 | ============================================================================== 17 | 1. Rule Basics *nvim-autopairs-rules-rule-basics* 18 | 19 | At its core, a rule consists of two things: a **pair definition** and an 20 | optional **declaration of filetypes** where the rule is in effect. A pair 21 | definition has an opening part and a closing part. Each of these parts can be 22 | as simple as a single character like a pair of parenthesis, or multiple 23 | characters like Markdown code fences. Defining a rule is straightforward: 24 | 25 | > 26 | Rule(begin_pair, end_pair, filetypes) 27 | < 28 | 29 | 30 | Where `begin_pair` is the opening part of the pair and `end_pair` is the 31 | closing part. `filetypes` may be specified in multiple ways: 32 | 33 | > 34 | Rule("(", ")") -- Enabled for all filetypes 35 | Rule("(", ")", "markdown") -- As a string 36 | Rule("(", ")", {"markdown", "vim"}) -- As a table 37 | < 38 | 39 | 40 | Additionally, it is possible to specify filetypes where the rule should **not** 41 | be enabled by prefixing it with a `-` character: 42 | 43 | > 44 | Rule("(", ")", "-markdown") -- All filetypes *except* markdown 45 | < 46 | 47 | 48 | ============================================================================== 49 | 2. Controlling rule behavior *nvim-autopairs-rules-controlling-rule-behavior* 50 | 51 | By default, rules are very simple and will always complete a pair the moment 52 | the opening part is typed. This is fine and in some cases desirable, but the 53 | rules API allows you to control the manner and context in which pairs are 54 | completed; this is done by attaching **conditions** (predicates) to **events** 55 | and adding **modifiers** to the rule. `Rule` objects expose a variety of 56 | methods to add these predicates and modifiers to the rule. 57 | 58 | METHOD OVERVIEW *nvim-autopairs-rules-method-overview* 59 | 60 | These methods allow control over if, when, and how rules perform completion of 61 | pairs. Each method returns the `Rule` object so that they may be chained 62 | together to easily define complex rules. 63 | 64 | │ method │ usage │ 65 | │with_pair(cond) │add condition to check during pair event │ 66 | │with_move(cond) │add condition to check during move right event │ 67 | │with_cr(cond) │add condition to check during line break event │ 68 | │with_del(cond) │add condition to check during delete pair event │ 69 | │only_cr(cond) │enable _only_ the line break event; disable everything │ 70 | │ │else │ 71 | │use_regex(bool, "") │ey │ 73 | │use_key("") │set trigger key │ 74 | │replace_endpair(fun│define ending part with a function; optionally add with│ 75 | │c, check_pair) │_pair │ 76 | │set_end_pair_length│override offset used to position the cursor between the│ 77 | │(number) │ pair when replace_endpair is used │ 78 | │replace_map_cr(func│change the mapping for used for during the line br│ 79 | │) │eak event │ 80 | │end_wise(cond) │make the rule an end-wise rule │ 81 | 82 | 83 | AIDING UNDERSTANDING: "WHEN" INSTEAD OF "WITH" ~ 84 | 85 | It may be helpful to think of the `with_` functions as reading more like 86 | `when_` instead, as the condition is checked **when** `` happens 87 | (or wants to happen). This naming scheme more accurately describes how the 88 | `Rule` is affected and reads more intuitively when reading a rule definition. 89 | 90 | For example, given a rule definition `Rule("(", ")")`, each method has a 91 | certain effect on how and when the ending part of the pair, the closing 92 | parenthesis, is completed. The ending part is only completed **when** 93 | associated conditions are met upon typing the opening part of the pair. 94 | 95 | CONDITIONS *nvim-autopairs-rules-conditions* 96 | 97 | nvim-autopairs comes with a variety of common predicates ready to use simply by 98 | including: 99 | 100 | > 101 | local cond = require('nvim-autopairs.conds') 102 | < 103 | 104 | 105 | │ function │ Usage │ 106 | │none() │always false │ 107 | │done() │always true │ 108 | │before_text(text) │text exists before opening part │ 109 | │after_text(text) │text exists after opening part │ 110 | │before_regex(regex, length│regex matches before opening part │ 111 | │) │ │ 112 | │after_regex(regex, length)│regex matches after opening part │ 113 | │ │ │ 114 | │not_before_text(text) │text is not before opening part │ 115 | │not_after_text(text) │text is not after opening part │ 116 | │not_before_regex(regex, le│regex doesn’t match before opening part │ 117 | │ngth) │ │ 118 | │not_after_regex(regex, len│regex doesn’t match after opening part │ 119 | │gth) │ │ 120 | │not_inside_quote() │not currently within quotation marks │ 121 | │is_inside_quote() │currently within quotation marks │ 122 | │not_filetypes({table}) │current filetype is not inside table │ 123 | │is_bracket_in_quote() │check the next char is quote and cursor is insi│ 124 | │ │de quote │ 125 | 126 | 127 | **N.B.** While `cond.not_filetypes` is available, it’s better to use the 128 | minus syntax on the desired filetype in the initial rule declaration, since 129 | then the rule is completely removed from the buffer. 130 | 131 | TREESITTER CONDITIONS ~ 132 | 133 | Predicates based on the state of the Treesitter graph can be used by including: 134 | 135 | > 136 | local ts_conds = require('nvim-autopairs.ts-conds') 137 | < 138 | 139 | 140 | │ function │ Usage │ 141 | │is_ts_node({node_table}) │check current treesitter node│ 142 | │is_not_ts_node({node_table})│check not in treesitter node │ 143 | 144 | 145 | ============================================================================== 146 | 3. Method Explanations *nvim-autopairs-rules-method-explanations* 147 | 148 | This section explains each method in more detail: their signatures and how they 149 | modify the rule’s behavior are all outlined here. 150 | 151 | THE `WITH_*` METHODS *nvim-autopairs-rules-the-`with_*`-methods* 152 | 153 | Calling these methods on a `Rule` will add predicate functions to their 154 | corresponding event, which determines whether the effect of the event actually 155 | takes place. There are no predicates if you don’t define any, and so any 156 | events without predicates behave as if they had a single predicate that always 157 | returns true. 158 | 159 | The predicate functions will receive an `opts` table with the following fields: 160 | - `rule` the `Rule` object - `bufnr` the buffer number - `col` the current 161 | column (1-indexed) - `ts_node` the current treesitter node (if treesitter is 162 | enabled) - `text` the current line, with typed char inserted - `line` the 163 | current line, before substitutions - `char` the typed char - `prev_char` the 164 | text just before cursor (with length `== #rule.start_pair`) - `next_char` the 165 | text just after cursor (with length `== #rule.start_pair` if rule is not regex, 166 | else end of line) 167 | 168 | A `Rule` may have more than one predicate defined for a given event, and the 169 | order that they are defined will be the order that they are checked. However, 170 | the **first** non-`nil` value returned by a predicate is used and the remaining 171 | predicates (if any) are **not** executed. In other words, predicates defined 172 | earlier have priority over predicates defined later. 173 | 174 | `WITH_PAIR(COND, POS)` ~ 175 | 176 | After typing the opening part, the ending part will only be added if 177 | `cond(opts)` returned true. `with_pair` may be called more than once, and by 178 | default, each predicate is appended to a list. When the "pair" event fires, the 179 | _first_ predicate to return non-nil is used as the condition result. Specifying 180 | `pos` allows explicit control over the order of the predicates. 181 | 182 | `WITH_MOVE(COND)` ~ 183 | 184 | If `cond(opts)` is true, the cursor is simply moved right when typing the 185 | ending part of the pair and the next character is also the ending part, 186 | e.g. `foo|"` => `foo"|` when typing `"`. If `cond(opts)` returns false, the 187 | ending part is inserted as normal instead. 188 | 189 | `WITH_CR(COND)` ~ 190 | 191 | If `cond(opts)` is true, then move the ending part of the pair to a new line 192 | below the cursor after pressing `` while the cursor is between the pair 193 | (think curly braces opening a block). Otherwise `` behaves as normal. For 194 | example: 195 | 196 | > 197 | {|} 198 | < 199 | 200 | 201 | Typing `` produces the following when `cond` is true: 202 | 203 | > 204 | { 205 | | 206 | } 207 | < 208 | 209 | 210 | `WITH_DEL(COND)` ~ 211 | 212 | If `cond(opts)` is true, when the cursor is between the pair, pressing `` 213 | to delete the opening part of the pair will delete the ending part as well. 214 | 215 | THE `USE_*` METHODS *nvim-autopairs-rules-the-`use_*`-methods* 216 | 217 | The `use_*` functions alter how auto-pairing is triggered. Normally, the first 218 | argument to `Rule` is taken literally as the opening part of the pair and as 219 | soon as it is typed the "pair" event fires. 220 | 221 | `USE_KEY(KEY)` ~ 222 | 223 | The pair is only completed when `key` is pressed, instead of the moment that 224 | the opening part is typed. This is particularly useful in `use_regex`. 225 | 226 | `USE_REGEX(BOOL, KEY)` ~ 227 | 228 | Causes the opening part to be interpreted as a lua pattern that triggers 229 | insertion of the ending part when matched. If `key` is specified, then it acts 230 | as an implicit `use_key`. 231 | 232 | SHORTHAND METHODS *nvim-autopairs-rules-shorthand-methods* 233 | 234 | These methods exist as convenient shortcuts for defining certain behaviors. 235 | 236 | `END_WISE(FUNC)` ~ 237 | 238 | This method is used to make "end-wise" rules, which is terminology that should 239 | be familiar to users of other auto-pair plugins, e.g. Lexima. Specifically, 240 | this method makes it so that the ending part of the pair will be completed 241 | _only upon pressing `` after the opening part_, in which case the "newline" 242 | event is fired as usual. 243 | 244 | This behavior is useful for languages with statement constructs like Lua and 245 | Bash. For example, defining the following `Rule`: 246 | 247 | > 248 | Rule('then', 'end'):end_wise(function(opts)) 249 | -- Add any context checks here, e.g. line starts with "if" 250 | return string.match(opts.line, '^%s*if') ~= nil 251 | end) 252 | < 253 | 254 | 255 | And then pressing `` at the following cursor position: 256 | 257 | > 258 | if foo == bar then| 259 | < 260 | 261 | 262 | Would be completed as this (assuming some kind of automatic indent is enabled): 263 | 264 | > 265 | if foo == bar then 266 | | 267 | end 268 | < 269 | 270 | 271 | `ONLY_CR(COND)` ~ 272 | 273 | This shortcut method disables the "pair", "del", and "move" events by setting a 274 | single predicate for each that is always false. Additionally, the effect of any 275 | `use_key` modifiers are removed as well. If `cond` is specified, a "newline" 276 | predicate is set as if `with_cr` were called. 277 | 278 | This method is convenient for defining _simple_ end-wise rules. As an example, 279 | a default rule is defined with `only_cr` for Markdown code blocks with an 280 | explicit language; the closing triple-backtick is not completed until you press 281 | `` after specifying the language: 282 | 283 | > 284 | ```lua <-- pressed here 285 | | 286 | ``` 287 | < 288 | 289 | 290 | ADVANCED METHODS *nvim-autopairs-rules-advanced-methods* 291 | 292 | These methods allow you to define more complex and dynamic rules. When combined 293 | with `with_*` and `use_*` methods, it is possible to create very powerful 294 | auto-pairs. 295 | 296 | `REPLACE_ENDPAIR(FUNC, CHECK_PAIR)` ~ 297 | 298 | Facilitates the creation of dynamic ending parts. When the "pair" event fires 299 | and the ending part is to be completed, `func` is called with a single `opts` 300 | argument and should return a string. The returned string will be sent to 301 | `nvim_feedkeys` to insert the ending part of the pair. 302 | 303 | The `opts` parameter is a table that provides context for the current pair 304 | completion, and can be useful for determining what to return. Note that because 305 | `nvim_feedkeys` is used, arbitrary Vim functionality can be leveraged, such as 306 | including `` to be able to send normal mode commands. 307 | 308 | *nvim-autopairs-rules-Optional-`check_pair`-parameter* 309 | 310 | Optional `check_pair` parameter The `check_pair` parameter is optional, 311 | and can either be a boolean or function. 312 | If `check_pair` is a function, it is 313 | passed as-is to `with_pair` to create a 314 | "pair" predicate. If `check_pair` is 315 | true, then an implicit 316 | `with_pair(cond.after_text(rule.end_pair))` 317 | predicate is added, where 318 | `rule.end_pair` is the second argument 319 | to the `Rule` constructor. If 320 | `check_pair` is false, an "always false" 321 | `with_pair` predicate is added. 322 | 323 | 324 | As an example, these two rule definitions are equivalent: 325 | 326 | > 327 | -- This... 328 | Rule("(", ")") 329 | :use_key("") 330 | :replace_endpair(function() return "" end, true) 331 | 332 | -- ...is shorthand for this 333 | Rule("(", "") 334 | :use_key("") 335 | :with_pair(cond.after_text(")")) -- check that text after cursor is `)` 336 | :replace_endpair(function() return "" end) 337 | < 338 | 339 | 340 | `SET_END_PAIR_LENGTH(LEN)` ~ 341 | 342 | When completing the ending part of a pair, the cursor necessarily moves 343 | backward so that is in between the opening part and the closing part. In order 344 | to do this, the `Rule` must know the length of the ending part, which by 345 | default is trivially determined. However, if you would like to override where 346 | the cursor is placed after completion, i.e. using `replace_endpair`, you can 347 | explicitly set the ending part length with this method. 348 | 349 | `REPLACE_MAP_CR(FUNC)` ~ 350 | 351 | This method allows you to set a custom mapping for the "newline" (``) event 352 | that will be used instead of the normal behavior. This can be helpful for 353 | enforcing certain styles or or adding additional edits. `func` is called with a 354 | single `opts` argument and should return a string specifying the mapping for 355 | ``. The default mapping is: `uO`. 356 | 357 | Generated by panvimdoc 358 | 359 | vim:tw=78:ts=8:noet:ft=help:norl: 360 | -------------------------------------------------------------------------------- /lua/nvim-autopairs.lua: -------------------------------------------------------------------------------- 1 | local log = require('nvim-autopairs._log') 2 | local utils = require('nvim-autopairs.utils') 3 | local basic_rule = require('nvim-autopairs.rules.basic') 4 | local api = vim.api 5 | local highlighter = nil 6 | local M = {} 7 | 8 | M.state = { 9 | disabled = false, 10 | rules = {}, 11 | buf_ts = {}, 12 | } 13 | 14 | local default = { 15 | map_bs = true, 16 | map_c_h = false, 17 | map_c_w = false, 18 | map_cr = true, 19 | enabled = nil, 20 | disable_filetype = { 'TelescopePrompt', 'spectre_panel', 'snacks_picker_input' }, 21 | disable_in_macro = true, 22 | disable_in_visualblock = false, 23 | disable_in_replace_mode = true, 24 | ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=], 25 | break_undo = true, 26 | check_ts = false, 27 | enable_moveright = true, 28 | enable_afterquote = true, 29 | enable_check_bracket_line = true, 30 | enable_bracket_in_quote = true, 31 | enable_abbr = false, 32 | ts_config = { 33 | lua = { 'string', 'source', 'string_content' }, 34 | javascript = { 'string', 'template_string' }, 35 | }, 36 | } 37 | 38 | M.setup = function(opt) 39 | M.config = vim.tbl_deep_extend('force', default, opt or {}) 40 | if M.config.fast_wrap then 41 | require('nvim-autopairs.fastwrap').setup(M.config.fast_wrap) 42 | end 43 | M.config.rules = basic_rule.setup(M.config) 44 | 45 | if M.config.check_ts then 46 | local ok, ts_rule = pcall(require, 'nvim-autopairs.rules.ts_basic') 47 | if ok then 48 | highlighter = require "vim.treesitter.highlighter" 49 | M.config.rules = ts_rule.setup(M.config) 50 | end 51 | end 52 | 53 | if M.config.map_cr then 54 | M.map_cr() 55 | end 56 | 57 | M.force_attach() 58 | local group = api.nvim_create_augroup('autopairs_buf', { clear = true }) 59 | api.nvim_create_autocmd({ 'BufEnter', 'BufWinEnter' }, { 60 | group = group, 61 | pattern = '*', 62 | callback = function() M.on_attach() end 63 | }) 64 | api.nvim_create_autocmd('BufDelete', { 65 | group = group, 66 | pattern = '*', 67 | callback = function(data) 68 | local cur = api.nvim_get_current_buf() 69 | local bufnr = tonumber(data.buf) or 0 70 | if bufnr ~= cur then 71 | M.set_buf_rule(nil, bufnr) 72 | end 73 | end, 74 | }) 75 | api.nvim_create_autocmd('FileType', { 76 | group = group, 77 | pattern = '*', 78 | callback = function() M.force_attach() end 79 | }) 80 | end 81 | 82 | M.add_rule = function(rule) 83 | M.add_rules({ rule }) 84 | end 85 | 86 | M.get_rule = function(start_pair) 87 | local tbl = M.get_rules(start_pair) 88 | if #tbl == 1 then 89 | return tbl[1] 90 | end 91 | return tbl 92 | end 93 | 94 | M.get_rules = function(start_pair) 95 | local tbl = {} 96 | for _, r in pairs(M.config.rules) do 97 | if r.start_pair == start_pair then 98 | table.insert(tbl, r) 99 | end 100 | end 101 | return tbl 102 | end 103 | 104 | M.remove_rule = function(pair) 105 | local tbl = {} 106 | for _, r in pairs(M.config.rules) do 107 | if r.start_pair ~= pair then 108 | table.insert(tbl, r) 109 | end 110 | end 111 | M.config.rules = tbl 112 | if M.state.rules then 113 | local state_tbl = {} 114 | local rules = M.get_buf_rules() 115 | for _, r in pairs(rules) do 116 | if r.start_pair ~= pair then 117 | table.insert(state_tbl, r) 118 | elseif r.key_map and r.key_map ~= '' then 119 | api.nvim_buf_del_keymap(0, 'i', r.key_map) 120 | end 121 | end 122 | M.set_buf_rule(state_tbl, 0) 123 | end 124 | M.force_attach() 125 | end 126 | 127 | M.add_rules = function(rules) 128 | for _, rule in pairs(rules) do 129 | table.insert(M.config.rules, rule) 130 | end 131 | M.force_attach() 132 | end 133 | 134 | M.clear_rules = function() 135 | M.state.rules = {} 136 | M.config.rules = {} 137 | end 138 | 139 | M.disable = function() 140 | M.state.disabled = true 141 | end 142 | 143 | M.enable = function() 144 | M.state.disabled = false 145 | end 146 | 147 | M.toggle = function() 148 | M.state.disabled = not M.state.disabled 149 | end 150 | 151 | --- force remap key to buffer 152 | M.force_attach = function(bufnr) 153 | utils.set_attach(bufnr, 0) 154 | M.on_attach(bufnr) 155 | end 156 | 157 | local del_keymaps = function() 158 | local status, autopairs_keymaps = pcall(api.nvim_buf_get_var, 0, 'autopairs_keymaps') 159 | if status and autopairs_keymaps and #autopairs_keymaps > 0 then 160 | for _, key in pairs(autopairs_keymaps) do 161 | pcall(api.nvim_buf_del_keymap, 0, 'i', key) 162 | end 163 | end 164 | end 165 | 166 | local function is_disable() 167 | if M.state.disabled then 168 | return true 169 | end 170 | 171 | if vim.bo.filetype == '' and api.nvim_win_get_config(0).relative ~= '' then 172 | -- disable for any floating window without filetype 173 | return true 174 | end 175 | 176 | if vim.bo.modifiable == false then 177 | return true 178 | end 179 | 180 | if M.config.disable_in_macro 181 | and (vim.fn.reg_recording() ~= '' or vim.fn.reg_executing() ~= '') 182 | then 183 | return true 184 | end 185 | 186 | if M.config.disable_in_replace_mode and vim.api.nvim_get_mode().mode == "R" then 187 | return true 188 | end 189 | 190 | if M.config.disable_in_visualblock and utils.is_block_wise_mode() then 191 | return true 192 | end 193 | 194 | if vim.v.count > 0 then 195 | return true 196 | end 197 | 198 | if 199 | utils.check_filetype(M.config.disable_filetype, vim.bo.filetype) 200 | or (M.config.enabled and not M.config.enabled(api.nvim_get_current_buf())) 201 | then 202 | del_keymaps() 203 | M.set_buf_rule({}, 0) 204 | return true 205 | end 206 | return false 207 | end 208 | 209 | ---@return table 210 | M.get_buf_rules = function(bufnr) 211 | return M.state.rules[bufnr or api.nvim_get_current_buf()] or {} 212 | end 213 | 214 | ---@param rules nil|table list or rule 215 | ---@param bufnr number buffer number 216 | M.set_buf_rule = function(rules, bufnr) 217 | if bufnr == 0 or bufnr == nil then 218 | bufnr = api.nvim_get_current_buf() 219 | end 220 | M.state.rules[bufnr] = rules 221 | end 222 | 223 | 224 | M.on_attach = function(bufnr) 225 | -- log.debug('on_attach' .. vim.bo.filetype) 226 | if is_disable() then 227 | return 228 | end 229 | bufnr = bufnr or api.nvim_get_current_buf() 230 | 231 | local rules = {} 232 | for _, rule in pairs(M.config.rules) do 233 | if utils.check_filetype(rule.filetypes, vim.bo.filetype) 234 | and utils.check_not_filetype(rule.not_filetypes, vim.bo.filetype) 235 | then 236 | table.insert(rules, rule) 237 | end 238 | end 239 | -- sort by pair and keymap 240 | table.sort(rules, function(a, b) 241 | if a.start_pair == b.start_pair then 242 | if not b.key_map then 243 | return a.key_map 244 | end 245 | if not a.key_map then 246 | return b.key_map 247 | end 248 | return #a.key_map < #b.key_map 249 | end 250 | if #a.start_pair == #b.start_pair then 251 | return string.byte(a.start_pair) > string.byte(b.start_pair) 252 | end 253 | return #a.start_pair > #b.start_pair 254 | end) 255 | 256 | M.set_buf_rule(rules, bufnr) 257 | 258 | if M.config.check_ts then 259 | if highlighter and highlighter.active[bufnr] then 260 | M.state.ts_node = M.config.ts_config[vim.bo.filetype] 261 | else 262 | M.state.ts_node = nil 263 | end 264 | end 265 | 266 | if utils.is_attached(bufnr) then 267 | return 268 | end 269 | del_keymaps() 270 | local enable_insert_auto = false 271 | local autopairs_keymaps = {} 272 | local expr_map = function(key) 273 | api.nvim_buf_set_keymap(bufnr, 'i', key, '', { 274 | expr = true, 275 | noremap = true, 276 | desc = "autopairs map key", 277 | callback = function() return M.autopairs_map(bufnr, key) end, 278 | }) 279 | table.insert(autopairs_keymaps, key) 280 | end 281 | for _, rule in pairs(rules) do 282 | if rule.key_map ~= nil then 283 | if rule.is_regex == false then 284 | if rule.key_map == '' then 285 | rule.key_map = rule.start_pair:sub(#rule.start_pair) 286 | end 287 | expr_map(rule.key_map) 288 | local key_end = rule.key_end or rule.end_pair:sub(1, 1) 289 | if #key_end >= 1 and key_end ~= rule.key_map and rule.move_cond ~= nil then 290 | expr_map(key_end) 291 | end 292 | else 293 | if rule.key_map ~= '' then 294 | expr_map(rule.key_map) 295 | elseif rule.is_endwise == false then 296 | enable_insert_auto = true 297 | end 298 | end 299 | end 300 | end 301 | api.nvim_buf_set_var(bufnr, 'autopairs_keymaps', autopairs_keymaps) 302 | 303 | if enable_insert_auto then 304 | -- capture all key use it to trigger regex pairs 305 | -- it can make an issue with paste from register 306 | api.nvim_create_autocmd('InsertCharPre', { 307 | group = api.nvim_create_augroup(string.format("autopairs_insert_%d", bufnr), { clear = true }), 308 | buffer = bufnr, 309 | callback = function() 310 | M.autopairs_insert(bufnr, vim.v.char) 311 | end 312 | }) 313 | end 314 | 315 | if M.config.fast_wrap and M.config.fast_wrap.map then 316 | api.nvim_buf_set_keymap( 317 | bufnr, 318 | 'i', 319 | M.config.fast_wrap.map, 320 | "llua require('nvim-autopairs.fastwrap').show()", 321 | { noremap = true, desc = "autopairs fastwrap" } 322 | ) 323 | end 324 | 325 | if M.config.map_bs then 326 | api.nvim_buf_set_keymap( 327 | bufnr, 328 | 'i', 329 | '', 330 | '', 331 | { callback = M.autopairs_bs, expr = true, noremap = true, desc = "autopairs delete" } 332 | ) 333 | end 334 | 335 | if M.config.map_c_h then 336 | api.nvim_buf_set_keymap( 337 | bufnr, 338 | "i", 339 | utils.key.c_h, 340 | '', 341 | { callback = M.autopairs_c_h, expr = true, noremap = true, desc = "autopairs delete" } 342 | ) 343 | end 344 | 345 | if M.config.map_c_w then 346 | api.nvim_buf_set_keymap( 347 | bufnr, 348 | 'i', 349 | '', 350 | '', 351 | { callback = M.autopairs_c_w, expr = true, noremap = true, desc = "autopairs delete" } 352 | ) 353 | end 354 | api.nvim_buf_set_var(bufnr, 'nvim-autopairs', 1) 355 | end 356 | 357 | local autopairs_delete = function(bufnr, key) 358 | if is_disable() then 359 | return utils.esc(key) 360 | end 361 | bufnr = bufnr or api.nvim_get_current_buf() 362 | local line = utils.text_get_current_line(bufnr) 363 | local _, col = utils.get_cursor() 364 | local rules = M.get_buf_rules(bufnr) 365 | for _, rule in pairs(rules) do 366 | if rule.start_pair then 367 | local prev_char, next_char = utils.text_cusor_line( 368 | line, 369 | col, 370 | #rule.start_pair, 371 | #rule.end_pair, 372 | rule.is_regex 373 | ) 374 | if utils.compare(rule.start_pair, prev_char, rule.is_regex) 375 | and utils.compare(rule.end_pair, next_char, rule.is_regex) 376 | and rule:can_del({ 377 | ts_node = M.state.ts_node, 378 | rule = rule, 379 | bufnr = bufnr, 380 | prev_char = prev_char, 381 | next_char = next_char, 382 | line = line, 383 | col = col, 384 | }) 385 | then 386 | local input = '' 387 | for _ = 1, api.nvim_strwidth(rule.start_pair), 1 do 388 | input = input .. utils.key.bs 389 | end 390 | for _ = 1, api.nvim_strwidth(rule.end_pair), 1 do 391 | input = input .. utils.key.del 392 | end 393 | return utils.esc('U' .. input) 394 | end 395 | end 396 | end 397 | return utils.esc(key) 398 | end 399 | 400 | M.autopairs_c_w = function(bufnr) 401 | return autopairs_delete(bufnr, 'U') 402 | end 403 | 404 | M.autopairs_c_h = function(bufnr) 405 | return autopairs_delete(bufnr, utils.key.c_h) 406 | end 407 | 408 | M.autopairs_bs = function(bufnr) 409 | return autopairs_delete(bufnr, utils.key.bs) 410 | end 411 | 412 | M.autopairs_map = function(bufnr, char) 413 | if is_disable() then 414 | return char 415 | end 416 | local line = utils.text_get_current_line(bufnr) 417 | local _, col = utils.get_cursor() 418 | local new_text = '' 419 | local add_char = 1 420 | local rules = M.get_buf_rules(bufnr) 421 | for _, rule in pairs(rules) do 422 | if rule.start_pair then 423 | if char:match('<.*>') then 424 | new_text = line 425 | add_char = 0 426 | else 427 | new_text = line:sub(1, col) .. char .. line:sub(col + 1, #line) 428 | add_char = rule.key_map and #rule.key_map or 1 429 | end 430 | 431 | -- log.debug("new_text:[" .. new_text .. "]") 432 | local prev_char, next_char = utils.text_cusor_line( 433 | new_text, 434 | col + add_char, 435 | #rule.start_pair, 436 | #rule.end_pair, 437 | rule.is_regex 438 | ) 439 | local cond_opt = { 440 | ts_node = M.state.ts_node, 441 | text = new_text, 442 | rule = rule, 443 | bufnr = bufnr, 444 | col = col + 1, 445 | char = char, 446 | line = line, 447 | prev_char = prev_char, 448 | next_char = next_char, 449 | } 450 | -- log.debug("start_pair" .. rule.start_pair) 451 | -- log.debug('prev_char' .. prev_char) 452 | -- log.debug('next_char' .. next_char) 453 | local char_matches_rule = (rule.end_pair == char or rule.key_map == char) 454 | -- for simple pairs, char will match end_pair 455 | -- for more complex pairs, user should map the wanted end char with `use_key` 456 | -- on a dedicated rule 457 | if char_matches_rule 458 | and utils.compare(rule.end_pair, next_char, rule.is_regex) 459 | and rule:can_move(cond_opt) 460 | then 461 | local end_pair = rule:get_end_pair(cond_opt) 462 | local end_pair_length = rule:get_end_pair_length(end_pair) 463 | return utils.esc(utils.repeat_key(utils.key.join_right, end_pair_length)) 464 | end 465 | 466 | if rule.key_map == char 467 | and utils.compare(rule.start_pair, prev_char, rule.is_regex) 468 | and rule:can_pair(cond_opt) 469 | then 470 | local end_pair = rule:get_end_pair(cond_opt) 471 | local end_pair_length = rule:get_end_pair_length(end_pair) 472 | local move_text = utils.repeat_key(utils.key.join_left, end_pair_length) 473 | if add_char == 0 then 474 | move_text = '' 475 | char = '' 476 | end 477 | if end_pair:match('<.*>') then 478 | end_pair = utils.esc(end_pair) 479 | end 480 | local result = char .. end_pair .. utils.esc(move_text) 481 | if rule.is_undo then 482 | result = utils.esc(utils.key.undo_sequence) .. result .. utils.esc(utils.key.undo_sequence) 483 | end 484 | if M.config.enable_abbr then 485 | result = utils.esc(utils.key.abbr) .. result 486 | end 487 | log.debug("key_map :" .. result) 488 | return result 489 | end 490 | end 491 | end 492 | return M.autopairs_afterquote(new_text, utils.esc(char)) 493 | end 494 | 495 | M.autopairs_insert = function(bufnr, char) 496 | if is_disable() then 497 | return char 498 | end 499 | local line = utils.text_get_current_line(bufnr) 500 | local _, col = utils.get_cursor() 501 | local new_text = line:sub(1, col) .. char .. line:sub(col + 1, #line) 502 | local rules = M.get_buf_rules(bufnr) 503 | for _, rule in pairs(rules) do 504 | if rule.start_pair and rule.is_regex and rule.key_map == '' then 505 | local prev_char, next_char = utils.text_cusor_line( 506 | new_text, 507 | col + 1, 508 | #rule.start_pair, 509 | #rule.end_pair, 510 | rule.is_regex 511 | ) 512 | local cond_opt = { 513 | ts_node = M.state.ts_node, 514 | text = new_text, 515 | rule = rule, 516 | bufnr = bufnr, 517 | col = col + 1, 518 | char = char, 519 | line = line, 520 | prev_char = prev_char, 521 | next_char = next_char, 522 | } 523 | -- log.debug("start_pair" .. rule.start_pair) 524 | -- log.debug('prev_char' .. prev_char) 525 | -- log.debug('next_char' .. next_char) 526 | if next_char == rule.end_pair and rule:can_move(cond_opt) then 527 | utils.set_vchar('') 528 | vim.schedule(function() 529 | utils.feed(utils.key.right, -1) 530 | end) 531 | return false 532 | end 533 | 534 | if utils.compare(rule.start_pair, prev_char, rule.is_regex) 535 | and rule:can_pair(cond_opt) 536 | then 537 | local end_pair = rule:get_end_pair(cond_opt) 538 | utils.set_vchar(char .. end_pair) 539 | vim.schedule(function() 540 | utils.feed(utils.key.left, rule:get_end_pair_length(end_pair)) 541 | end) 542 | return 543 | end 544 | end 545 | end 546 | return char 547 | end 548 | 549 | M.autopairs_cr = function(bufnr) 550 | if is_disable() then 551 | return utils.esc('') 552 | end 553 | bufnr = bufnr or api.nvim_get_current_buf() 554 | local line = utils.text_get_current_line(bufnr) 555 | local _, col = utils.get_cursor() 556 | -- log.debug("on_cr") 557 | local rules = M.get_buf_rules(bufnr) 558 | for _, rule in pairs(rules) do 559 | if rule.start_pair then 560 | local prev_char, next_char = utils.text_cusor_line( 561 | line, 562 | col, 563 | #rule.start_pair, 564 | #rule.end_pair, 565 | rule.is_regex 566 | ) 567 | 568 | local cond_opt = { 569 | ts_node = M.state.ts_node, 570 | check_endwise_ts = true, 571 | rule = rule, 572 | bufnr = bufnr, 573 | col = col, 574 | line = line, 575 | prev_char = prev_char, 576 | next_char = next_char, 577 | } 578 | -- log.debug('prev_char' .. rule.start_pair) 579 | -- log.debug('prev_char' .. prev_char) 580 | -- log.debug('next_char' .. next_char) 581 | if rule.is_endwise 582 | and utils.compare(rule.start_pair, prev_char, rule.is_regex) 583 | and rule:can_cr(cond_opt) 584 | then 585 | local end_pair = rule:get_end_pair(cond_opt) 586 | return utils.esc( 587 | '' .. end_pair 588 | -- FIXME do i need to re indent twice #118 589 | .. 'normal! ====' 590 | ) 591 | end 592 | 593 | cond_opt.check_endwise_ts = false 594 | 595 | if utils.compare(rule.start_pair, prev_char, rule.is_regex) 596 | and utils.compare(rule.end_pair, next_char, rule.is_regex) 597 | and rule:can_cr(cond_opt) 598 | then 599 | log.debug('do_cr') 600 | return utils.esc(rule:get_map_cr({ rule = rule, line = line, color = col, bufnr = bufnr })) 601 | end 602 | end 603 | end 604 | return utils.esc('') 605 | end 606 | 607 | --- add bracket pairs after quote (|"aaaaa" => (|"aaaaaa") 608 | M.autopairs_afterquote = function(line, key_char) 609 | if M.config.enable_afterquote and not utils.is_block_wise_mode() then 610 | line = line or utils.text_get_current_line(0) 611 | local _, col = utils.get_cursor() 612 | local prev_char, next_char = utils.text_cusor_line(line, col + 1, 1, 1, false) 613 | if utils.is_bracket(prev_char) 614 | and utils.is_quote(next_char) 615 | and not utils.is_in_quotes(line, col, next_char) 616 | then 617 | local count = 0 618 | local index = 0 619 | local is_prev_slash = false 620 | local char_end = '' 621 | for i = col, #line, 1 do 622 | local char = line:sub(i, i + #next_char - 1) 623 | if not is_prev_slash and char == next_char then 624 | count = count + 1 625 | char_end = line:sub(i + 1, i + #next_char) 626 | index = i 627 | end 628 | is_prev_slash = char == '\\' 629 | end 630 | if count == 2 and index >= (#line - 2) then 631 | local rules = M.get_buf_rules(api.nvim_get_current_buf()) 632 | for _, rule in pairs(rules) do 633 | if rule.start_pair == prev_char and char_end ~= rule.end_pair then 634 | local new_text = line:sub(0, index) 635 | .. rule.end_pair 636 | .. line:sub(index + 1, #line) 637 | M.state.expr_quote = new_text 638 | local append = 'a' 639 | if col > 0 then 640 | append = 'la' 641 | end 642 | return utils.esc( 643 | "lua require'nvim-autopairs'.autopairs_closequote_expr()" .. append 644 | ) 645 | end 646 | end 647 | end 648 | end 649 | end 650 | return key_char 651 | end 652 | 653 | M.autopairs_closequote_expr = function() 654 | ---@diagnostic disable-next-line: param-type-mismatch 655 | vim.fn.setline('.', M.state.expr_quote) 656 | end 657 | 658 | M.check_break_line_char = function() 659 | return M.autopairs_cr() 660 | end 661 | 662 | M.completion_confirm = function() 663 | if vim.fn.pumvisible() ~= 0 then 664 | return M.esc("") 665 | else 666 | return M.autopairs_cr() 667 | end 668 | end 669 | 670 | M.map_cr = function() 671 | api.nvim_set_keymap( 672 | 'i', 673 | '', 674 | "v:lua.require'nvim-autopairs'.completion_confirm()", 675 | { expr = true, noremap = true, desc = "autopairs completion confirm" } 676 | ) 677 | end 678 | 679 | M.esc = utils.esc 680 | return M 681 | -------------------------------------------------------------------------------- /tests/nvim-autopairs_spec.lua: -------------------------------------------------------------------------------- 1 | local npairs = require('nvim-autopairs') 2 | local Rule = require('nvim-autopairs.rule') 3 | local cond = require('nvim-autopairs.conds') 4 | 5 | local log = require('nvim-autopairs._log') 6 | _G.log = log 7 | local utils = require('nvim-autopairs.utils') 8 | _G.npairs = npairs; 9 | 10 | -- make test-file FILE=tests/nvim-autopairs_spec.lua 11 | 12 | -- use only = true to test 1 case 13 | -- stylua: ignore 14 | local data = { 15 | { 16 | -- only = true, 17 | name = "1 add normal bracket", 18 | key = [[{]], 19 | before = [[x| ]], 20 | after = [[x{|} ]] 21 | }, 22 | 23 | { 24 | name = "2 add bracket inside bracket", 25 | key = [[{]], 26 | before = [[{|} ]], 27 | after = [[{{|}} ]] 28 | }, 29 | { 30 | name = "3 test single quote ", 31 | filetype = "lua", 32 | key = "'", 33 | before = [[data,|) ]], 34 | after = [[data,'|') ]] 35 | }, 36 | { 37 | name = "4 add normal bracket", 38 | key = [[(]], 39 | before = [[aaaa| x ]], 40 | after = [[aaaa(|) x ]] 41 | }, 42 | { 43 | name = "5 add normal quote", 44 | key = [["]], 45 | before = [[aa| aa]], 46 | after = [[aa"|" aa]] 47 | }, 48 | { 49 | name = "6 add python quote", 50 | filetype = "python", 51 | key = [["]], 52 | before = [[""| ]], 53 | after = [["""|""" ]] 54 | }, 55 | { 56 | name = "7 don't repeat python quote", 57 | filetype = "python", 58 | key = [["]], 59 | before = [[a"""|""" ]], 60 | after = [[a""""|"" ]] 61 | }, 62 | 63 | { 64 | name = "8 add markdown quote", 65 | filetype = "markdown", 66 | key = [[`]], 67 | before = [[``| ]], 68 | after = [[```|``` ]] 69 | }, 70 | { 71 | name = "9 don't add single quote with previous alphabet char", 72 | key = [[']], 73 | before = [[aa| aa ]], 74 | after = [[aa'| aa ]] 75 | }, 76 | { 77 | name = "10 don't add single quote with alphabet char", 78 | key = [[']], 79 | before = [[a|x ]], 80 | after = [[a'|x ]] 81 | }, 82 | { 83 | name = "11 don't add single quote on end line", 84 | key = [[']], 85 | before = [[c aa|]], 86 | after = [[c aa'|]] 87 | }, 88 | { 89 | name = "12 don't add quote after alphabet char", 90 | key = [["]], 91 | before = [[aa |aa]], 92 | after = [[aa "|aa]] 93 | }, 94 | { 95 | name = "13 don't add quote inside quote", 96 | key = [["]], 97 | before = [["aa | aa]], 98 | after = [["aa "| aa]] 99 | }, 100 | { 101 | name = "14 add quote if not inside quote", 102 | key = [["]], 103 | before = [["aa " | aa]], 104 | after = [["aa " "|" aa]] 105 | }, 106 | { 107 | name = "15 don't add pair after alphabet char", 108 | key = [[(]], 109 | before = [[aa |aa]], 110 | after = [[aa (|aa]] 111 | }, 112 | { 113 | name = "16 don't add pair after dot char", 114 | key = [[(]], 115 | before = [[aa |.aa]], 116 | after = [[aa (|.aa]] 117 | }, 118 | { 119 | name = "17 don't add bracket have open bracket in same line", 120 | key = [[(]], 121 | before = [[( many char |))]], 122 | after = [[( many char (|))]] 123 | }, 124 | { 125 | filetype = 'vim', 126 | name = "18 add bracket inside quote when nextchar is ignore", 127 | key = [[{]], 128 | before = [["|"]], 129 | after = [["{|}"]] 130 | }, 131 | { 132 | filetype = '', 133 | name = "19 add bracket inside quote when next char is ignore", 134 | key = [[{]], 135 | before = [[" |"]], 136 | after = [[" {|}"]] 137 | }, 138 | { 139 | name = "20 move right on quote line ", 140 | key = [["]], 141 | before = [["|"]], 142 | after = [[""|]] 143 | }, 144 | { 145 | name = "21 move right end line ", 146 | key = [["]], 147 | before = [[aaaa|"]], 148 | after = [[aaaa"|]] 149 | }, 150 | { 151 | name = "22 move right when inside quote", 152 | key = [["]], 153 | before = [[("abcd|")]], 154 | after = [[("abcd"|)]] 155 | }, 156 | 157 | { 158 | name = "23 move right when inside quote", 159 | key = [["]], 160 | before = [[foo("|")]], 161 | after = [[foo(""|)]] 162 | }, 163 | { 164 | name = "24 move right square bracket", 165 | key = [[)]], 166 | before = [[("abcd|) ]], 167 | after = [[("abcd)| ]] 168 | }, 169 | { 170 | name = "25 move right bracket", 171 | key = [[}]], 172 | before = [[("abcd|}} ]], 173 | after = [[("abcd}|} ]] 174 | }, 175 | { 176 | -- ref: issue #331 177 | name = "26 move right, should not move on non-end-pair char: `§|§` with (", 178 | setup_func = function() 179 | npairs.add_rule(Rule("§", "§"):with_move(cond.done())) 180 | end, 181 | key = [[(]], 182 | before = [[§|§]], 183 | after = [[§(|)§]] 184 | }, 185 | { 186 | -- ref: issue #331 187 | name = "27 move right, should not move on non-end-pair char: `#|#` with \"", 188 | setup_func = function() 189 | npairs.add_rule(Rule("#", "#"):with_move(cond.done())) 190 | end, 191 | key = [["]], 192 | before = [[#|#]], 193 | after = [[#"|"#]] 194 | }, 195 | { 196 | -- ref: issue #331 and #330 197 | name = "28 move right, should not move on non-end-pair char: `<|>` with (", 198 | setup_func = function() 199 | npairs.add_rule(Rule("<", ">"):with_move(cond.done())) 200 | end, 201 | key = [[(]], 202 | before = [[<|>]], 203 | after = [[<(|)>]], 204 | }, 205 | { 206 | name = "29 move right when inside grave with special slash", 207 | key = [[`]], 208 | before = [[(`abcd\"|`)]], 209 | after = [[(`abcd\"`|)]] 210 | }, 211 | { 212 | name = "30 move right when inside quote with special slash", 213 | key = [["]], 214 | before = [[("abcd\"|")]], 215 | after = [[("abcd\""|)]] 216 | }, 217 | { 218 | filetype = 'rust', 219 | name = "31 move right double quote after single quote", 220 | key = [["]], 221 | before = [[ ('x').expect("|");]], 222 | after = [[ ('x').expect(""|);]], 223 | }, 224 | { 225 | filetype = 'rust', 226 | name = "32 move right, should not move when bracket not closing", 227 | key = [[}]], 228 | before = [[{{ |} ]], 229 | after = [[{{ }|} ]] 230 | }, 231 | 232 | { 233 | filetype = 'rust', 234 | name = "33 move right, should move when bracket closing", 235 | key = [[}]], 236 | before = [[{ }|} ]], 237 | after = [[{ }}| ]] 238 | }, 239 | { 240 | name = "34 delete bracket", 241 | filetype = "javascript", 242 | key = [[]], 243 | before = [[aaa(|) ]], 244 | after = [[aaa| ]] 245 | }, 246 | { 247 | name = "35 breakline on {", 248 | filetype = "javascript", 249 | key = [[]], 250 | before = [[a{|}]], 251 | after = { 252 | "a{", 253 | "|", 254 | "}" 255 | } 256 | }, 257 | { 258 | setup_func = function() 259 | vim.opt.indentexpr = "nvim_treesitter#indent()" 260 | end, 261 | name = "36 breakline on (", 262 | filetype = "javascript", 263 | key = [[]], 264 | before = [[function ab(|)]], 265 | after = { 266 | "function ab(", 267 | "|", 268 | ")" 269 | } 270 | }, 271 | { 272 | name = "37 breakline on ]", 273 | filetype = "javascript", 274 | key = [[]], 275 | before = [[a[|] ]], 276 | after = { 277 | "a[", 278 | "|", 279 | "]" 280 | } 281 | }, 282 | { 283 | name = "38 move ) inside nested function call", 284 | filetype = "javascript", 285 | key = [[)]], 286 | before = { 287 | "fn(fn(|))", 288 | }, 289 | after = { 290 | "fn(fn()|)", 291 | } 292 | }, 293 | { 294 | name = "39 move } inside singleline function's params", 295 | filetype = "javascript", 296 | key = [[}]], 297 | before = { 298 | "({|}) => {}", 299 | }, 300 | after = { 301 | "({}|) => {}", 302 | } 303 | }, 304 | { 305 | name = "40 move } inside multiline function's params", 306 | filetype = "javascript", 307 | key = [[}]], 308 | before = { 309 | "({|}) => {", 310 | "", 311 | "}", 312 | }, 313 | after = { 314 | "({}|) => {", 315 | "", 316 | "}", 317 | } 318 | }, 319 | { 320 | name = "41 breakline on markdown ", 321 | filetype = "markdown", 322 | key = [[]], 323 | before = [[``` lua|```]], 324 | after = { 325 | [[``` lua]], 326 | [[|]], 327 | [[```]] 328 | } 329 | }, 330 | { 331 | name = "42 breakline on < html", 332 | filetype = "html", 333 | key = [[]], 334 | before = [[
|
]], 335 | after = { 336 | [[
]], 337 | [[|]], 338 | [[
]] 339 | } 340 | }, 341 | { 342 | name = "43 breakline on < html with text", 343 | filetype = "html", 344 | key = [[]], 345 | before = [[
ads |
]], 346 | after = { 347 | [[
ads]], 348 | [[|]], 349 | [[
]] 350 | }, 351 | }, 352 | { 353 | name = "44 breakline on < html with space after cursor", 354 | filetype = "html", 355 | key = [[]], 356 | before = [[
ads |
]], 357 | after = { 358 | [[
ads]], 359 | [[|]], 360 | [[
]] 361 | }, 362 | }, 363 | { 364 | name = "45 do not mapping on > html", 365 | filetype = "html", 366 | key = [[>]], 367 | before = [[| ]] 369 | }, 370 | { 371 | name = "46 press multiple key", 372 | filetype = "html", 373 | key = [[((((]], 374 | before = [[a| ]], 375 | after = [[a((((|)))) ]] 376 | }, 377 | { 378 | setup_func = function() 379 | npairs.add_rules({ 380 | Rule('u%d%d%d%d$', 'number', 'lua'):use_regex(true), 381 | }) 382 | end, 383 | name = "47 text regex", 384 | filetype = "lua", 385 | key = "4", 386 | before = [[u123| ]], 387 | after = [[u1234|number ]] 388 | }, 389 | { 390 | 391 | setup_func = function() 392 | npairs.add_rules({ 393 | Rule('x%d%d%d%d$', 'number', 'lua'):use_regex(true):replace_endpair(function(opts) 394 | return opts.prev_char:sub(#opts.prev_char - 3, #opts.prev_char) 395 | end), 396 | }) 397 | end, 398 | name = "48 text regex with custom end_pair", 399 | filetype = "lua", 400 | key = "4", 401 | before = [[x123| ]], 402 | after = [[x1234|1234 ]] 403 | }, 404 | { 405 | setup_func = function() 406 | npairs.add_rules({ 407 | Rule('b%d%d%d%d%w$', '', 'vim'):use_regex(true, ''):replace_endpair(function(opts) 408 | return opts.prev_char:sub(#opts.prev_char - 4, #opts.prev_char) .. 'viwUi' 409 | end), 410 | }) 411 | end, 412 | name = "49 text regex with custom key", 413 | filetype = "vim", 414 | key = "", 415 | before = [[b1234s| ]], 416 | after = [[B|1234S1234S ]] 417 | 418 | }, 419 | { 420 | setup_func = function() 421 | npairs.add_rules({ 422 | Rule('b%d%d%d%d%w$', '', 'vim'):use_regex(true, ''):replace_endpair(function(opts) 423 | return opts.prev_char:sub(#opts.prev_char - 4, #opts.prev_char) .. 'viwUi' 424 | end), 425 | }) 426 | end, 427 | name = "50 test move right custom char", 428 | filetype = "vim", 429 | key = "", 430 | before = [[b1234s| ]], 431 | after = [[B|1234S1234S ]] 432 | }, 433 | { 434 | setup_func = function() 435 | npairs.add_rules({ 436 | Rule("-", "+", "vim") 437 | :with_move(function(opt) 438 | return utils.get_prev_char(opt) == "x" 439 | end) 440 | :with_move(cond.done()) 441 | }) 442 | end, 443 | name = "51 test move right custom char plus", 444 | filetype = "vim", 445 | key = "+", 446 | before = [[x|+ ]], 447 | after = [[x+| ]] 448 | }, 449 | { 450 | setup_func = function() 451 | npairs.add_rules({ 452 | Rule("/**", "**/", "javascript") 453 | :with_move(cond.none()) 454 | }) 455 | end, 456 | name = "52 test javascript comment", 457 | filetype = "javascript", 458 | key = "*", 459 | before = [[/*| ]], 460 | after = [[/**|**/ ]] 461 | }, 462 | { 463 | setup_func = function() 464 | npairs.add_rules({ 465 | Rule("(", ")") 466 | :use_key("") 467 | :replace_endpair(function() return "" end, true) 468 | }) 469 | end, 470 | name = "53 test map custom key", 471 | filetype = "latex", 472 | key = [[]], 473 | before = [[ abcde(|) ]], 474 | after = [[ abcde| ]], 475 | }, 476 | { 477 | setup_func = function() 478 | npairs.add_rules { 479 | Rule(' ', ' '):with_pair(function(opts) 480 | local pair = opts.line:sub(opts.col, opts.col + 1) 481 | return vim.tbl_contains({ '()', '[]', '{}' }, pair) 482 | end), 483 | Rule('( ', ' )') 484 | :with_pair(function() return false end) 485 | :with_del(function() return false end) 486 | :with_move(function() return true end) 487 | :use_regex(false, ")") 488 | } 489 | end, 490 | name = "54 test multiple move right", 491 | filetype = "latex", 492 | key = [[)]], 493 | before = [[( | ) ]], 494 | after = [[( )| ]], 495 | }, 496 | { 497 | setup_func = function() 498 | npairs.setup({ 499 | enable_check_bracket_line = false 500 | }) 501 | end, 502 | name = "55 test disable check bracket line", 503 | filetype = "latex", 504 | key = [[(]], 505 | before = [[(|))) ]], 506 | after = [[((|)))) ]], 507 | }, 508 | { 509 | setup_func = function() 510 | npairs.add_rules({ 511 | Rule("<", ">", { "rust" }) 512 | :with_pair(cond.before_text("Vec")) 513 | }) 514 | end, 515 | name = "56 test disable check bracket line", 516 | filetype = "rust", 517 | key = [[<]], 518 | before = [[Vec| ]], 519 | after = [[Vec<|> ]], 520 | }, 521 | { 522 | setup_func = function() 523 | npairs.add_rule(Rule("!", "!"):with_pair(cond.not_filetypes({ "lua" }))) 524 | end, 525 | name = "57 disable pairs in lua", 526 | filetype = "lua", 527 | key = "!", 528 | before = [[x| ]], 529 | after = [[x!| ]] 530 | }, 531 | { 532 | setup_func = function() 533 | npairs.clear_rules() 534 | npairs.add_rules({ 535 | Rule("%(.*%)%s*%=>", " { }", { "typescript", "typescriptreact", "javascript" }) 536 | :use_regex(true) 537 | :set_end_pair_length(2) 538 | }) 539 | end, 540 | name = "58 mapping regex with custom end_pair_length", 541 | filetype = "typescript", 542 | key = ">", 543 | before = [[(o)=| ]], 544 | after = [[(o)=> { | } ]] 545 | 546 | }, 547 | { 548 | setup_func = function() 549 | npairs.add_rules({ 550 | Rule('(', ')'):use_key(''):replace_endpair(function() 551 | return '' 552 | end, true), 553 | Rule('(', ')'):use_key(''):replace_endpair(function() 554 | return '' 555 | end, true), 556 | }) 557 | end, 558 | name = "59 mapping same pair with different key", 559 | filetype = "typescript", 560 | key = "(", 561 | before = [[(test|) ]], 562 | after = [[(test(|)) ]] 563 | 564 | }, 565 | { 566 | setup_func = function() 567 | npairs.clear_rules() 568 | npairs.add_rule(Rule("„", "”")) 569 | end, 570 | name = "60 multibyte character from custom keyboard", 571 | not_replace_term_code = true, 572 | key = "„", 573 | before = [[a | ]], 574 | after = [[a „|” ]], 575 | end_cursor = 3 576 | }, 577 | { 578 | setup_func = function() 579 | npairs.clear_rules() 580 | npairs.add_rule(Rule("„", "”"):with_move(cond.done())) 581 | end, 582 | name = "61 multibyte character move_right", 583 | not_replace_term_code = true, 584 | key = "”", 585 | before = [[a „|”xx ]], 586 | after = [[a „”|xx ]], 587 | end_cursor = 6 588 | }, 589 | { 590 | setup_func = function() 591 | npairs.clear_rules() 592 | npairs.add_rule(Rule("„", "”"):with_move(cond.done())) 593 | end, 594 | name = "62 multibyte character delete", 595 | key = "", 596 | before = [[a „|” ]], 597 | after = [[a | ]], 598 | }, 599 | { 600 | setup_func = function() 601 | npairs.clear_rules() 602 | npairs.add_rule(Rule("a„", "”b"):with_move(cond.done())) 603 | end, 604 | not_replace_term_code = true, 605 | name = "63 multibyte character and multiple ", 606 | key = "„", 607 | before = [[a| ]], 608 | after = [[a„|”b ]], 609 | end_cursor = 2 610 | }, 611 | { 612 | setup_func = function() 613 | npairs.setup({ map_c_h = true }) 614 | end, 615 | name = "64 map ", 616 | key = "", 617 | before = [[aa'|' ]], 618 | after = [[aa| ]], 619 | }, 620 | { 621 | setup_func = function() 622 | npairs.setup({ 623 | map_c_w = true 624 | }) 625 | end, 626 | name = "65 map ", 627 | key = "", 628 | before = [[aa'|' ]], 629 | after = [[aa| ]], 630 | }, 631 | { 632 | setup_func = function() 633 | npairs.clear_rules() 634 | npairs.add_rule(Rule("x", "x", { '-vim', '-rust' })) 635 | end, 636 | filetype = 'vim', 637 | name = "66 disable filetype vim", 638 | key = [[x]], 639 | before = [[a | ]], 640 | after = [[a x| ]] 641 | }, 642 | { 643 | filetype = 'vim', 644 | name = "67 undo on quote", 645 | key = [[{123u]], 646 | end_cursor = 12, 647 | before = [[local abc=| ]], 648 | after = [[local abc={|} ]] 649 | }, 650 | { 651 | filetype = 'vim', 652 | name = "68 undo on bracket", 653 | key = [['123u]], 654 | end_cursor = 12, 655 | before = [[local abc=| ]], 656 | after = [[local abc='|' ]] 657 | }, 658 | { 659 | filetype = 'vim', 660 | name = "69 double quote on vim after char", 661 | key = [["ab]], 662 | before = [[echo | ]], 663 | after = [[echo "ab|" ]] 664 | }, 665 | { 666 | filetype = 'vim', 667 | name = "70 double quote on vim on begin", 668 | key = [["ab]], 669 | before = [[ | aaa]], 670 | after = [[ "ab| aaa]] 671 | }, 672 | { 673 | setup_func = function() 674 | npairs.add_rule( 675 | Rule('struct%s[a-zA-Z]+%s?{$', '};') 676 | :use_regex(true, "{") 677 | ) 678 | end, 679 | filetype = 'javascript', 680 | name = "71 custom endwise rule", 681 | key = [[{]], 682 | before = [[struct abc | ]], 683 | after = [[struct abc {|};]], 684 | }, 685 | { 686 | setup_func = function() 687 | npairs.clear_rules() 688 | npairs.add_rule(Rule("{", "}"):end_wise()) 689 | end, 690 | filetype = 'javascript', 691 | name = "72 custom endwise rule", 692 | key = [[]], 693 | before = [[function () {| ]], 694 | after = { 695 | [[function () {]], 696 | [[|]], 697 | [[}]], 698 | }, 699 | }, 700 | { 701 | setup_func = function() 702 | vim.opt.smartindent = true 703 | end, 704 | filetype = 'ps1', 705 | name = "73 indent on powershell", 706 | key = [[]], 707 | before = [[function () {|} ]], 708 | after = { 709 | [[function () {]], 710 | [[|]], 711 | [[}]], 712 | }, 713 | }, 714 | { 715 | setup_func = function() 716 | npairs.clear_rules() 717 | npairs.add_rule( 718 | Rule("{", "") 719 | :replace_endpair(function() 720 | return "}" 721 | end) 722 | :end_wise() 723 | ) 724 | end, 725 | filetype = 'javascript', 726 | name = "74 custom endwise rule with custom end_pair", 727 | key = [[]], 728 | before = [[function () {| ]], 729 | after = { 730 | [[function () {]], 731 | [[|]], 732 | [[}]], 733 | }, 734 | }, 735 | { 736 | name = "75 open bracker on back tick", 737 | key = [[(]], 738 | before = [[ |`abcd`]], 739 | after = [[ (`abcd`) ]] 740 | }, 741 | { 742 | name = "76 should not add bracket on line have bracket ", 743 | key = [[(]], 744 | before = [[ |(abcd))]], 745 | after = [[ ((abcd)) ]] 746 | }, 747 | { 748 | name = "77 not add bracket on line have bracket ", 749 | key = [[(]], 750 | before = [[ |(abcd) ( visual)]], 751 | after = [[ ()(abcd) ( visual)]] 752 | }, 753 | { 754 | name = "78 should add single quote when it have primes char", 755 | key = [[']], 756 | before = [[Ben's friends say: | ]], 757 | after = [[Ben's friends say: '|' ]] 758 | }, 759 | { 760 | name = "79 a quote with single quote string", 761 | key = "'", 762 | before = [[{{("It doesn't name %s", ''), 'ErrorMsg'| }}, ]], 763 | after = [[{{("It doesn't name %s", ''), 'ErrorMsg''|' }}, ]], 764 | end_cursor = 41 765 | }, 766 | { 767 | name = "80 add normal quote with '", 768 | key = [["]], 769 | before = [[aa| 'aa]], 770 | after = [[aa"|" 'aa]] 771 | }, 772 | { 773 | name = "81 add closing single quote for python prefixed string", 774 | filetype = "python", 775 | key = [[']], 776 | before = [[print(f|)]], 777 | after = [[print(f'|')]] 778 | }, 779 | { 780 | name = "82 add closing single quote for capital python prefixed string", 781 | filetype = "python", 782 | key = [[']], 783 | before = [[print(B|)]], 784 | after = [[print(B'|')]] 785 | }, 786 | { 787 | name = "83 don't add closing single quote for random prefix string", 788 | filetype = "python", 789 | key = [[']], 790 | before = [[print(s|)]], 791 | after = [[print(s'|)]] 792 | }, 793 | { 794 | name = "84 don't add closing single quote for other filetype prefixed string", 795 | filetype = "lua", 796 | key = [[']], 797 | before = [[print(f|)]], 798 | after = [[print(f'|)]] 799 | }, 800 | { 801 | name = "85 allow brackets in prefixed python single quote string", 802 | filetype = "python", 803 | key = [[{]], 804 | before = [[print(f'|')]], 805 | after = [[print(f'{|}')]] 806 | }, 807 | { 808 | name = "86 move ' is working on python", 809 | filetype = "python", 810 | key = [[']], 811 | before = [[('|') ]], 812 | after = [[(''|) ]] 813 | }, 814 | { 815 | setup_func = function() 816 | npairs.add_rules({ 817 | Rule('123456', '789'):with_pair(cond.before_regex('^12345$', 5)), 818 | }) 819 | end, 820 | name = '87 test before_regex with a specific string length', 821 | key = [[123456]], 822 | before = [[ some text before| ]], 823 | after = [[ some text before123456|789 ]], 824 | }, 825 | { 826 | name = "88 disable on count mode", 827 | filetype = "txt", 828 | key = function() 829 | local keys = vim.api.nvim_replace_termcodes('2otest({', true, true, true) 830 | vim.api.nvim_feedkeys(keys, 'x', true) 831 | end, 832 | before = [[ | ]], 833 | after = { 834 | '', 835 | ' test({', 836 | } 837 | }, 838 | { 839 | name = "89 key on markdown", 840 | filetype = "markdown", 841 | key = [[]], 842 | before = [[|```python ]], 843 | after = { 844 | "", 845 | "|```python" 846 | } 847 | }, 848 | } 849 | 850 | local run_data = _G.Test_filter(data) 851 | 852 | describe("autopairs ", function() 853 | _G.Test_withfile(run_data, { 854 | cursor_add = 0, 855 | before_each = function(value) 856 | npairs.setup() 857 | vim.opt.indentexpr = "" 858 | if value.setup_func then 859 | value.setup_func() 860 | end 861 | end, 862 | }) 863 | end) 864 | --------------------------------------------------------------------------------