diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6a4506d7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ + +root = true + +[*] +indent_style = space +indent_size = 4 + +[*.lua] +indent_style = space +indent_size = 4 + + +[*.vim] +indent_style = space +indent_size = 4 + +[*.snippets] +indent_style = tab +indent_size = 4 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6849e11f..e2ed65f2 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ -github:windwp +github: ['windwp'] +custom: https://paypal.me/trieule1vn diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..bf63b992 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,71 @@ +name: Bug report +description: Report a problem with nvim-autopairs +labels: [bug] +body: + - type: markdown + attributes: + value: | + 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. + - type: textarea + attributes: + label: "Description" + description: "A short description of the problem you are reporting." + validations: + required: true + - type: textarea + attributes: + label: "Mapping bug" + description: "report a bug about mapping `` key and completion plugin" + value: | + 1.If you report a bug about indent. Please remember that plugin doesn't do anything about indent. + It just trigger the indent of your vim config so if you have wrong indent config then it will do wrong indent. + You can check by select a block of code and press `==` + 2. provide result of command `:verbose imap `. + validations: + required: false + - type: textarea + attributes: + label: "Steps to reproduce" + description: "Steps to reproduce using the minimal config provided below." + placeholder: | + - It will beter if you can provide a video of gif + validations: + required: false + - type: textarea + attributes: + label: "Minimal config" + 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." + render: Lua + value: | + vim.cmd [[set runtimepath=$VIMRUNTIME]] + vim.cmd [[set packpath=/tmp/nvim/site]] + local package_root = '/tmp/nvim/site/pack' + local install_path = package_root .. '/packer/start/packer.nvim' + local function load_plugins() + require('packer').startup { + { + 'wbthomason/packer.nvim', + { + 'windwp/nvim-autopairs', + }, + -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE + }, + config = { + package_root = package_root, + compile_path = install_path .. '/plugin/packer_compiled.lua', + display = { non_interactive = true }, + }, + } + end + _G.load_config = function() + require('nvim-autopairs').setup() + end + if vim.fn.isdirectory(install_path) == 0 then + print("Installing nvim-autopairs and dependencies.") + vim.fn.system { 'git', 'clone', '--depth=1', 'https://github.com/wbthomason/packer.nvim', install_path } + end + load_plugins() + require('packer').sync() + vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua load_config()]] + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..99d680b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,2 @@ +blank_issues_enabled: false + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..11fc491e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/generated-files-bot.yml b/.github/generated-files-bot.yml new file mode 100644 index 00000000..e7cda275 --- /dev/null +++ b/.github/generated-files-bot.yml @@ -0,0 +1,7 @@ +generatedFiles: + - path: "doc/nvim-autopairs.txt" + - path: "doc/nvim-autopairs-rules.txt" + message: "`nvim-autopairs.txt` is generated from README.md. Make changes there instead." +ignoreAuthors: + - 'github-actions[bot]' + - 'windwp' diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..cf2285d6 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,19 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false + +only: issues diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 642fd59f..5b4ca398 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,28 +5,40 @@ on: [push, pull_request] jobs: x64-ubuntu: name: X64-ubuntu - runs-on: ubuntu-20.04 + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-20.04 + url: https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz + manager: sudo apt-get + packages: -y fd-find steps: - uses: actions/checkout@v2 - run: date +%F > todays-date - - name: Restore cache for today's nightly. + - name: Restore from todays cache uses: actions/cache@v2 with: - path: | - _neovim - key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }} - + path: _neovim + key: ${{ runner.os }}-${{ matrix.url }}-${{ hashFiles('todays-date') }} - name: Prepare run: | + ${{ matrix.manager }} update + ${{ matrix.manager }} install ${{ matrix.packages }} + test -d _neovim || { + mkdir -p _neovim + curl -sL ${{ matrix.url }} | tar xzf - --strip-components=1 -C "${PWD}/_neovim" + } mkdir -p ~/.local/share/nvim/site/pack/vendor/start git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim - git clone --depth 1 https://github.com/nvim-lua/popup.nvim ~/.local/share/nvim/site/pack/vendor/start/popup.nvim git clone --depth 1 https://github.com/nvim-treesitter/nvim-treesitter ~/.local/share/nvim/site/pack/vendor/start/nvim-treesitter git clone --depth 1 https://github.com/nvim-treesitter/playground ~/.local/share/nvim/site/pack/vendor/start/playground ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start - name: Run tests run: | - curl -OL https://raw.githubusercontent.com/norcalli/bot-ci/master/scripts/github-actions-setup.sh - source github-actions-setup.sh nightly-x64 + export PATH="${PWD}/_neovim/bin:${PATH}" + export VIM="${PWD}/_neovim/share/nvim/runtime" nvim --headless -u tests/minimal.vim -c "TSInstallSync all" -c "q" make test + diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..f79e0fe9 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,51 @@ +name: panvimdoc + +on: + push: + paths: + - README.md + branches: + - master +jobs: + docs: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: markdown to vimdoc + steps: + - uses: actions/checkout@v2 + - name: Setup git + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + printf 'VIMDOC_BRANCH=bot/vimdoc/%s\n' ${GITHUB_REF#refs/heads/} >> $GITHUB_ENV + - name: Checkout to vimdoc branch + run: git checkout -b ${VIMDOC_BRANCH} + - name: panvimdoc + uses: kdheepak/panvimdoc@v2.7.1 + with: + vimdoc: nvim-autopairs + description: A super powerful autopair for Neovim. + - name: clone rules api docs + run: | + git clone --depth 1 https://github.com/windwp/nvim-autopairs.wiki.git ../nvim-autopairs.wiki + cp ../nvim-autopairs.wiki/Rules-API.md ./Rules-API.md + - name: panvimdoc + uses: kdheepak/panvimdoc@v2.7.1 + with: + vimdoc: nvim-autopairs-rules + description: nvim-autopairs rules + pandoc: "Rules-API.md" + toc: true + - name: Create PR + run: | + if ! [[ -z $(git status -s) ]]; then + git add doc/nvim-autopairs.txt + git add doc/nvim-autopairs-rules.txt + git commit -m "chore: generated vimdoc" + git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${VIMDOC_BRANCH} + gh pr create --fill --base ${GITHUB_REF#refs/heads/} --head ${VIMDOC_BRANCH} || true + fi diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml new file mode 100644 index 00000000..68fc9c76 --- /dev/null +++ b/.github/workflows/sponsors.yml @@ -0,0 +1,30 @@ +name: Generate Sponsors README +on: + workflow_dispatch: + schedule: + - cron: 0 0 1 */3 * +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v2 + + - name: Setup Git Config + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' + + - name: Generate Sponsors 💖 + uses: JamesIves/github-sponsors-readme-action@v1 + with: + token: ${{ secrets.PAT }} + file: 'README.md' + + - name: Deploy to GitHub Pages 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: master + folder: '.' diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..926ccaaf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +doc/tags diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 00000000..01f62598 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "Lua.diagnostics.disable": [ + "undefined-field" + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 19494a67..284268c4 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,5 @@ test: nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}" + +test-file: + nvim --headless --noplugin -u tests/minimal.vim -c "lua require(\"plenary.busted\").run(\"$(FILE)\")" diff --git a/README.md b/README.md index 2a3c9d2d..025ba42c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,69 @@ ## nvim-autopairs -A super powerful autopairs for Neovim. -It support multiple character. +A super powerful autopair plugin for Neovim that supports multiple characters. -Requires neovim 0.5+ +Requires neovim 0.7 -### Setup -``` lua -require('nvim-autopairs').setup{} +## Installation + +Install the plugin with your preferred package manager: + +### [lazy.nvim](https://github.com/folke/lazy.nvim) + +```lua +{ + 'windwp/nvim-autopairs', + event = "InsertEnter", + config = true + -- use opts = {} for passing setup options + -- this is equivalent to setup({}) function +} +``` + +### [vim-plug](https://github.com/junegunn/vim-plug) +```vim +Plug 'windwp/nvim-autopairs' + +lua << EOF +require("nvim-autopairs").setup {} +EOF +``` + +### [packer](https://github.com/wbthomason/packer.nvim) + +```lua +use { + "windwp/nvim-autopairs", + event = "InsertEnter", + config = function() + require("nvim-autopairs").setup {} + end +} ``` ## Default values ``` lua -local disable_filetype = { "TelescopePrompt" } -local ignored_next_char = string.gsub([[ [%w%%%'%[%"%.] ]],"%s+", "") -local enable_moveright = true -local enable_afterquote = true -- add bracket pairs after quote -local enable_check_bracket_line = true --- check bracket in same line -local check_ts = false - +{ + enabled = function(bufnr) return true end, -- control if auto-pairs should be enabled when attaching to a buffer + disable_filetype = { "TelescopePrompt", "spectre_panel" }, + disable_in_macro = true, -- disable when recording or executing a macro + disable_in_visualblock = false, -- disable when insert after visual block mode + disable_in_replace_mode = true, + ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=], + enable_moveright = true, + enable_afterquote = true, -- add bracket pairs after quote + enable_check_bracket_line = true, --- check bracket in same line + enable_bracket_in_quote = true, -- + enable_abbr = false, -- trigger abbreviation + break_undo = true, -- switch for basic rule break undo sequence + check_ts = false, + map_cr = true, + map_bs = true, -- map the key + map_c_h = false, -- Map the key to delete a pair + map_c_w = false, -- map to delete a pair if possible +} ``` ### Override default values @@ -41,81 +84,129 @@ Before Input After } ------------------------------------ ``` +
-nvim-compe +nvim-cmp +

+You need to add mapping `CR` on nvim-cmp setup. +Check readme.md on nvim-cmp repo. +

``` lua -require("nvim-autopairs.completion.compe").setup({ - map_cr = true, -- map on insert mode - map_complete = true -- it will auto insert `(` after select function or method item -}) +-- If you want insert `(` after select function or method item +local cmp_autopairs = require('nvim-autopairs.completion.cmp') +local cmp = require('cmp') +cmp.event:on( + 'confirm_done', + cmp_autopairs.on_confirm_done() +) ``` -Make sure to remove mapping insert mode `` binding if you have it. -
+You can customize the kind of completion to add `(` or any character. +```lua +local handlers = require('nvim-autopairs.completion.handlers') + +cmp.event:on( + 'confirm_done', + cmp_autopairs.on_confirm_done({ + filetypes = { + -- "*" is a alias to all filetypes + ["*"] = { + ["("] = { + kind = { + cmp.lsp.CompletionItemKind.Function, + cmp.lsp.CompletionItemKind.Method, + }, + handler = handlers["*"] + } + }, + lua = { + ["("] = { + kind = { + cmp.lsp.CompletionItemKind.Function, + cmp.lsp.CompletionItemKind.Method + }, + ---@param char string + ---@param item table item completion + ---@param bufnr number buffer number + ---@param rules table + ---@param commit_character table + handler = function(char, item, bufnr, rules, commit_character) + -- Your handler function. Inspect with print(vim.inspect{char, item, bufnr, rules, commit_character}) + end + } + }, + -- Disable for tex + tex = false + } + }) +) +``` + +Don't use `nil` to disable a filetype. If a filetype is `nil` then `*` is used as fallback. + +
-completion nvim +coq_nvim ``` lua local remap = vim.api.nvim_set_keymap local npairs = require('nvim-autopairs') +npairs.setup({ map_bs = false, map_cr = false }) + +vim.g.coq_settings = { keymap = { recommended = false } } + +-- these mappings are coq recommended mappings unrelated to nvim-autopairs +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) +remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) + -- skip it, if you use another global object _G.MUtils= {} -vim.g.completion_confirm_key = "" - -MUtils.completion_confirm=function() - if vim.fn.pumvisible() ~= 0 then - if vim.fn.complete_info()["selected"] ~= -1 then - require'completion'.confirmCompletion() - return npairs.esc("") +MUtils.CR = function() + if vim.fn.pumvisible() ~= 0 then + if vim.fn.complete_info({ 'selected' }).selected ~= -1 then + return npairs.esc('') else - vim.api.nvim_select_popupmenu_item(0 , false , false ,{}) - require'completion'.confirmCompletion() - return npairs.esc("") + return npairs.esc('') .. npairs.autopairs_cr() end else return npairs.autopairs_cr() end end +remap('i', '', 'v:lua.MUtils.CR()', { expr = true, noremap = true }) -remap('i' , '','v:lua.MUtils.completion_confirm()', {expr = true , noremap = true}) - +MUtils.BS = function() + if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info({ 'mode' }).mode == 'eval' then + return npairs.esc('') .. npairs.autopairs_bs() + else + return npairs.autopairs_bs() + end +end +remap('i', '', 'v:lua.MUtils.BS()', { expr = true, noremap = true }) ```
-
without completion plugin ```lua -local remap = vim.api.nvim_set_keymap -local npairs = require('nvim-autopairs') - --- skip it, if you use another global object -_G.MUtils= {} - -MUtils.completion_confirm=function() - if vim.fn.pumvisible() ~= 0 then - return npairs.esc("") - else - return npairs.autopairs_cr() - end -end - - -remap('i' , '','v:lua.MUtils.completion_confirm()', {expr = true , noremap = true}) +-- add option map_cr +npairs.setup({ map_cr = true }) ```
-If you have a problem with indent after press ` ` -Please check setting of treesitter indent or install plugin support indent on your filetype +[another completion plugin](https://github.com/windwp/nvim-autopairs/wiki/Completion-plugin) +If you have a problem with indent after you press ` ` +please check the settings of treesitter indent or install a plugin that has indent support for your filetype. ### Rule -nvim-autopairs use rule with condition to check pair. +nvim-autopairs uses rules with conditions to check pairs. ``` lua local Rule = require('nvim-autopairs.rule') @@ -123,7 +214,7 @@ local npairs = require('nvim-autopairs') npairs.add_rule(Rule("$$","$$","tex")) --- you can use some built-in condition +-- you can use some built-in conditions local cond = require('nvim-autopairs.conds') print(vim.inspect(cond)) @@ -131,19 +222,20 @@ print(vim.inspect(cond)) npairs.add_rules({ Rule("$", "$",{"tex", "latex"}) -- don't add a pair if the next character is % - :with_pair(cond.not_after_regex_check("%%")) + :with_pair(cond.not_after_regex("%%")) -- don't add a pair if the previous character is xxx - :with_pair(cond.not_before_regex_check("xxx", 3)) + :with_pair(cond.not_before_regex("xxx", 3)) -- don't move right when repeat character :with_move(cond.none()) -- don't delete if the next character is xx - :with_del(cond.not_after_regex_check("xx")) - -- disable add newline when press + :with_del(cond.not_after_regex("xx")) + -- disable adding a newline when you press :with_cr(cond.none()) }, + -- disable for .vim files, but it work for another filetypes + Rule("a","a","-vim") ) - npairs.add_rules({ Rule("$$","$$","tex") :with_pair(function(opts) @@ -157,7 +249,7 @@ npairs.add_rules({ ) -- you can use regex --- press u1234 => u1234number +-- press u1234 => u1234number npairs.add_rules({ Rule("u%d%d%d%d$", "number", "lua") :use_regex(true) @@ -165,7 +257,7 @@ npairs.add_rules({ --- press x1234 => x12341234 +-- press x1234 => x12341234 npairs.add_rules({ Rule("x%d%d%d%d$", "number", "lua") :use_regex(true) @@ -177,7 +269,7 @@ npairs.add_rules({ -- you can do anything with regex +special key --- example press tab will upper text +-- example press tab to uppercase text: -- press b1234s => B1234S1234S npairs.add_rules({ @@ -189,34 +281,37 @@ npairs.add_rules({ .."viwU" end) }) + +-- you can exclude filetypes +npairs.add_rule( + Rule("$$","$$") + :with_pair(cond.not_filetypes({"lua"})) +) --- check ./lua/nvim-autopairs/rules/basic.lua ``` [Rules API](https://github.com/windwp/nvim-autopairs/wiki/Rules-API) ### Treesitter -You can use treesitter to check pair +You can use treesitter to check for a pair. ```lua local npairs = require("nvim-autopairs") +local Rule = require('nvim-autopairs.rule') npairs.setup({ check_ts = true, ts_config = { - lua = {'string'},-- it will not add pair on that treesitter node + lua = {'string'},-- it will not add a pair on that treesitter node javascript = {'template_string'}, java = false,-- don't check treesitter on java } }) -require('nvim-treesitter.configs').setup { - autopairs = {enable = true} -} - local ts_conds = require('nvim-autopairs.ts-conds') --- press % => %% is only inside comment or string +-- press % => %% only while inside a comment or string npairs.add_rules({ Rule("%", "%", "lua") :with_pair(ts_conds.is_ts_node({'string','comment'})), @@ -225,8 +320,8 @@ npairs.add_rules({ }) ``` -### Don't add pairs if it already have a close pairs in same line -if **next character** is a close pairs and it doesn't have an open pairs in same line then it will not add a close pairs +### Don't add pairs if it already has a close pair in the same line +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 ``` text Before Input After @@ -262,22 +357,33 @@ Before Input After ``` lua require('nvim-autopairs').disable() require('nvim-autopairs').enable() - require('nvim-autopairs').remove_rule('(')-- remove rule ( - require('nvim-autopairs').clear_rules() -- clear all rule - require('nvim-autopairs').get_rule('"') -- get rule " then modify it + require('nvim-autopairs').toggle() + require('nvim-autopairs').remove_rule('(') -- remove rule ( + require('nvim-autopairs').clear_rules() -- clear all rules + require('nvim-autopairs').get_rules('"') +``` +* Sample +```lua +-- remove add single quote on filetype scheme or lisp +require("nvim-autopairs").get_rules("'")[1].not_filetypes = { "scheme", "lisp" } +require("nvim-autopairs").get_rules("'")[1]:with_pair(cond.not_after_text("[")) ``` + ### FastWrap ``` text -Before Input After --------------------------------------------------- -(|foobar then press $ (|foobar) +Before Input After Note +----------------------------------------------------------------- +(|foobar then press $ (|foobar) (|)(foobar) then press q (|(foobar)) +(|foo bar then press qh (|foo) bar +(|foo bar then press qH (foo|) bar +(|foo bar then press qH (foo)| bar if cursor_pos_before = false ``` ```lua --- put this to setup function and press to use fast_wrap +-- put this to setup function and press to use fast_wrap npairs.setup({ fast_wrap = {}, }) @@ -287,11 +393,15 @@ npairs.setup({ fast_wrap = { map = '', chars = { '{', '[', '(', '"', "'" }, - pattern = string.gsub([[ [%'%"%)%>%]%)%}%,] ]], '%s+', ''), + pattern = [=[[%'%"%>%]%)%}%,]]=], end_key = '$', + before_key = 'h', + after_key = 'l', + cursor_pos_before = true, keys = 'qwertyuiopzxcvbnmasdfghjkl', - check_comma = true, - hightlight = 'Search' + manual_position = true, + highlight = 'Search', + highlight_grey='Comment' }, }) ``` @@ -307,3 +417,8 @@ npairs.setup({ ### Custom rules [rules](https://github.com/windwp/nvim-autopairs/wiki/Custom-rules) +## Sponsors + +Thanks to everyone who sponsors my projects and makes continued development maintenance possible! + +george looshch diff --git a/doc/nvim-autopairs-rules.txt b/doc/nvim-autopairs-rules.txt new file mode 100644 index 00000000..88e85a4b --- /dev/null +++ b/doc/nvim-autopairs-rules.txt @@ -0,0 +1,359 @@ +*nvim-autopairs-rules.txt* nvim-autopairs rules + +============================================================================== +Table of Contents *nvim-autopairs-rules-table-of-contents* + +1. Rule Basics |nvim-autopairs-rules-rule-basics| +2. Controlling rule behavior |nvim-autopairs-rules-controlling-rule-behavior| + - Method Overview |nvim-autopairs-rules-method-overview| + - Conditions |nvim-autopairs-rules-conditions| +3. Method Explanations |nvim-autopairs-rules-method-explanations| + - The `with_*` methods |nvim-autopairs-rules-the-`with_*`-methods| + - The `use_*` methods |nvim-autopairs-rules-the-`use_*`-methods| + - Shorthand methods |nvim-autopairs-rules-shorthand-methods| + - Advanced methods |nvim-autopairs-rules-advanced-methods| + +============================================================================== +1. Rule Basics *nvim-autopairs-rules-rule-basics* + +At its core, a rule consists of two things: a **pair definition** and an +optional **declaration of filetypes** where the rule is in effect. A pair +definition has an opening part and a closing part. Each of these parts can be +as simple as a single character like a pair of parenthesis, or multiple +characters like Markdown code fences. Defining a rule is straightforward: + +> + Rule(begin_pair, end_pair, filetypes) +< + + +Where `begin_pair` is the opening part of the pair and `end_pair` is the +closing part. `filetypes` may be specified in multiple ways: + +> + Rule("(", ")") -- Enabled for all filetypes + Rule("(", ")", "markdown") -- As a string + Rule("(", ")", {"markdown", "vim"}) -- As a table +< + + +Additionally, it is possible to specify filetypes where the rule should **not** +be enabled by prefixing it with a `-` character: + +> + Rule("(", ")", "-markdown") -- All filetypes *except* markdown +< + + +============================================================================== +2. Controlling rule behavior *nvim-autopairs-rules-controlling-rule-behavior* + +By default, rules are very simple and will always complete a pair the moment +the opening part is typed. This is fine and in some cases desirable, but the +rules API allows you to control the manner and context in which pairs are +completed; this is done by attaching **conditions** (predicates) to **events** +and adding **modifiers** to the rule. `Rule` objects expose a variety of +methods to add these predicates and modifiers to the rule. + +METHOD OVERVIEW *nvim-autopairs-rules-method-overview* + +These methods allow control over if, when, and how rules perform completion of +pairs. Each method returns the `Rule` object so that they may be chained +together to easily define complex rules. + +│ method │ usage │ +│with_pair(cond) │add condition to check during pair event │ +│with_move(cond) │add condition to check during move right event │ +│with_cr(cond) │add condition to check during line break event │ +│with_del(cond) │add condition to check during delete pair event │ +│only_cr(cond) │enable _only_ the line break event; disable everything │ +│ │else │ +│use_regex(bool, "") │ey │ +│use_key("") │set trigger key │ +│replace_endpair(fun│define ending part with a function; optionally add with│ +│c, check_pair) │_pair │ +│set_end_pair_length│override offset used to position the cursor between the│ +│(number) │ pair when replace_endpair is used │ +│replace_map_cr(func│change the mapping for used for during the line br│ +│) │eak event │ +│end_wise(cond) │make the rule an end-wise rule │ + + +AIDING UNDERSTANDING: "WHEN" INSTEAD OF "WITH" ~ + +It may be helpful to think of the `with_` functions as reading more like +`when_` instead, as the condition is checked **when** `` happens +(or wants to happen). This naming scheme more accurately describes how the +`Rule` is affected and reads more intuitively when reading a rule definition. + +For example, given a rule definition `Rule("(", ")")`, each method has a +certain effect on how and when the ending part of the pair, the closing +parenthesis, is completed. The ending part is only completed **when** +associated conditions are met upon typing the opening part of the pair. + +CONDITIONS *nvim-autopairs-rules-conditions* + +nvim-autopairs comes with a variety of common predicates ready to use simply by +including: + +> + local cond = require('nvim-autopairs.conds') +< + + +│ function │ Usage │ +│none() │always false │ +│done() │always true │ +│before_text(text) │text exists before opening part │ +│after_text(text) │text exists after opening part │ +│before_regex(regex, length│regex matches before opening part │ +│) │ │ +│after_regex(regex, length)│regex matches after opening part │ +│ │ │ +│not_before_text(text) │text is not before opening part │ +│not_after_text(text) │text is not after opening part │ +│not_before_regex(regex, le│regex doesn’t match before opening part │ +│ngth) │ │ +│not_after_regex(regex, len│regex doesn’t match after opening part │ +│gth) │ │ +│not_inside_quote() │not currently within quotation marks │ +│is_inside_quote() │currently within quotation marks │ +│not_filetypes({table}) │current filetype is not inside table │ +│is_bracket_in_quote() │check the next char is quote and cursor is insi│ +│ │de quote │ + + +**N.B.** While `cond.not_filetypes` is available, it’s better to use the +minus syntax on the desired filetype in the initial rule declaration, since +then the rule is completely removed from the buffer. + +TREESITTER CONDITIONS ~ + +Predicates based on the state of the Treesitter graph can be used by including: + +> + local ts_conds = require('nvim-autopairs.ts-conds') +< + + +│ function │ Usage │ +│is_ts_node({node_table}) │check current treesitter node│ +│is_not_ts_node({node_table})│check not in treesitter node │ + + +============================================================================== +3. Method Explanations *nvim-autopairs-rules-method-explanations* + +This section explains each method in more detail: their signatures and how they +modify the rule’s behavior are all outlined here. + +THE `WITH_*` METHODS *nvim-autopairs-rules-the-`with_*`-methods* + +Calling these methods on a `Rule` will add predicate functions to their +corresponding event, which determines whether the effect of the event actually +takes place. There are no predicates if you don’t define any, and so any +events without predicates behave as if they had a single predicate that always +returns true. + +The predicate functions will receive an `opts` table with the following fields: +- `rule` the `Rule` object - `bufnr` the buffer number - `col` the current +column (1-indexed) - `ts_node` the current treesitter node (if treesitter is +enabled) - `text` the current line, with typed char inserted - `line` the +current line, before substitutions - `char` the typed char - `prev_char` the +text just before cursor (with length `== #rule.start_pair`) - `next_char` the +text just after cursor (with length `== #rule.start_pair` if rule is not regex, +else end of line) + +A `Rule` may have more than one predicate defined for a given event, and the +order that they are defined will be the order that they are checked. However, +the **first** non-`nil` value returned by a predicate is used and the remaining +predicates (if any) are **not** executed. In other words, predicates defined +earlier have priority over predicates defined later. + +`WITH_PAIR(COND, POS)` ~ + +After typing the opening part, the ending part will only be added if +`cond(opts)` returned true. `with_pair` may be called more than once, and by +default, each predicate is appended to a list. When the "pair" event fires, the +_first_ predicate to return non-nil is used as the condition result. Specifying +`pos` allows explicit control over the order of the predicates. + +`WITH_MOVE(COND)` ~ + +If `cond(opts)` is true, the cursor is simply moved right when typing the +ending part of the pair and the next character is also the ending part, +e.g. `foo|"` => `foo"|` when typing `"`. If `cond(opts)` returns false, the +ending part is inserted as normal instead. + +`WITH_CR(COND)` ~ + +If `cond(opts)` is true, then move the ending part of the pair to a new line +below the cursor after pressing `` while the cursor is between the pair +(think curly braces opening a block). Otherwise `` behaves as normal. For +example: + +> + {|} +< + + +Typing `` produces the following when `cond` is true: + +> + { + | + } +< + + +`WITH_DEL(COND)` ~ + +If `cond(opts)` is true, when the cursor is between the pair, pressing `` +to delete the opening part of the pair will delete the ending part as well. + +THE `USE_*` METHODS *nvim-autopairs-rules-the-`use_*`-methods* + +The `use_*` functions alter how auto-pairing is triggered. Normally, the first +argument to `Rule` is taken literally as the opening part of the pair and as +soon as it is typed the "pair" event fires. + +`USE_KEY(KEY)` ~ + +The pair is only completed when `key` is pressed, instead of the moment that +the opening part is typed. This is particularly useful in `use_regex`. + +`USE_REGEX(BOOL, KEY)` ~ + +Causes the opening part to be interpreted as a lua pattern that triggers +insertion of the ending part when matched. If `key` is specified, then it acts +as an implicit `use_key`. + +SHORTHAND METHODS *nvim-autopairs-rules-shorthand-methods* + +These methods exist as convenient shortcuts for defining certain behaviors. + +`END_WISE(FUNC)` ~ + +This method is used to make "end-wise" rules, which is terminology that should +be familiar to users of other auto-pair plugins, e.g. Lexima. Specifically, +this method makes it so that the ending part of the pair will be completed +_only upon pressing `` after the opening part_, in which case the "newline" +event is fired as usual. + +This behavior is useful for languages with statement constructs like Lua and +Bash. For example, defining the following `Rule`: + +> + Rule('then', 'end'):end_wise(function(opts)) + -- Add any context checks here, e.g. line starts with "if" + return string.match(opts.line, '^%s*if') ~= nil + end) +< + + +And then pressing `` at the following cursor position: + +> + if foo == bar then| +< + + +Would be completed as this (assuming some kind of automatic indent is enabled): + +> + if foo == bar then + | + end +< + + +`ONLY_CR(COND)` ~ + +This shortcut method disables the "pair", "del", and "move" events by setting a +single predicate for each that is always false. Additionally, the effect of any +`use_key` modifiers are removed as well. If `cond` is specified, a "newline" +predicate is set as if `with_cr` were called. + +This method is convenient for defining _simple_ end-wise rules. As an example, +a default rule is defined with `only_cr` for Markdown code blocks with an +explicit language; the closing triple-backtick is not completed until you press +`` after specifying the language: + +> + ```lua <-- pressed here + | + ``` +< + + +ADVANCED METHODS *nvim-autopairs-rules-advanced-methods* + +These methods allow you to define more complex and dynamic rules. When combined +with `with_*` and `use_*` methods, it is possible to create very powerful +auto-pairs. + +`REPLACE_ENDPAIR(FUNC, CHECK_PAIR)` ~ + +Facilitates the creation of dynamic ending parts. When the "pair" event fires +and the ending part is to be completed, `func` is called with a single `opts` +argument and should return a string. The returned string will be sent to +`nvim_feedkeys` to insert the ending part of the pair. + +The `opts` parameter is a table that provides context for the current pair +completion, and can be useful for determining what to return. Note that because +`nvim_feedkeys` is used, arbitrary Vim functionality can be leveraged, such as +including `` to be able to send normal mode commands. + + *nvim-autopairs-rules-Optional-`check_pair`-parameter* + +Optional `check_pair` parameter The `check_pair` parameter is optional, + and can either be a boolean or function. + If `check_pair` is a function, it is + passed as-is to `with_pair` to create a + "pair" predicate. If `check_pair` is + true, then an implicit + `with_pair(cond.after_text(rule.end_pair))` + predicate is added, where + `rule.end_pair` is the second argument + to the `Rule` constructor. If + `check_pair` is false, an "always false" + `with_pair` predicate is added. + + +As an example, these two rule definitions are equivalent: + +> + -- This... + Rule("(", ")") + :use_key("") + :replace_endpair(function() return "" end, true) + + -- ...is shorthand for this + Rule("(", "") + :use_key("") + :with_pair(cond.after_text(")")) -- check that text after cursor is `)` + :replace_endpair(function() return "" end) +< + + +`SET_END_PAIR_LENGTH(LEN)` ~ + +When completing the ending part of a pair, the cursor necessarily moves +backward so that is in between the opening part and the closing part. In order +to do this, the `Rule` must know the length of the ending part, which by +default is trivially determined. However, if you would like to override where +the cursor is placed after completion, i.e. using `replace_endpair`, you can +explicitly set the ending part length with this method. + +`REPLACE_MAP_CR(FUNC)` ~ + +This method allows you to set a custom mapping for the "newline" (``) event +that will be used instead of the normal behavior. This can be helpful for +enforcing certain styles or or adding additional edits. `func` is called with a +single `opts` argument and should return a string specifying the mapping for +``. The default mapping is: `uO`. + +Generated by panvimdoc + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/nvim-autopairs.txt b/doc/nvim-autopairs.txt new file mode 100644 index 00000000..a46df27c --- /dev/null +++ b/doc/nvim-autopairs.txt @@ -0,0 +1,472 @@ +*nvim-autopairs.txt* A super powerful autopair for Neovim. + +============================================================================== +Table of Contents *nvim-autopairs-table-of-contents* + + - nvim-autopairs |nvim-autopairs-nvim-autopairs| + - Installation |nvim-autopairs-installation| + - Default values |nvim-autopairs-default-values| + - Sponsors |nvim-autopairs-sponsors| + +NVIM-AUTOPAIRS *nvim-autopairs-nvim-autopairs* + +A super powerful autopair plugin for Neovim that supports multiple characters. + +Requires neovim 0.7 + +INSTALLATION *nvim-autopairs-installation* + +Install the plugin with your preferred package manager: + +LAZY.NVIM ~ + +> + { + 'windwp/nvim-autopairs', + event = "InsertEnter", + config = true + -- use opts = {} for passing setup options + -- this is equivalent to setup({}) function + } +< + + +VIM-PLUG ~ + +> + Plug 'windwp/nvim-autopairs' + + lua << EOF + require("nvim-autopairs").setup {} + EOF +< + + +PACKER ~ + +> + use { + "windwp/nvim-autopairs", + event = "InsertEnter", + config = function() + require("nvim-autopairs").setup {} + end + } +< + + +DEFAULT VALUES *nvim-autopairs-default-values* + +> + { + enabled = function(bufnr) return true end, -- control if auto-pairs should be enabled when attaching to a buffer + disable_filetype = { "TelescopePrompt", "spectre_panel" } + disable_in_macro = true -- disable when recording or executing a macro + disable_in_visualblock = false -- disable when insert after visual block mode + disable_in_replace_mode = true + ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=] + enable_moveright = true + enable_afterquote = true -- add bracket pairs after quote + enable_check_bracket_line = true --- check bracket in same line + enable_bracket_in_quote = true -- + enable_abbr = false -- trigger abbreviation + break_undo = true -- switch for basic rule break undo sequence + check_ts = false + map_cr = true + map_bs = true -- map the key + map_c_h = false -- Map the key to delete a pair + map_c_w = false -- map to delete a pair if possible + } +< + + +OVERRIDE DEFAULT VALUES ~ + +> + require('nvim-autopairs').setup({ + disable_filetype = { "TelescopePrompt" , "vim" }, + }) +< + + + *nvim-autopairs-Mapping-``* + +> + Before Input After + ------------------------------------ + {|} { + | + } + ------------------------------------ +< + + +nvim-cmp ~ + +

+ +You need to add mapping `CR` on nvim-cmp setup. +Check readme.md on nvim-cmp repo. + +

+ +> + -- If you want insert `(` after select function or method item + local cmp_autopairs = require('nvim-autopairs.completion.cmp') + local cmp = require('cmp') + cmp.event:on( + 'confirm_done', + cmp_autopairs.on_confirm_done() + ) +< + + +Mapping `` You can customize the kind of completion + to add `(` or any character. + + +> + local handlers = require('nvim-autopairs.completion.handlers') + + cmp.event:on( + 'confirm_done', + cmp_autopairs.on_confirm_done({ + filetypes = { + -- "*" is a alias to all filetypes + ["*"] = { + ["("] = { + kind = { + cmp.lsp.CompletionItemKind.Function, + cmp.lsp.CompletionItemKind.Method, + }, + handler = handlers["*"] + } + }, + lua = { + ["("] = { + kind = { + cmp.lsp.CompletionItemKind.Function, + cmp.lsp.CompletionItemKind.Method + }, + ---@param char string + ---@param item table item completion + ---@param bufnr number buffer number + ---@param rules table + ---@param commit_character table + handler = function(char, item, bufnr, rules, commit_character) + -- Your handler function. Inspect with print(vim.inspect{char, item, bufnr, rules, commit_character}) + end + } + }, + -- Disable for tex + tex = false + } + }) + ) +< + + +Don’t use `nil` to disable a filetype. If a filetype is `nil` then `*` is +used as fallback. + +coq_nvim ~ + +> + local remap = vim.api.nvim_set_keymap + local npairs = require('nvim-autopairs') + + npairs.setup({ map_bs = false, map_cr = false }) + + vim.g.coq_settings = { keymap = { recommended = false } } + + -- these mappings are coq recommended mappings unrelated to nvim-autopairs + remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) + remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) + remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) + remap('i', '', [[pumvisible() ? "" : ""]], { expr = true, noremap = true }) + + -- skip it, if you use another global object + _G.MUtils= {} + + MUtils.CR = function() + if vim.fn.pumvisible() ~= 0 then + if vim.fn.complete_info({ 'selected' }).selected ~= -1 then + return npairs.esc('') + else + return npairs.esc('') .. npairs.autopairs_cr() + end + else + return npairs.autopairs_cr() + end + end + remap('i', '', 'v:lua.MUtils.CR()', { expr = true, noremap = true }) + + MUtils.BS = function() + if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info({ 'mode' }).mode == 'eval' then + return npairs.esc('') .. npairs.autopairs_bs() + else + return npairs.autopairs_bs() + end + end + remap('i', '', 'v:lua.MUtils.BS()', { expr = true, noremap = true }) +< + + +without completion plugin ~ + +> + -- add option map_cr + npairs.setup({ map_cr = true }) +< + + +another completion plugin + + +If you have a problem with indent after you press `` please check the +settings of treesitter indent or install a plugin that has indent support for +your filetype. + +RULE ~ + +nvim-autopairs uses rules with conditions to check pairs. + +> + local Rule = require('nvim-autopairs.rule') + local npairs = require('nvim-autopairs') + + npairs.add_rule(Rule("$$","$$","tex")) + + -- you can use some built-in conditions + + local cond = require('nvim-autopairs.conds') + print(vim.inspect(cond)) + + npairs.add_rules({ + Rule("$", "$",{"tex", "latex"}) + -- don't add a pair if the next character is % + :with_pair(cond.not_after_regex("%%")) + -- don't add a pair if the previous character is xxx + :with_pair(cond.not_before_regex("xxx", 3)) + -- don't move right when repeat character + :with_move(cond.none()) + -- don't delete if the next character is xx + :with_del(cond.not_after_regex("xx")) + -- disable adding a newline when you press + :with_cr(cond.none()) + }, + -- disable for .vim files, but it work for another filetypes + Rule("a","a","-vim") + ) + + npairs.add_rules({ + Rule("$$","$$","tex") + :with_pair(function(opts) + print(vim.inspect(opts)) + if opts.line=="aa $$" then + -- don't add pair on that line + return false + end + end) + } + ) + + -- you can use regex + -- press u1234 => u1234number + npairs.add_rules({ + Rule("u%d%d%d%d$", "number", "lua") + :use_regex(true) + }) + + + + -- press x1234 => x12341234 + npairs.add_rules({ + Rule("x%d%d%d%d$", "number", "lua") + :use_regex(true) + :replace_endpair(function(opts) + -- print(vim.inspect(opts)) + return opts.prev_char:sub(#opts.prev_char - 3,#opts.prev_char) + end) + }) + + + -- you can do anything with regex +special key + -- example press tab to uppercase text: + -- press b1234s => B1234S1234S + + npairs.add_rules({ + Rule("b%d%d%d%d%w$", "", "vim") + :use_regex(true,"") + :replace_endpair(function(opts) + return + opts.prev_char:sub(#opts.prev_char - 4,#opts.prev_char) + .."viwU" + end) + }) + + -- you can exclude filetypes + npairs.add_rule( + Rule("$$","$$") + :with_pair(cond.not_filetypes({"lua"})) + ) + --- check ./lua/nvim-autopairs/rules/basic.lua +< + + +Rules API + +TREESITTER ~ + +You can use treesitter to check for a pair. + +> + local npairs = require("nvim-autopairs") + local Rule = require('nvim-autopairs.rule') + + npairs.setup({ + check_ts = true, + ts_config = { + lua = {'string'},-- it will not add a pair on that treesitter node + javascript = {'template_string'}, + java = false,-- don't check treesitter on java + } + }) + + local ts_conds = require('nvim-autopairs.ts-conds') + + + -- press % => %% only while inside a comment or string + npairs.add_rules({ + Rule("%", "%", "lua") + :with_pair(ts_conds.is_ts_node({'string','comment'})), + Rule("$", "$", "lua") + :with_pair(ts_conds.is_not_ts_node({'function'})) + }) +< + + +DON’T ADD PAIRS IF IT ALREADY HAS A CLOSE PAIR IN THE SAME LINE ~ + +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 + +> + Before Input After + ------------------------------------ + ( |)) ( ( (|)) +< + + +> + require('nvim-autopairs').setup({ + enable_check_bracket_line = false + }) +< + + +DON’T ADD PAIRS IF THE NEXT CHAR IS ALPHANUMERIC ~ + +You can customize how nvim-autopairs will behave if it encounters a specific +character + +> + require('nvim-autopairs').setup({ + ignored_next_char = "[%w%.]" -- will ignore alphanumeric and `.` symbol + }) +< + + +> + Before Input After + ------------------------------------ + |foobar ( (|foobar + |.foobar ( (|.foobar +< + + +PLUGIN INTEGRATION ~ + +> + require('nvim-autopairs').disable() + require('nvim-autopairs').enable() + require('nvim-autopairs').toggle() + require('nvim-autopairs').remove_rule('(') -- remove rule ( + require('nvim-autopairs').clear_rules() -- clear all rules + require('nvim-autopairs').get_rules('"') +< + + + +- Sample + + +> + -- remove add single quote on filetype scheme or lisp + require("nvim-autopairs").get_rules("'")[1].not_filetypes = { "scheme", "lisp" } + require("nvim-autopairs").get_rules("'")[1]:with_pair(cond.not_after_text("[")) +< + + +FASTWRAP ~ + +> + Before Input After Note + ----------------------------------------------------------------- + (|foobar then press $ (|foobar) + (|)(foobar) then press q (|(foobar)) + (|foo bar then press qh (|foo) bar + (|foo bar then press qH (foo|) bar + (|foo bar then press qH (foo)| bar if cursor_pos_before = false +< + + +> + -- put this to setup function and press to use fast_wrap + npairs.setup({ + fast_wrap = {}, + }) + + -- change default fast_wrap + npairs.setup({ + fast_wrap = { + map = '', + chars = { '{', '[', '(', '"', "'" }, + pattern = [=[[%'%"%>%]%)%}%,]]=], + end_key = '$', + before_key = 'h', + after_key = 'l', + cursor_pos_before = true, + avoid_move_to_end = true, -- stay for direct end_key use + keys = 'qwertyuiopzxcvbnmasdfghjkl', + manual_position = true, + highlight = 'Search', + highlight_grey='Comment' + }, + }) +< + + +AUTOTAG HTML AND TSX ~ + +autotag + +ENDWISE ~ + +endwise + +CUSTOM RULES ~ + +rules + +SPONSORS *nvim-autopairs-sponsors* + +Thanks to everyone who sponsors my projects and makes continued development +maintenance possible! + +george looshch + +Generated by panvimdoc + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/lua/nvim-autopairs.lua b/lua/nvim-autopairs.lua index d3c2aa6b..83b40f24 100644 --- a/lua/nvim-autopairs.lua +++ b/lua/nvim-autopairs.lua @@ -2,42 +2,39 @@ local log = require('nvim-autopairs._log') local utils = require('nvim-autopairs.utils') local basic_rule = require('nvim-autopairs.rules.basic') local api = vim.api - -local M={} +local highlighter = nil +local M = {} M.state = { - disabled=false, + disabled = false, rules = {}, - buf_ts = {} + buf_ts = {}, } local default = { - disable_filetype = {"TelescopePrompt", "spectre_panel"}, - ignored_next_char = string.gsub([[ [%w%%%'%[%"%.] ]],"%s+", ""), + map_bs = true, + map_c_h = false, + map_c_w = false, + map_cr = true, + enabled = nil, + disable_filetype = { 'TelescopePrompt', 'spectre_panel' }, + disable_in_macro = true, + disable_in_visualblock = false, + disable_in_replace_mode = true, + ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=], + break_undo = true, check_ts = false, enable_moveright = true, enable_afterquote = true, enable_check_bracket_line = true, + enable_bracket_in_quote = true, + enable_abbr = false, ts_config = { - lua = {'string', 'source'}, - javascript = {'string', 'template_string'} - } + lua = { 'string', 'source', 'string_content' }, + javascript = { 'string', 'template_string' }, + }, } -M.init = function() - local ok = pcall(require, 'nvim-treesitter') - if ok then - require "nvim-treesitter".define_modules { - autopairs = { - module_path = 'nvim-autopairs.internal', - is_supported = function() - return true - end - } - } - end -end - M.setup = function(opt) M.config = vim.tbl_deep_extend('force', default, opt or {}) if M.config.fast_wrap then @@ -48,39 +45,63 @@ M.setup = function(opt) if M.config.check_ts then local ok, ts_rule = pcall(require, 'nvim-autopairs.rules.ts_basic') if ok then + highlighter = require "vim.treesitter.highlighter" M.config.rules = ts_rule.setup(M.config) - else - print("you need to install treesitter") end end - M.force_attach() + if M.config.map_cr then + M.map_cr() + end - api.nvim_exec ([[ - augroup autopairs_buf - autocmd! - autocmd BufEnter * :lua require("nvim-autopairs").on_attach() - autocmd FileType * :lua require("nvim-autopairs").force_attach() - augroup end - ]],false) + M.force_attach() + local group = api.nvim_create_augroup('autopairs_buf', { clear = true }) + api.nvim_create_autocmd({ 'BufEnter', 'BufWinEnter' }, { + group = group, + pattern = '*', + callback = function() M.on_attach() end + }) + api.nvim_create_autocmd('BufDelete', { + group = group, + pattern = '*', + callback = function(data) + local cur = api.nvim_get_current_buf() + local bufnr = tonumber(data.buf) or 0 + if bufnr ~= cur then + M.set_buf_rule(nil, bufnr) + end + end, + }) + api.nvim_create_autocmd('FileType', { + group = group, + pattern = '*', + callback = function() M.force_attach() end + }) end -M.add_rule = function (rule) - M.add_rules({rule}) +M.add_rule = function(rule) + M.add_rules({ rule }) end M.get_rule = function(start_pair) + local tbl = M.get_rules(start_pair) + if #tbl == 1 then + return tbl[1] + end + return tbl +end + +M.get_rules = function(start_pair) local tbl = {} - for _,r in pairs(M.config.rules) do + for _, r in pairs(M.config.rules) do if r.start_pair == start_pair then table.insert(tbl, r) end end - if #tbl == 1 then return tbl[1] end return tbl end -M.remove_rule = function (pair) +M.remove_rule = function(pair) local tbl = {} for _, r in pairs(M.config.rules) do if r.start_pair ~= pair then @@ -88,9 +109,22 @@ M.remove_rule = function (pair) end end M.config.rules = tbl + if M.state.rules then + local state_tbl = {} + local rules = M.get_buf_rules() + for _, r in pairs(rules) do + if r.start_pair ~= pair then + table.insert(state_tbl, r) + elseif r.key_map and r.key_map ~= '' then + api.nvim_buf_del_keymap(0, 'i', r.key_map) + end + end + M.set_buf_rule(state_tbl, 0) + end + M.force_attach() end -M.add_rules = function (rules) +M.add_rules = function(rules) for _, rule in pairs(rules) do table.insert(M.config.rules, rule) end @@ -98,10 +132,11 @@ M.add_rules = function (rules) end M.clear_rules = function() + M.state.rules = {} M.config.rules = {} end -M.disable=function() +M.disable = function() M.state.disabled = true end @@ -109,106 +144,225 @@ M.enable = function() M.state.disabled = false end +M.toggle = function() + M.state.disabled = not M.state.disabled +end + --- force remap key to buffer M.force_attach = function(bufnr) utils.set_attach(bufnr, 0) M.on_attach(bufnr) end +local del_keymaps = function() + local status, autopairs_keymaps = pcall(api.nvim_buf_get_var, 0, 'autopairs_keymaps') + if status and autopairs_keymaps and #autopairs_keymaps > 0 then + for _, key in pairs(autopairs_keymaps) do + pcall(api.nvim_buf_del_keymap, 0, 'i', key) + end + end +end + local function is_disable() - if M.state.disabled then return true end - if utils.check_filetype(M.config.disable_filetype, vim.bo.filetype) then - -- should have a way to remove the mapping when vim.bo.filetype = '' - -- now we only remove a rule - -- the event FileType happen after BufEnter - M.state.rules = {} + if M.state.disabled then + return true + end + + if vim.bo.filetype == '' and api.nvim_win_get_config(0).relative ~= '' then + -- disable for any floating window without filetype + return true + end + + if vim.bo.modifiable == false then + return true + end + + if M.config.disable_in_macro + and (vim.fn.reg_recording() ~= '' or vim.fn.reg_executing() ~= '') + then + return true + end + + if M.config.disable_in_replace_mode and vim.api.nvim_get_mode().mode == "R" then + return true + end + + if M.config.disable_in_visualblock and utils.is_block_wise_mode() then + return true + end + + if vim.v.count > 0 then + return true + end + + if + utils.check_filetype(M.config.disable_filetype, vim.bo.filetype) + or (M.config.enabled and not M.config.enabled(api.nvim_get_current_buf())) + then + del_keymaps() + M.set_buf_rule({}, 0) return true end return false end +---@return table +M.get_buf_rules = function(bufnr) + return M.state.rules[bufnr or api.nvim_get_current_buf()] or {} +end + +---@param rules nil|table list or rule +---@param bufnr number buffer number +M.set_buf_rule = function(rules, bufnr) + if bufnr == 0 or bufnr == nil then + bufnr = api.nvim_get_current_buf() + end + M.state.rules[bufnr] = rules +end + + M.on_attach = function(bufnr) - if is_disable() then return end + -- log.debug('on_attach' .. vim.bo.filetype) + if is_disable() then + return + end bufnr = bufnr or api.nvim_get_current_buf() local rules = {} for _, rule in pairs(M.config.rules) do - if utils.check_filetype(rule.filetypes,vim.bo.filetype) then + if utils.check_filetype(rule.filetypes, vim.bo.filetype) + and utils.check_not_filetype(rule.not_filetypes, vim.bo.filetype) + then table.insert(rules, rule) end end - -- sort by length - table.sort(rules, function (a, b) - return (#a.start_pair or 0) > (#b.start_pair or 0) + -- sort by pair and keymap + table.sort(rules, function(a, b) + if a.start_pair == b.start_pair then + if not b.key_map then + return a.key_map + end + if not a.key_map then + return b.key_map + end + return #a.key_map < #b.key_map + end + if #a.start_pair == #b.start_pair then + return string.byte(a.start_pair) > string.byte(b.start_pair) + end + return #a.start_pair > #b.start_pair end) - M.state.rules = rules + M.set_buf_rule(rules, bufnr) - if M.state.buf_ts[bufnr] == true then - M.state.ts_node = M.config.ts_config[vim.bo.filetype] - else - M.state.ts_node = nil + if M.config.check_ts then + if highlighter and highlighter.active[bufnr] then + M.state.ts_node = M.config.ts_config[vim.bo.filetype] + else + M.state.ts_node = nil + end end - if utils.is_attached(bufnr) then return end + if utils.is_attached(bufnr) then + return + end + del_keymaps() local enable_insert_auto = false - for _, rule in pairs(M.state.rules) do + local autopairs_keymaps = {} + local expr_map = function(key) + api.nvim_buf_set_keymap(bufnr, 'i', key, '', { + expr = true, + noremap = true, + desc = "autopairs map key", + callback = function() return M.autopairs_map(bufnr, key) end, + }) + table.insert(autopairs_keymaps, key) + end + for _, rule in pairs(rules) do if rule.key_map ~= nil then if rule.is_regex == false then - if rule.key_map == "" then - rule.key_map = rule.start_pair:sub((#rule.start_pair)) + if rule.key_map == '' then + rule.key_map = rule.start_pair:sub(#rule.start_pair) end - local key = string.format('"%s"', rule.key_map) - if rule.key_map == '"' then key = [['"']] end - local mapping = string.format("v:lua.MPairs.autopairs_map(%d,%s)", bufnr, key) - api.nvim_buf_set_keymap(bufnr, "i", rule.key_map, mapping, {expr = true, noremap = true}) - - local key_end = rule.end_pair:sub(1,1) - if #key_end == 1 and key_end ~= rule.key_map and rule.move_cond ~= nil then - mapping = string.format([[v:lua.MPairs.autopairs_map(%d, '%s')]], bufnr,key_end ) - vim.api.nvim_buf_set_keymap(bufnr, 'i',key_end, mapping, {expr = true, noremap = true}) + expr_map(rule.key_map) + local key_end = rule.key_end or rule.end_pair:sub(1, 1) + if #key_end >= 1 and key_end ~= rule.key_map and rule.move_cond ~= nil then + expr_map(key_end) end else - if rule.key_map ~= "" then - local mapping = string.format("v:lua.MPairs.autopairs_map(%d,'%s')", bufnr, rule.key_map) - api.nvim_buf_set_keymap(bufnr, "i", rule.key_map, mapping, {expr = true, noremap = true}) + if rule.key_map ~= '' then + expr_map(rule.key_map) elseif rule.is_endwise == false then enable_insert_auto = true end end end end + api.nvim_buf_set_var(bufnr, 'autopairs_keymaps', autopairs_keymaps) if enable_insert_auto then -- capture all key use it to trigger regex pairs -- it can make an issue with paste from register - api.nvim_exec(string.format([[ - augroup autopairs_insert_%d - autocmd! - autocmd InsertCharPre call luaeval("require('nvim-autopairs').autopairs_insert(%d, _A)", v:char) - augroup end ]], - bufnr, bufnr, bufnr), false) + api.nvim_create_autocmd('InsertCharPre', { + group = api.nvim_create_augroup(string.format("autopairs_insert_%d", bufnr), { clear = true }), + buffer = bufnr, + callback = function() + M.autopairs_insert(bufnr, vim.v.char) + end + }) end if M.config.fast_wrap and M.config.fast_wrap.map then - api.nvim_buf_set_keymap(bufnr, "i", + api.nvim_buf_set_keymap( + bufnr, + 'i', M.config.fast_wrap.map, "llua require('nvim-autopairs.fastwrap').show()", - {noremap = true}) + { noremap = true } + ) end - api.nvim_buf_set_keymap(bufnr, - 'i', - "", - string.format("v:lua.MPairs.autopairs_bs(%d)", bufnr), {expr = true, noremap = true}) - api.nvim_buf_set_var(bufnr, "nvim-autopairs", 1) + if M.config.map_bs then + api.nvim_buf_set_keymap( + bufnr, + 'i', + '', + '', + { callback = M.autopairs_bs, expr = true, noremap = true } + ) + end + + if M.config.map_c_h then + api.nvim_buf_set_keymap( + bufnr, + "i", + utils.key.c_h, + '', + { callback = M.autopairs_c_h, expr = true, noremap = true } + ) + end + + if M.config.map_c_w then + api.nvim_buf_set_keymap( + bufnr, + 'i', + '', + '', + { callback = M.autopairs_c_w, expr = true, noremap = true } + ) + end + api.nvim_buf_set_var(bufnr, 'nvim-autopairs', 1) end -M.autopairs_bs = function(bufnr) - if is_disable() then return utils.esc(utils.key.bs) end +local autopairs_delete = function(bufnr, key) + if is_disable() then + return utils.esc(key) + end + bufnr = bufnr or api.nvim_get_current_buf() local line = utils.text_get_current_line(bufnr) local _, col = utils.get_cursor() - for _, rule in pairs(M.state.rules) do + local rules = M.get_buf_rules(bufnr) + for _, rule in pairs(rules) do if rule.start_pair then local prev_char, next_char = utils.text_cusor_line( line, @@ -217,48 +371,61 @@ M.autopairs_bs = function(bufnr) #rule.end_pair, rule.is_regex ) - if - utils.is_equal(rule.start_pair, prev_char, rule.is_regex) - and rule.end_pair == next_char + if utils.compare(rule.start_pair, prev_char, rule.is_regex) + and utils.compare(rule.end_pair, next_char, rule.is_regex) and rule:can_del({ ts_node = M.state.ts_node, + rule = rule, bufnr = bufnr, prev_char = prev_char, next_char = next_char, - line = line + line = line, + col = col, }) then - local input = "" - for _ = 1, (#rule.start_pair), 1 do + local input = '' + for _ = 1, api.nvim_strwidth(rule.start_pair), 1 do input = input .. utils.key.bs end - for _ = 1, #rule.end_pair, 1 do - input = input .. utils.key.right .. utils.key.bs + for _ = 1, api.nvim_strwidth(rule.end_pair), 1 do + input = input .. utils.key.del end - return utils.esc("U" .. input) + return utils.esc('U' .. input) end end end - return utils.esc(utils.key.bs) + return utils.esc(key) +end + +M.autopairs_c_w = function(bufnr) + return autopairs_delete(bufnr, 'U') +end + +M.autopairs_c_h = function(bufnr) + return autopairs_delete(bufnr, utils.key.c_h) +end + +M.autopairs_bs = function(bufnr) + return autopairs_delete(bufnr, utils.key.bs) end M.autopairs_map = function(bufnr, char) - if is_disable() then return char end + if is_disable() then + return char + end local line = utils.text_get_current_line(bufnr) local _, col = utils.get_cursor() - local new_text = "" + local new_text = '' local add_char = 1 - for _, rule in pairs(M.state.rules) do + local rules = M.get_buf_rules(bufnr) + for _, rule in pairs(rules) do if rule.start_pair then - if rule.is_regex and rule.key_map and rule.key_map ~= "" then - new_text = line:sub(1, col) .. line:sub(col + 1,#line) - add_char = 0 - elseif rule.key_map and #rule.key_map > 1 and utils.esc(rule.key_map) == char then - new_text = line:sub(1, col) .. line:sub(col + 1,#line) + if char:match('<.*>') then + new_text = line add_char = 0 else - new_text = line:sub(1, col) .. char .. line:sub(col + 1,#line) - add_char = 1 + new_text = line:sub(1, col) .. char .. line:sub(col + 1, #line) + add_char = rule.key_map and #rule.key_map or 1 end -- log.debug("new_text:[" .. new_text .. "]") @@ -266,7 +433,8 @@ M.autopairs_map = function(bufnr, char) new_text, col + add_char, #rule.start_pair, - #rule.end_pair, rule.is_regex + #rule.end_pair, + rule.is_regex ) local cond_opt = { ts_node = M.state.ts_node, @@ -282,43 +450,64 @@ M.autopairs_map = function(bufnr, char) -- log.debug("start_pair" .. rule.start_pair) -- log.debug('prev_char' .. prev_char) -- log.debug('next_char' .. next_char) - if - utils.is_equal(rule.end_pair, next_char, rule.is_regex) + local char_matches_rule = (rule.end_pair == char or rule.key_map == char) + -- for simple pairs, char will match end_pair + -- for more complex pairs, user should map the wanted end char with `use_key` + -- on a dedicated rule + if char_matches_rule + and utils.compare(rule.end_pair, next_char, rule.is_regex) and rule:can_move(cond_opt) then local end_pair = rule:get_end_pair(cond_opt) - return utils.esc(utils.repeat_key(utils.key.join_right, #end_pair)) + local end_pair_length = rule:get_end_pair_length(end_pair) + return utils.esc(utils.repeat_key(utils.key.join_right, end_pair_length)) end - if - utils.is_equal(rule.start_pair, prev_char, rule.is_regex) + if rule.key_map == char + and utils.compare(rule.start_pair, prev_char, rule.is_regex) and rule:can_pair(cond_opt) then local end_pair = rule:get_end_pair(cond_opt) - local move_text = utils.repeat_key(utils.key.join_left,#end_pair) + local end_pair_length = rule:get_end_pair_length(end_pair) + local move_text = utils.repeat_key(utils.key.join_left, end_pair_length) if add_char == 0 then - move_text ="" - char = "" + move_text = '' + char = '' + end + if end_pair:match('<.*>') then + end_pair = utils.esc(end_pair) + end + local result = char .. end_pair .. utils.esc(move_text) + if rule.is_undo then + result = utils.esc(utils.key.undo_sequence) .. result .. utils.esc(utils.key.undo_sequence) + end + if M.config.enable_abbr then + result = utils.esc(utils.key.abbr) .. result end - return utils.esc(char .. end_pair .. move_text) + log.debug("key_map :" .. result) + return result end end end - return M.autopairs_afterquote(new_text, char) + return M.autopairs_afterquote(new_text, utils.esc(char)) end M.autopairs_insert = function(bufnr, char) - if is_disable() then return char end + if is_disable() then + return char + end local line = utils.text_get_current_line(bufnr) local _, col = utils.get_cursor() - local new_text = line:sub(1, col) .. char .. line:sub(col + 1,#line) - for _, rule in pairs(M.state.rules) do - if rule.start_pair and rule.is_regex and rule.key_map == "" then + local new_text = line:sub(1, col) .. char .. line:sub(col + 1, #line) + local rules = M.get_buf_rules(bufnr) + for _, rule in pairs(rules) do + if rule.start_pair and rule.is_regex and rule.key_map == '' then local prev_char, next_char = utils.text_cusor_line( new_text, col + 1, #rule.start_pair, - #rule.end_pair, rule.is_regex + #rule.end_pair, + rule.is_regex ) local cond_opt = { ts_node = M.state.ts_node, @@ -334,25 +523,21 @@ M.autopairs_insert = function(bufnr, char) -- log.debug("start_pair" .. rule.start_pair) -- log.debug('prev_char' .. prev_char) -- log.debug('next_char' .. next_char) - if - next_char == rule.end_pair - and rule:can_move(cond_opt) - then - utils.set_vchar("") + if next_char == rule.end_pair and rule:can_move(cond_opt) then + utils.set_vchar('') vim.schedule(function() utils.feed(utils.key.right, -1) end) return false end - if - utils.is_equal(rule.start_pair, prev_char, rule.is_regex) + if utils.compare(rule.start_pair, prev_char, rule.is_regex) and rule:can_pair(cond_opt) then local end_pair = rule:get_end_pair(cond_opt) utils.set_vchar(char .. end_pair) vim.schedule(function() - utils.feed(utils.key.left, #end_pair) + utils.feed(utils.key.left, rule:get_end_pair_length(end_pair)) end) return end @@ -362,12 +547,15 @@ M.autopairs_insert = function(bufnr, char) end M.autopairs_cr = function(bufnr) - if is_disable() then return utils.esc("") end + if is_disable() then + return utils.esc('') + end bufnr = bufnr or api.nvim_get_current_buf() local line = utils.text_get_current_line(bufnr) local _, col = utils.get_cursor() -- log.debug("on_cr") - for _, rule in pairs(M.state.rules) do + local rules = M.get_buf_rules(bufnr) + for _, rule in pairs(rules) do if rule.start_pair then local prev_char, next_char = utils.text_cusor_line( line, @@ -376,45 +564,40 @@ M.autopairs_cr = function(bufnr) #rule.end_pair, rule.is_regex ) + + local cond_opt = { + ts_node = M.state.ts_node, + check_endwise_ts = true, + rule = rule, + bufnr = bufnr, + col = col, + line = line, + prev_char = prev_char, + next_char = next_char, + } -- log.debug('prev_char' .. rule.start_pair) -- log.debug('prev_char' .. prev_char) -- log.debug('next_char' .. next_char) - if - rule.is_endwise - and utils.is_equal(rule.start_pair, prev_char, rule.is_regex) - and rule:can_cr({ - ts_node = M.state.ts_node, - check_endwise_ts = true, - bufnr = bufnr, - rule = rule, - col = col, - prev_char = prev_char, - next_char = next_char, - line = line - }) + if rule.is_endwise + and utils.compare(rule.start_pair, prev_char, rule.is_regex) + and rule:can_cr(cond_opt) then + local end_pair = rule:get_end_pair(cond_opt) return utils.esc( - rule.end_pair - .. utils.repeat_key(utils.key.join_left, #rule.end_pair) - .. "==O" + '' .. end_pair + -- FIXME do i need to re indent twice #118 + .. 'normal! ====' ) end - if - utils.is_equal(rule.start_pair, prev_char, rule.is_regex) - and rule.end_pair == next_char - and rule:can_cr({ - ts_node = M.state.ts_node, - check_endwise_ts = false, - bufnr = bufnr, - rule = rule, - col = col, - prev_char = prev_char, - next_char = next_char, - line = line, - }) + + cond_opt.check_endwise_ts = false + + if utils.compare(rule.start_pair, prev_char, rule.is_regex) + and utils.compare(rule.end_pair, next_char, rule.is_regex) + and rule:can_cr(cond_opt) then log.debug('do_cr') - return utils.esc('O') + return utils.esc(rule:get_map_cr({ rule = rule, line = line, color = col, bufnr = bufnr })) end end end @@ -423,19 +606,18 @@ end --- add bracket pairs after quote (|"aaaaa" => (|"aaaaaa") M.autopairs_afterquote = function(line, key_char) - if M.config.enable_afterquote then + if M.config.enable_afterquote and not utils.is_block_wise_mode() then line = line or utils.text_get_current_line(0) local _, col = utils.get_cursor() local prev_char, next_char = utils.text_cusor_line(line, col + 1, 1, 1, false) - if - utils.is_bracket(prev_char) + if utils.is_bracket(prev_char) and utils.is_quote(next_char) - and not utils.is_in_quote(line, col, next_char) + and not utils.is_in_quotes(line, col, next_char) then local count = 0 local index = 0 local is_prev_slash = false - local char_end='' + local char_end = '' for i = col, #line, 1 do local char = line:sub(i, i + #next_char - 1) if not is_prev_slash and char == next_char then @@ -445,29 +627,31 @@ M.autopairs_afterquote = function(line, key_char) end is_prev_slash = char == '\\' end - if count == 2 and index >=(#line -2) then - for _, rule in pairs(M.state.rules) do + if count == 2 and index >= (#line - 2) then + local rules = M.get_buf_rules(api.nvim_get_current_buf()) + for _, rule in pairs(rules) do if rule.start_pair == prev_char and char_end ~= rule.end_pair then local new_text = line:sub(0, index) - .. rule.end_pair - .. line:sub(index + 1, #line) + .. rule.end_pair + .. line:sub(index + 1, #line) M.state.expr_quote = new_text local append = 'a' if col > 0 then append = 'la' end return utils.esc( - 'lua MPairs.autopairs_closequote_expr()' .. append + "lua require'nvim-autopairs'.autopairs_closequote_expr()" .. append ) end end end end end - return utils.esc(key_char) + return key_char end -M.autopairs_closequote_expr = function () +M.autopairs_closequote_expr = function() + ---@diagnostic disable-next-line: param-type-mismatch vim.fn.setline('.', M.state.expr_quote) end @@ -475,6 +659,22 @@ M.check_break_line_char = function() return M.autopairs_cr() end +M.completion_confirm = function() + if vim.fn.pumvisible() ~= 0 then + return M.esc("") + else + return M.autopairs_cr() + end +end + +M.map_cr = function() + api.nvim_set_keymap( + 'i', + '', + "v:lua.require'nvim-autopairs'.completion_confirm()", + { expr = true, noremap = true } + ) +end + M.esc = utils.esc -_G.MPairs = M return M diff --git a/lua/nvim-autopairs/_log.lua b/lua/nvim-autopairs/_log.lua index f7c80cff..25061efc 100644 --- a/lua/nvim-autopairs/_log.lua +++ b/lua/nvim-autopairs/_log.lua @@ -1,13 +1,13 @@ +---@diagnostic disable: undefined-field +local empty = { + debug = function(_) end, + info = function(_) end, + error = function(_) end, +} if _G.__is_log then return require('plenary.log').new { plugin = 'nvim-autopairs', level = (_G.__is_log == true and 'debug') or 'warn', - } -else - return{ - debug = function(_) end, - info = function(_) end, - error = function(_) end, - - } + } or empty end +return empty diff --git a/lua/nvim-autopairs/completion/cmp.lua b/lua/nvim-autopairs/completion/cmp.lua new file mode 100644 index 00000000..790585df --- /dev/null +++ b/lua/nvim-autopairs/completion/cmp.lua @@ -0,0 +1,98 @@ +local autopairs = require('nvim-autopairs') +local handlers = require('nvim-autopairs.completion.handlers') +local cmp = require('cmp') + +local Kind = cmp.lsp.CompletionItemKind + +local M = {} + +M.filetypes = { + -- Alias to all filetypes + ["*"] = { + ["("] = { + kind = { Kind.Function, Kind.Method }, + handler = handlers["*"] + } + }, + python = { + ["("] = { + kind = { Kind.Function, Kind.Method }, + handler = handlers.python + } + }, + clojure = { + ["("] = { + kind = { Kind.Function, Kind.Method }, + handler = handlers.lisp + } + }, + clojurescript = { + ["("] = { + kind = { Kind.Function, Kind.Method }, + handler = handlers.lisp + } + }, + fennel = { + ["("] = { + kind = { Kind.Function, Kind.Method }, + handler = handlers.lisp + } + }, + janet = { + ["("] = { + kind = { Kind.Function, Kind.Method }, + handler = handlers.lisp + } + }, + tex = false, + plaintex = false, + context = false, + haskell = false, + purescript = false, + sh = false, + bash = false, + nix = false, + helm = false +} + +M.on_confirm_done = function(opts) + opts = vim.tbl_deep_extend('force', { + filetypes = M.filetypes + }, opts or {}) + + return function(evt) + if evt.commit_character then + return + end + + local entry = evt.entry + local commit_character = entry:get_commit_characters() + local bufnr = vim.api.nvim_get_current_buf() + local filetype = vim.api.nvim_buf_get_option(bufnr, 'filetype') + local item = entry:get_completion_item() + + -- Without options and fallback + if not opts.filetypes[filetype] and not opts.filetypes["*"] then + return + end + + if opts.filetypes[filetype] == false then + return + end + + -- If filetype is nil then use * + local completion_options = opts.filetypes[filetype] or opts.filetypes["*"] + + local rules = vim.tbl_filter(function(rule) + return completion_options[rule.key_map] + end, autopairs.get_buf_rules(bufnr)) + + for char, value in pairs(completion_options) do + if vim.tbl_contains(value.kind, item.kind) then + value.handler(char, item, bufnr, rules, commit_character) + end + end + end +end + +return M diff --git a/lua/nvim-autopairs/completion/compe.lua b/lua/nvim-autopairs/completion/compe.lua index 708a4bb5..04cdb92d 100644 --- a/lua/nvim-autopairs/completion/compe.lua +++ b/lua/nvim-autopairs/completion/compe.lua @@ -5,11 +5,19 @@ local utils = require('nvim-autopairs.utils') local method_kind = nil local function_kind = nil -_G.MPairs.completion_done = function() +local options = {} + +local M = {} +M.completion_done = function() local line = utils.text_get_current_line(0) local _, col = utils.get_cursor() local prev_char, next_char = utils.text_cusor_line(line, col, 1, 1, false) - if prev_char ~= '(' and next_char ~= '(' then + + local filetype = vim.bo.filetype + local char = options.map_char[filetype] or options.map_char["all"] or '(' + if char == '' then return end + + if prev_char ~= char and next_char ~= char then if method_kind == nil then method_kind = require('vim.lsp.protocol').CompletionItemKind[2] function_kind = require('vim.lsp.protocol').CompletionItemKind[3] @@ -22,43 +30,55 @@ _G.MPairs.completion_done = function() ( completion_item.textEdit and completion_item.textEdit.newText - and completion_item.textEdit.newText:match('%(') + and completion_item.textEdit.newText:match('[%(%[%$]') ) - or (completion_item.insertText and completion_item.insertText:match('%(')) + or (completion_item.insertText and completion_item.insertText:match('[%(%[%$]')) then return end - vim.api.nvim_feedkeys('(', 'i', true) + vim.api.nvim_feedkeys(char, 'i', true) end end end -_G.MPairs.completion_confirm = function() - if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info()['selected'] ~= -1 then - return vim.fn['compe#confirm'](npairs.esc('')) - else - return npairs.autopairs_cr() - end -end - -local M = {} M.setup = function(opt) - opt = opt or { map_cr = true, map_complete = true } + opt = opt or { map_cr = true, map_complete = true, auto_select = false, map_char = {all = '('}} + if not opt.map_char then opt.map_char = {} end + options = opt local map_cr = opt.map_cr local map_complete = opt.map_complete vim.g.completion_confirm_key = '' if map_cr then vim.api.nvim_set_keymap( - 'i', '', 'v:lua.MPairs.completion_confirm()', - { expr = true, noremap = true } + 'i', + '', + '', + { callback = M.completion_confirm, expr = true, noremap = true } ) end + if opt.auto_select then + M.completion_confirm = function() + if vim.fn.pumvisible() ~= 0 then + return vim.fn['compe#confirm']({ keys = '', select = true }) + else + return npairs.autopairs_cr() + end + end + else + M.completion_confirm = function() + if vim.fn.pumvisible() ~= 0 and vim.fn.complete_info()['selected'] ~= -1 then + return vim.fn['compe#confirm'](npairs.esc('')) + else + return npairs.autopairs_cr() + end + end + end if map_complete then vim.cmd([[ augroup autopairs_compe autocmd! - autocmd User CompeConfirmDone call v:lua.MPairs.completion_done() + autocmd User CompeConfirmDone lua require'nvim-autopairs.completion.compe'.completion_done() augroup end ]]) end diff --git a/lua/nvim-autopairs/completion/handlers.lua b/lua/nvim-autopairs/completion/handlers.lua new file mode 100644 index 00000000..b0df4331 --- /dev/null +++ b/lua/nvim-autopairs/completion/handlers.lua @@ -0,0 +1,91 @@ +local autopairs = require('nvim-autopairs') +local utils = require('nvim-autopairs.utils') + +local M = {} + +---@param char string +---@param item table +---@param bufnr number +---@param rules table +---@param commit_character table +M["*"] = function(char, item, bufnr, rules, _) + local line = utils.text_get_current_line(bufnr) + local _, col = utils.get_cursor() + local char_before, char_after = utils.text_cusor_line(line, col, 1, 1, false) + + if char == '' or char_before == char or char_after == char + or (item.data and type(item.data) == 'table' and item.data.funcParensDisabled) + or (item.textEdit and item.textEdit.newText and item.textEdit.newText:match "[%(%[%$]") + or (item.insertText and item.insertText:match "[%(%[%$]") + then + return + end + + if vim.tbl_isempty(rules) then + return + end + + local new_text = '' + local add_char = 1 + + for _, rule in pairs(rules) do + if rule.start_pair then + local prev_char, next_char = utils.text_cusor_line( + new_text, + col + add_char, + #rule.start_pair, + #rule.end_pair, + rule.is_regex + ) + local cond_opt = { + ts_node = autopairs.state.ts_node, + text = new_text, + rule = rule, + bufnr = bufnr, + col = col + 1, + char = char, + line = line, + prev_char = prev_char, + next_char = next_char, + } + if rule.key_map and rule:can_pair(cond_opt) then + vim.api.nvim_feedkeys(rule.key_map, "i", true) + return + end + end + end +end + +---Handler with "clojure", "clojurescript", "fennel", "janet +M.lisp = function (char, item, bufnr, _, _) + local line = utils.text_get_current_line(bufnr) + local _, col = utils.get_cursor() + local char_before, char_after = utils.text_cusor_line(line, col, 1, 1, false) + local length = #item.label + + if char == '' or char_before == char or char_after == char + or (item.data and item.data.funcParensDisabled) + or (item.textEdit and item.textEdit.newText and item.textEdit.newText:match "[%(%[%$]") + or (item.insertText and item.insertText:match "[%(%[%$]") + then + return + end + + if utils.text_sub_char(line, col - length, 1) == "(" then + utils.feed("") + return + end + utils.feed(utils.key.left, length) + utils.feed(char) + utils.feed(utils.key.right, length) + utils.feed("") +end + +M.python = function(char, item, bufnr, rules, _) + if item.data then + item.data.funcParensDisabled = false + end + M["*"](char,item,bufnr,rules) +end + +return M diff --git a/lua/nvim-autopairs/conds.lua b/lua/nvim-autopairs/conds.lua index 886bde35..a07518e0 100644 --- a/lua/nvim-autopairs/conds.lua +++ b/lua/nvim-autopairs/conds.lua @@ -1,5 +1,16 @@ local utils = require('nvim-autopairs.utils') local log = require('nvim-autopairs._log') +---@class CondOpts +---@field ts_node table +---@field text string +---@field rule table +---@field bufnr number +---@field col number +---@field char string +---@field line string +---@field prev_char string +---@field next_char string +---@field is_endwise string local cond = {} @@ -7,12 +18,11 @@ local cond = {} -- @return false when it is not correct -- true when it is correct -- nil when it is not determine - - +-- stylua: ignore cond.none = function() return function() return false end end - +-- stylua: ignore cond.done = function() return function() return true end end @@ -20,26 +30,33 @@ end cond.invert = function(func) return function(...) local result = func(...) - if result ~= nil then return not result end + if result ~= nil then + return not result + end return nil end end -cond.before_regex_check = function(regex, length) +cond.before_regex = function(regex, length) length = length or 1 + if length < 0 then length = nil end + length = length and -length + ---@param opts CondOpts return function(opts) - log.debug('before_regex_check') - local str = utils.text_sub_char(opts.line, opts.col - 1, -length) + log.debug('before_regex') + local str = utils.text_sub_char(opts.line, opts.col - 1, length or -opts.col) if str:match(regex) then return true end return false end end -cond.before_text_check = function(text) + +cond.before_text = function(text) local length = #text + ---@param opts CondOpts return function(opts) - log.debug('before_text_check') + log.debug('before_text') local str = utils.text_sub_char(opts.line, opts.col - 1, -length) if str == text then return true @@ -47,10 +64,12 @@ cond.before_text_check = function(text) return false end end -cond.after_text_check = function(text) + +cond.after_text = function(text) local length = #text + ---@param opts CondOpts return function(opts) - log.debug('after_text_check') + log.debug('after_text') local str = utils.text_sub_char(opts.line, opts.col, length) if str == text then return true @@ -59,12 +78,13 @@ cond.after_text_check = function(text) end end -cond.after_regex_check = function(regex, length) +cond.after_regex = function(regex, length) length = length or 1 + if length < 0 then length = nil end + ---@param opts CondOpts return function(opts) - if not regex then return end - log.debug('after_regex_check') - local str = utils.text_sub_char(opts.line, opts.col, length) + log.debug('after_regex') + local str = utils.text_sub_char(opts.line, opts.col, length or #opts.line) if str:match(regex) then return true end @@ -72,10 +92,10 @@ cond.after_regex_check = function(regex, length) end end -cond.not_before_text_check = function(text) +cond.not_before_text = function(text) local length = #text return function(opts) - log.debug('not_before_text_check') + log.debug('not_before_text') local str = utils.text_sub_char(opts.line, opts.col - 1, -length) if str == text then return false @@ -83,10 +103,11 @@ cond.not_before_text_check = function(text) end end -cond.not_after_text_check = function(text) +cond.not_after_text = function(text) local length = #text + ---@param opts CondOpts return function(opts) - log.debug('not_after_text_check') + log.debug('not_after_text') local str = utils.text_sub_char(opts.line, opts.col, length) if str == text then return false @@ -94,47 +115,84 @@ cond.not_after_text_check = function(text) end end -cond.not_before_regex_check = function(regex, length) +cond.not_before_regex = function(regex, length) length = length or 1 + if length < 0 then length = nil end + length = length and -length + ---@param opts CondOpts return function(opts) - log.debug('not_before_regex_check') - local str = utils.text_sub_char(opts.line, opts.col - 1, -length) + log.debug('not_before_regex') + log.debug(length) + local str = utils.text_sub_char(opts.line, opts.col - 1, length or -opts.col) if str:match(regex) then return false end end end -cond.not_after_regex_check = function(regex, length) +cond.not_after_regex = function(regex, length) length = length or 1 + if length < 0 then length = nil end + ---@param opts CondOpts return function(opts) - if not regex then - return - end - log.debug('not_after_regex_check') - local str = utils.text_sub_char(opts.line, opts.col, length) + log.debug('not_after_regex') + local str = utils.text_sub_char(opts.line, opts.col, length or #opts.line) if str:match(regex) then return false end end end -cond.check_is_bracket_line = function() +local function count_bracket_char(line, prev_char, next_char) + local count_prev_char = 0 + local count_next_char = 0 + for i = 1, #line, 1 do + local c = line:sub(i, i) + if c == prev_char then + count_prev_char = count_prev_char + 1 + elseif c == next_char then + count_next_char = count_next_char + 1 + end + end + return count_prev_char, count_next_char +end + +-- Checks if bracket chars are balanced around specific postion. +---@param line string +---@param open_char string +---@param close_char string +---@param col integer position +local function is_brackets_balanced_around_position(line, open_char, close_char, col) + local balance = 0 + for i = 1, #line, 1 do + local c = line:sub(i, i) + if c == open_char then + balance = balance + 1 + elseif balance > 0 and c == close_char then + balance = balance - 1 + if col <= i and balance == 0 then + break + end + end + end + return balance == 0 +end + +cond.is_bracket_line = function() + ---@param opts CondOpts return function(opts) - log.debug('check_is_bracket_line') - if utils.is_bracket(opts.char) and opts.next_char == opts.rule.end_pair then + log.debug('is_bracket_line') + if utils.is_bracket(opts.char) and + (opts.next_char == opts.rule.end_pair + or opts.next_char == opts.rule.start_pair) + then -- (( many char |)) => add -- ( many char |)) => not add - local count_prev_char = 0 - local count_next_char = 0 - for i = 1, #opts.line, 1 do - local c = opts.line:sub(i, i) - if c == opts.char then - count_prev_char = count_prev_char + 1 - elseif c == opts.rule.end_pair then - count_next_char = count_next_char + 1 - end - end + local count_prev_char, count_next_char = count_bracket_char( + opts.line, + opts.rule.start_pair, + opts.rule.end_pair + ) if count_prev_char ~= count_next_char then return false end @@ -142,22 +200,52 @@ cond.check_is_bracket_line = function() end end +cond.is_bracket_line_move = function() + ---@param opts CondOpts + return function(opts) + log.debug('is_bracket_line_move') + if utils.is_close_bracket(opts.char) + and opts.char == opts.rule.end_pair + then + -- (( many char |)) => move + -- (( many char |) => not move + local is_balanced = is_brackets_balanced_around_position( + opts.line, + opts.rule.start_pair, + opts.char, + opts.col + ) + return is_balanced + end + end +end cond.not_inside_quote = function() + ---@param opts CondOpts return function(opts) - log.debug('not_add_quote_inside_quote') - if utils.is_in_quote(opts.text, opts.col - 1, opts.char) then + log.debug('not_inside_quote') + if utils.is_in_quotes(opts.text, opts.col - 1) then return false end end end +cond.is_inside_quote = function() + ---@param opts CondOpts + return function(opts) + log.debug('is_inside_quote') + if utils.is_in_quotes(opts.text, opts.col - 1) then + return true + end + end +end + cond.not_add_quote_inside_quote = function() + ---@param opts CondOpts return function(opts) log.debug('not_add_quote_inside_quote') - if - utils.is_quote(opts.char) - and utils.is_in_quote(opts.text, opts.col - 1, opts.char) + if utils.is_quote(opts.char) + and utils.is_in_quotes(opts.text, opts.col - 1) then return false end @@ -165,22 +253,23 @@ cond.not_add_quote_inside_quote = function() end cond.move_right = function() + ---@param opts CondOpts return function(opts) log.debug('move_right') if opts.next_char == opts.char then if utils.is_close_bracket(opts.char) then - return true + return end -- move right when have quote on end line or in quote -- situtaion |" => "| if utils.is_quote(opts.char) then if opts.col == string.len(opts.line) then - return true + return end -- ("|") => (""|) -- "" |" " => "" "| " - if utils.is_in_quote(opts.line, opts.col -1, opts.char) then - return true + if utils.is_in_quotes(opts.line, opts.col - 1, opts.char) then + return end end end @@ -189,14 +278,73 @@ cond.move_right = function() end cond.is_end_line = function() + ---@param opts CondOpts return function(opts) log.debug('is_end_line') local end_text = opts.line:sub(opts.col + 1) -- end text is blank - if end_text ~= "" and end_text:match("^%s+$") == nil then + if end_text ~= '' and end_text:match('^%s+$') == nil then return false end end end +--- Check the next char is quote and cursor is inside quote +cond.is_bracket_in_quote = function() + ---@param opts CondOpts + return function(opts) + log.debug("is_bracket_in_quote") + if utils.is_bracket(opts.char) + and utils.is_quote(opts.next_char) + and utils.is_in_quotes(opts.line, opts.col - 1, opts.next_char) + then + return true + end + end +end + +cond.not_filetypes = function(filetypes) + return function() + log.debug('not_filetypes') + for _, filetype in pairs(filetypes) do + if vim.bo.filetype == filetype then + return false + end + end + end +end + +--- Check the character before the cursor is not equal +---@param char string character to compare +---@param index number the position of character before current curosr +cond.not_before_char = function(char, index) + index = index or 1 + ---@param opts CondOpts + return function(opts) + log.debug('not_before_char') + local match_char = #opts.line > index + and opts.line:sub(#opts.line - index, #opts.line - index) or '' + if match_char == char and match_char ~= "" then + return false + end + end +end + +---@deprecated +cond.not_after_regex_check = cond.not_after_regex +---@deprecated +cond.after_regex_check = cond.after_regex +---@deprecated +cond.before_regex_check = cond.before_regex +---@deprecated +cond.not_before_regex_check = cond.not_before_regex +---@deprecated +cond.after_text_check = cond.after_text +---@deprecated +cond.not_after_text_check = cond.not_after_text +---@deprecated +cond.before_text_check = cond.before_text +---@deprecated +cond.not_before_text_check = cond.not_before_text + return cond diff --git a/lua/nvim-autopairs/fastwrap.lua b/lua/nvim-autopairs/fastwrap.lua index 3e354240..2cbd97ea 100644 --- a/lua/nvim-autopairs/fastwrap.lua +++ b/lua/nvim-autopairs/fastwrap.lua @@ -6,24 +6,27 @@ local M = {} local default_config = { map = '', chars = { '{', '[', '(', '"', "'" }, - pattern = string.gsub([[ [%'%"%)%>%]%)%}%,] ]], '%s+', ''), + pattern = [=[[%'%"%>%]%)%}%,%`]]=], end_key = '$', + avoid_move_to_end = true, -- choose your move behaviour for non-alphabetical end_keys' + before_key = 'h', + after_key = 'l', + cursor_pos_before = true, keys = 'qwertyuiopzxcvbnmasdfghjkl', - check_comma = true, highlight = 'Search', + highlight_grey = 'Comment', + manual_position = true, + use_virt_lines = true } -local state = npairs.state - M.ns_fast_wrap = vim.api.nvim_create_namespace('autopairs_fastwrap') local config = {} M.setup = function(cfg) if config.chars == nil then - config = vim.tbl_extend('force', default_config, cfg or {}) + config = vim.tbl_extend('force', default_config, cfg or {}) or {} npairs.config.fast_wrap = config - npairs.config.ignored_next_char = string.gsub([[ [%w%%%'%(%{%[%"%.] ]], '%s+', '') end end @@ -32,7 +35,7 @@ function M.getchar_handler() if not ok then return nil end - if type(key) == 'number' then + if key ~= 27 and type(key) == 'number' then local key_str = vim.fn.nr2char(key) return key_str end @@ -46,7 +49,8 @@ M.show = function(line) local prev_char = utils.text_cusor_line(line, col, 1, 1, false) local end_pair = '' if utils.is_in_table(config.chars, prev_char) then - for _, rule in pairs(state.rules) do + local rules = npairs.get_buf_rules() + for _, rule in pairs(rules) do if rule.start_pair == prev_char then end_pair = rule.end_pair end @@ -54,36 +58,96 @@ M.show = function(line) if end_pair == '' then return end - local list_pos = {} + local list_pos = {} --holds target locations local index = 1 local str_length = #line - local is_have_end = false + local offset = -1 for i = col + 2, #line, 1 do local char = line:sub(i, i) - if string.match(char, config.pattern) then + local char2 = line:sub(i - 1, i) + if string.match(char, config.pattern) + or (char == ' ' and string.match(char2, '%w')) + then local key = config.keys:sub(index, index) index = index + 1 - if str_length == i then + if not config.manual_position and ( + utils.is_quote(char) + or ( + utils.is_close_bracket(char) + and utils.is_in_quotes(line, col, prev_char) + ) + ) + then + offset = 0 + end + + if config.manual_position and i == str_length then key = config.end_key - is_have_end = true end - table.insert(list_pos, { col = i, key = key, char = char }) + + table.insert( + list_pos, + { col = i + offset, key = key, char = char, pos = i } + ) end end + log.debug(list_pos) - if not is_have_end then - table.insert(list_pos, { col = str_length + 1, key = config.end_key }) + local end_col, end_pos + if config.manual_position then + end_col = str_length + offset + end_pos = str_length + else + end_col = str_length + 1 + end_pos = str_length + 1 + end + -- add end_key to list extmark + if #list_pos == 0 or list_pos[#list_pos].key ~= config.end_key then + table.insert( + list_pos, + { col = end_col, key = config.end_key, pos = end_pos, char = config.end_key } + ) end - M.highlight_wrap(list_pos, row) + -- Create a whitespace string for the current line which replaces every non whitespace + -- character with a space and preserves tabs, so we can use it for highlighting with + -- virtual lines so that highlighting lines up correctly. + -- The string is limited to the last position in list_pos + local whitespace_line = line:sub(1, list_pos[#list_pos].end_pos):gsub("[^ \t]", " ") + + M.highlight_wrap(list_pos, row, col, #line, whitespace_line) vim.defer_fn(function() - local char = M.getchar_handler() + -- get the first char + local char = #list_pos == 1 and config.end_key or M.getchar_handler() + vim.api.nvim_buf_clear_namespace(0, M.ns_fast_wrap, row, row + 1) + for _, pos in pairs(list_pos) do + -- handle end_key specially + if char == config.end_key and char == pos.key then + vim.print("Run to end!") + -- M.highlight_wrap({pos = pos.pos, key = config.end_key}, row, col, #line, whitespace_line) + local move_end_key = (not config.avoid_move_to_end and char == string.upper(config.end_key)) + M.move_bracket(line, pos.col+1, end_pair, move_end_key) + break + end + local hl_mark = { + { pos = pos.pos - 1, key = config.before_key }, + { pos = pos.pos + 1, key = config.after_key }, + } + if config.manual_position and (char == pos.key or char == string.upper(pos.key)) then + M.highlight_wrap(hl_mark, row, col, #line, whitespace_line) + M.choose_pos(row, line, pos, end_pair) + break + end if char == pos.key then - M.move_bracket(line, pos.col, end_pair, pos.char) + M.move_bracket(line, pos.col, end_pair, false) + break + end + if char == string.upper(pos.key) then + M.move_bracket(line, pos.col, end_pair, true) + break end end - vim.api.nvim_buf_clear_namespace(0, M.ns_fast_wrap, row - 1, row + 1) vim.cmd('startinsert') end, 10) return @@ -91,32 +155,82 @@ M.show = function(line) vim.cmd('startinsert') end -M.move_bracket = function(line, target_pos, end_pair, char_map) +M.choose_pos = function(row, line, pos, end_pair) + vim.defer_fn(function() + -- select a second key + local char = + pos.char == nil and config.before_key + or pos.char == config.end_key and config.after_key + or M.getchar_handler() + vim.api.nvim_buf_clear_namespace(0, M.ns_fast_wrap, row, row + 1) + if not char then return end + local change_pos = false + local col = pos.col + if char == string.upper(config.before_key) or char == string.upper(config.after_key) then + change_pos = true + end + if char == config.after_key or char == string.upper(config.after_key) then + col = pos.col + 1 + end + M.move_bracket(line, col, end_pair, change_pos) + vim.cmd('startinsert') + end, 10) +end + +M.move_bracket = function(line, target_pos, end_pair, change_pos) + log.debug(target_pos) line = line or utils.text_get_current_line(0) - local _, col = utils.get_cursor() + local row, col = utils.get_cursor() local _, next_char = utils.text_cusor_line(line, col, 1, 1, false) -- remove an autopairs if that exist - -- ((fsadfsa)) dsafdsa if next_char == end_pair then line = line:sub(1, col) .. line:sub(col + 2, #line) target_pos = target_pos - 1 end - if config.check_comma and char_map == ',' then - target_pos = target_pos - 1 - end line = line:sub(1, target_pos) .. end_pair .. line:sub(target_pos + 1, #line) - vim.fn.setline('.', line) + vim.api.nvim_set_current_line(line) + if change_pos then + vim.api.nvim_win_set_cursor(0, { row + 1, target_pos + (config.cursor_pos_before and 0 or 1) }) + end end -M.highlight_wrap = function(tbl_pos, row) +M.highlight_wrap = function(tbl_pos, row, col, end_col, whitespace_line) local bufnr = vim.api.nvim_win_get_buf(0) - for _, pos in ipairs(tbl_pos) do - vim.api.nvim_buf_set_extmark(bufnr, M.ns_fast_wrap, row, pos.col - 1, { - virt_text = { { pos.key, config.highlight } }, - virt_text_pos = 'overlay', + if config.use_virt_lines then + local virt_lines = {} + local start = 0 + local left_col = vim.fn.winsaveview().leftcol + if left_col > 0 then + vim.fn.winrestview({ leftcol = 0 }) + end + for _, pos in ipairs(tbl_pos) do + virt_lines[#virt_lines + 1] = { whitespace_line:sub(start + 1, pos.pos - 1), 'Normal' } + virt_lines[#virt_lines + 1] = { pos.key, config.highlight } + start = pos.pos + end + vim.api.nvim_buf_set_extmark(bufnr, M.ns_fast_wrap, row, 0, { + virt_lines = { virt_lines }, hl_mode = 'blend', }) + else + if config.highlight_grey then + (vim.hl or vim.highlight).range( + bufnr, + M.ns_fast_wrap, + config.highlight_grey, + { row, col }, + { row, end_col }, + {} + ) + end + for _, pos in ipairs(tbl_pos) do + vim.api.nvim_buf_set_extmark(bufnr, M.ns_fast_wrap, row, pos.pos - 1, { + virt_text = { { pos.key, config.highlight } }, + virt_text_pos = 'overlay', + hl_mode = 'blend', + }) + end end end diff --git a/lua/nvim-autopairs/internal.lua b/lua/nvim-autopairs/internal.lua deleted file mode 100644 index 9948ab12..00000000 --- a/lua/nvim-autopairs/internal.lua +++ /dev/null @@ -1,15 +0,0 @@ -local log = require('nvim-autopairs._log') - -local M = {} - -M.attach = function (bufnr) - log.debug('treesitter.attach') - MPairs.state.buf_ts[bufnr] = true -end - -M.detach = function (bufnr ) - MPairs.state.buf_ts[bufnr] =nil -end - --- _G.AUTO = M -return M diff --git a/lua/nvim-autopairs/rule.lua b/lua/nvim-autopairs/rule.lua index 1355d5c5..0652ca42 100644 --- a/lua/nvim-autopairs/rule.lua +++ b/lua/nvim-autopairs/rule.lua @@ -1,50 +1,104 @@ -local log = require('nvim-autopairs._log') -local Rule = {} - local Cond = require('nvim-autopairs.conds') +--- @class Rule +--- @field start_pair string +--- @field end_pair string +--- @field end_pair_func function dynamic change end_pair +--- @field map_cr_func function dynamic change mapping_cr +--- @field end_pair_length number change end_pair length for key map like +--- @field key_map string|nil equal nil mean it will skip on autopairs map +--- @field filetypes table|nil +--- @field not_filetypes table|nil +--- @field is_regex boolean use regex to compare +--- @field is_multibyte boolean +--- @field is_endwise boolean only use on end_wise +--- @field is_undo boolean add break undo sequence +local Rule = setmetatable({}, { + __call = function(self, ...) + return self.new(...) + end, +}) + +---@return Rule function Rule.new(...) - local params = {...} + local params = { ... } local opt = {} if type(params[1]) == 'table' then opt = params[1] else opt.start_pair = params[1] opt.end_pair = params[2] - if type(params[3]) == "string" then - opt.filetypes = {params[3]} + if type(params[3]) == 'string' then + opt.filetypes = { params[3] } else opt.filetypes = params[3] end end opt = vim.tbl_extend('force', { - -- set to nil mean it will skip on autopairs_map - key_map = "", - start_pair = nil, - end_pair = nil, - -- function to dynamic update end pair - end_pair_func = false, - filetypes = nil, - move_cond = nil, - del_cond = {}, - cr_cond = {}, - pair_cond = {}, - -- only use on end_wise - is_endwise = false, - -- use regex to compare - is_regex = false - },opt) - return setmetatable(opt, {__index = Rule}) -end - -function Rule:use_regex(value,key_map) + key_map = "", + start_pair = nil, + end_pair = nil, + end_pair_func = false, + filetypes = nil, + not_filetypes = nil, + move_cond = nil, + del_cond = {}, + cr_cond = {}, + pair_cond = {}, + is_endwise = false, + is_regex = false, + is_multibyte = false, + end_pair_length = nil, + }, opt) or {} + + ---@param rule Rule + local function constructor(rule) + -- check multibyte + if #rule.start_pair ~= vim.api.nvim_strwidth(rule.start_pair) then + rule:use_multibyte() + end + -- check filetypes and not_filetypes + -- if have something like "-vim" it will add to not_filetypes + if rule.filetypes then + local ft, not_ft = {}, {} + for _, value in pairs(rule.filetypes) do + if value:sub(1, 1) == '-' then + table.insert(not_ft, value:sub(2, #value)) + else + table.insert(ft, value) + end + end + rule.filetypes = #ft > 0 and ft or nil + rule.not_filetypes = #not_ft > 0 and not_ft or nil + end + return rule + end + + local r = setmetatable(opt, { __index = Rule }) + return constructor(r) +end + +function Rule:use_regex(value, key_map) self.is_regex = value - self.key_map = key_map or "" + self.key_map = key_map or '' return self end function Rule:use_key(key_map) - self.key_map = key_map or "" + self.key_map = key_map or '' + return self +end + +function Rule:use_undo(value) + self.is_undo = value + return self +end + +function Rule:use_multibyte() + self.is_multibyte = true + self.end_pair_length = vim.fn.strdisplaywidth(self.end_pair) + self.key_map = string.match(self.start_pair, "[^\128-\191][\128-\191]*$") + self.key_end = string.match(self.end_pair, "[%z\1-\127\194-\244][\128-\191]*") return self end @@ -52,14 +106,35 @@ function Rule:get_end_pair(opts) if self.end_pair_func then return self.end_pair_func(opts) end - return self.end_pair + return self.end_pair +end + +function Rule:get_map_cr(opts) + if self.map_cr_func then + return self.map_cr_func(opts) + end + return 'unormal! ====' +end +function Rule:replace_map_cr(value) + self.map_cr_func = value + return self +end + +function Rule:get_end_pair_length(opts) + if self.end_pair_length then + return self.end_pair_length + end + if type(opts) == 'string' then + return #opts + end + return #self.get_end_pair(opts) end -function Rule:replace_endpair(value,check_pair) +function Rule:replace_endpair(value, check_pair) self.end_pair_func = value if check_pair ~= nil then if check_pair == true then - self:with_pair(Cond.after_text_check(self.end_pair)) + self:with_pair(Cond.after_text(self.end_pair)) else self:with_pair(check_pair) end @@ -67,28 +142,40 @@ function Rule:replace_endpair(value,check_pair) return self end +function Rule:set_end_pair_length(length) + self.end_pair_length = length + return self +end + function Rule:with_move(cond) - if self.move_cond == nil then self.move_cond = {}end + if self.move_cond == nil then self.move_cond = {} end table.insert(self.move_cond, cond) return self end function Rule:with_del(cond) - if self.del_cond == nil then self.del_cond = {}end + if self.del_cond == nil then self.del_cond = {} end table.insert(self.del_cond, cond) return self end - function Rule:with_cr(cond) - if self.cr_cond == nil then self.cr_cond = {}end + if self.cr_cond == nil then self.cr_cond = {} end table.insert(self.cr_cond, cond) return self end -function Rule:with_pair(cond) - if self.pair_cond == nil then self.pair_cond = {}end - table.insert(self.pair_cond, cond) +---add condition to rule +---@param cond any +---@param pos number|nil = 1. It have higher priority to another condition +---@return Rule +function Rule:with_pair(cond, pos) + if self.pair_cond == nil then self.pair_cond = {} end + if pos then + table.insert(self.pair_cond, pos, cond) + else + table.insert(self.pair_cond, cond) + end return self end @@ -123,7 +210,6 @@ end function Rule:can_pair(opt) return can_do(self.pair_cond, opt) - end function Rule:can_move(opt) @@ -134,10 +220,8 @@ function Rule:can_del(opt) return can_do(self.del_cond, opt) end - function Rule:can_cr(opt) return can_do(self.cr_cond, opt) end - -return Rule.new +return Rule diff --git a/lua/nvim-autopairs/rules/basic.lua b/lua/nvim-autopairs/rules/basic.lua index 86b55aad..1f0efd6d 100644 --- a/lua/nvim-autopairs/rules/basic.lua +++ b/lua/nvim-autopairs/rules/basic.lua @@ -1,43 +1,91 @@ -local Rule = require('nvim-autopairs.rule') -local cond = require('nvim-autopairs.conds') +local Rule = require("nvim-autopairs.rule") +local cond = require("nvim-autopairs.conds") +local utils = require('nvim-autopairs.utils') -local function setup(opt) - local basic = function(...) +local function quote_creator(opt) + local quote = function(...) local move_func = opt.enable_moveright and cond.move_right or cond.none + ---@type Rule + local rule = Rule(...) + :with_move(move_func()) + :with_pair(cond.not_add_quote_inside_quote()) - return Rule(...) - :with_move(move_func()) - :with_pair(cond.not_after_regex_check(opt.ignored_next_char)) - :with_pair(cond.not_add_quote_inside_quote()) + if #opt.ignored_next_char > 1 then + rule:with_pair(cond.not_after_regex(opt.ignored_next_char)) + end + rule:use_undo(opt.break_undo) + return rule end + return quote +end +local function bracket_creator(opt) + local quote = quote_creator(opt) local bracket = function(...) + local rule = quote(...) if opt.enable_check_bracket_line == true then - return basic(...) - :with_pair(cond.check_is_bracket_line()) + rule:with_pair(cond.is_bracket_line()) + :with_move(cond.is_bracket_line_move()) + end + if opt.enable_bracket_in_quote then + -- still add bracket if text is quote "|" and next_char have " + rule:with_pair(cond.is_bracket_in_quote(), 1) end - return basic(...) + return rule end + return bracket +end +local function setup(opt) + local quote = quote_creator(opt) + local bracket = bracket_creator(opt) local rules = { - Rule("", 'html'):with_cr(cond.none()), - Rule("```", "```", { 'markdown', 'vimwiki' }), - Rule("```.*$", "```", { 'markdown', 'vimwiki' }) - :only_cr() - :use_regex(true) - , - Rule('"""', '"""', 'python'), - basic("'", "'") - :with_pair(cond.not_before_regex_check("%w")) , - basic("`", "`"), - basic('"', '"'), + Rule("", { "html", "markdown" }):with_cr(cond.none()), + Rule("```", "```", { "markdown", "vimwiki", "rmarkdown", "rmd", "pandoc", "quarto" }) + :with_pair(cond.not_before_char('`', 3)), + Rule("```.*$", "```", { "markdown", "vimwiki", "rmarkdown", "rmd", "pandoc", "quarto" }):only_cr():use_regex(true), + Rule('"""', '"""', { "python", "elixir", "julia", "kotlin", "scala","sbt" }):with_pair(cond.not_before_char('"', 3)), + Rule("'''", "'''", { "python" }):with_pair(cond.not_before_char("'", 3)), + quote("'", "'", { "-rust", "-nix" }) + :with_pair(function(opts) + -- python literals string + local str = utils.text_sub_char(opts.line, opts.col - 1, 1) + if vim.bo.filetype == 'python' and str:match("[frbuFRBU]") then + return true + end + end) + :with_pair(cond.not_before_regex("%w")), + quote("'", "'", "rust"):with_pair(cond.not_before_regex("[%w<&]")):with_pair(cond.not_after_text(">")), + Rule("''", "''", 'nix'):with_move(cond.after_text("'")), + quote("`", "`"), + quote('"', '"', "-vim"), + quote('"', '"', "vim"):with_pair(cond.not_before_regex("^%s*$", -1)), bracket("(", ")"), bracket("[", "]"), bracket("{", "}"), - Rule(">", "<", - { 'html', 'typescript', 'typescriptreact', 'svelte', 'vue', 'xml'}) - :only_cr() + Rule( + ">[%w%s]*$", + "^%s*$', 'end', 'elixir', nil), +} + +return rules diff --git a/lua/nvim-autopairs/rules/endwise-lua.lua b/lua/nvim-autopairs/rules/endwise-lua.lua index 21fbdf9d..9105e0cf 100644 --- a/lua/nvim-autopairs/rules/endwise-lua.lua +++ b/lua/nvim-autopairs/rules/endwise-lua.lua @@ -2,7 +2,7 @@ local endwise = require('nvim-autopairs.ts-rule').endwise local rules = { endwise('then$', 'end', 'lua', 'if_statement'), - endwise('function.*%(.*%)$', 'end', 'lua', {'function_definition', 'local_function', 'function'}), + endwise('function.*%(.*%)$', 'end', 'lua', {'function_declaration', 'local_function', 'function'}), } diff --git a/lua/nvim-autopairs/ts-conds.lua b/lua/nvim-autopairs/ts-conds.lua index 3b494be3..8ab2383d 100644 --- a/lua/nvim-autopairs/ts-conds.lua +++ b/lua/nvim-autopairs/ts-conds.lua @@ -1,8 +1,6 @@ -local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') - local log = require('nvim-autopairs._log') -local parsers = require'nvim-treesitter.parsers' local utils = require('nvim-autopairs.utils') +local ts_get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text local conds = {} @@ -16,10 +14,10 @@ conds.is_endwise_node = function(nodes) if nodes == nil then return true end if #nodes == 0 then return true end - parsers.get_parser():parse() - local target = ts_utils.get_node_at_cursor() + vim.treesitter.get_parser():parse() + local target = vim.treesitter.get_node({ ignore_injections = false }) if target ~= nil and utils.is_in_table(nodes, target:type()) then - local text = ts_utils.get_node_text(target) or {""} + local text = ts_get_node_text(target) or {""} local last = text[#text]:match(opts.rule.end_pair) -- check last character is match with end_pair if last == nil then @@ -31,9 +29,9 @@ conds.is_endwise_node = function(nodes) local begin_target,_, end_target = target:range() local begin_parent,_, end_parent = target:parent():range() -- log.debug(target:range()) - -- log.debug(ts_utils.get_node_text(target)) + -- log.debug(ts_get_node_text(target)) -- log.debug(target:parent():range()) - -- log.debug(ts_utils.get_node_text(target:parent())) + -- log.debug(ts_get_node_text(target:parent())) if ( begin_target ~= begin_parent @@ -51,16 +49,68 @@ conds.is_endwise_node = function(nodes) end end +conds.is_in_range = function(callback, position) + assert( + type(callback) == 'function' and type(position) == 'function', + 'callback and position should be a function' + ) + return function(opts) + log.debug('is_in_range') + -- `parser` will be a table (on success) or a string (error message) + local _, parser = pcall(vim.treesitter.get_parser) + if not type(parser) == 'string' then + return + end + local cursor = position() + assert( + type(cursor) == 'table' and #cursor == 2, + 'position should be return a table like {line, col}' + ) + local line = cursor[1] + local col = cursor[2] + + local bufnr = 0 + local root_lang_tree = vim.treesitter.get_parser(bufnr) + local lang_tree = root_lang_tree:language_for_range({ line, col, line, col }) + + local result + + for _, tree in ipairs(lang_tree:trees()) do + local root = tree:root() + if root and vim.treesitter.is_in_node_range(root, line, col) then + local node = root:named_descendant_for_range(line, col, line, col) + local anonymous_node = root:descendant_for_range( + line, + col, + line, + col + ) + + result = { + node = node, + lang = lang_tree:lang(), + type = node:type(), + cursor = vim.api.nvim_win_get_cursor(0), + line = vim.api.nvim_buf_get_lines(bufnr, line, line + 1, true)[1], + range = { node:range() }, + anonymous = anonymous_node:type(), + } + end + end + + return callback(result) + end +end + conds.is_ts_node = function(nodes) if type(nodes) == 'string' then nodes = {nodes} end assert(nodes ~= nil, "ts nodes should be string or table") return function (opts) log.debug('is_ts_node') - if not opts.ts_node then return end if #nodes == 0 then return end - parsers.get_parser():parse() - local target = ts_utils.get_node_at_cursor() + vim.treesitter.get_parser():parse() + local target = vim.treesitter.get_node({ ignore_injections = false }) if target ~= nil and utils.is_in_table(nodes, target:type()) then return true end @@ -73,11 +123,10 @@ conds.is_not_ts_node = function(nodes) assert(nodes ~= nil, "ts nodes should be string or table") return function (opts) log.debug('is_not_ts_node') - if not opts.ts_node then return end if #nodes == 0 then return end - parsers.get_parser():parse() - local target = ts_utils.get_node_at_cursor() + vim.treesitter.get_parser():parse() + local target = vim.treesitter.get_node({ ignore_injections = false }) if target ~= nil and utils.is_in_table(nodes, target:type()) then return false end @@ -89,12 +138,22 @@ conds.is_not_ts_node_comment = function() log.debug('not_in_ts_node_comment') if not opts.ts_node then return end - parsers.get_parser():parse() - local target = ts_utils.get_node_at_cursor() + vim.treesitter.get_parser():parse() + local target = vim.treesitter.get_node({ ignore_injections = false }) if target ~= nil and utils.is_in_table(opts.ts_node, target:type()) then return false end end end +conds.is_not_in_context = function() + return function(opts) + local context = require("nvim-autopairs.ts-utils") + .get_language_tree_at_position({ utils.get_cursor() }) + if not vim.tbl_contains(opts.rule.filetypes, context:lang()) then + return false + end + end +end + return conds diff --git a/lua/nvim-autopairs/ts-rule.lua b/lua/nvim-autopairs/ts-rule.lua index b01824ae..3e2ebccc 100644 --- a/lua/nvim-autopairs/ts-rule.lua +++ b/lua/nvim-autopairs/ts-rule.lua @@ -9,7 +9,8 @@ return { :use_regex(true) :end_wise(cond.is_end_line()) if params[4] then - rule:with_cr(ts_conds.is_endwise_node(params[4])) + -- rule:with_cr(ts_conds.is_endwise_node(params[4])) + rule:with_cr(ts_conds.is_ts_node(params[4])) end return rule end diff --git a/lua/nvim-autopairs/ts-utils.lua b/lua/nvim-autopairs/ts-utils.lua index 4c5ee754..daa92623 100644 --- a/lua/nvim-autopairs/ts-utils.lua +++ b/lua/nvim-autopairs/ts-utils.lua @@ -1,11 +1,22 @@ - -local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') +local ts_get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text local M = {} +--- Returns the language tree at the given position. +---@return LanguageTree +function M.get_language_tree_at_position(position) + local language_tree = vim.treesitter.get_parser() + language_tree:for_each_tree(function(_, tree) + if tree:contains(vim.tbl_flatten({ position, position })) then + language_tree = tree + end + end) + return language_tree +end + function M.get_tag_name(node) local tag_name = nil if node ~=nil then - tag_name = ts_utils.get_node_text(node) + tag_name = ts_get_node_text(node) end return tag_name end diff --git a/lua/nvim-autopairs/utils.lua b/lua/nvim-autopairs/utils.lua index c2e94928..31d9f610 100644 --- a/lua/nvim-autopairs/utils.lua +++ b/lua/nvim-autopairs/utils.lua @@ -1,36 +1,40 @@ -local M={} +local M = {} local api = vim.api local log = require('nvim-autopairs._log') M.key = { del = "", bs = "", + c_h = "", left = "", right = "", join_left = "U", - join_right = "U" + join_right = "U", + undo_sequence = "u", + noundo_sequence = "U", + abbr = "" } M.set_vchar = function(text) text = text:gsub('"', '\\"') - vim.cmd(string.format([[let v:char = "%s"]],text)) + vim.v.char = text end -M.is_quote = function (char) +M.is_quote = function(char) return char == "'" or char == '"' or char == '`' end -M.is_bracket = function (char) - return char == "(" or char == '[' or char == '{' +M.is_bracket = function(char) + return char == "(" or char == '[' or char == '{' or char == '<' end -M.is_close_bracket = function (char) - return char == ")" or char == ']' or char == '}' +M.is_close_bracket = function(char) + return char == ")" or char == ']' or char == '}' or char == '>' end -M.is_equal = function (value,text, is_regex) +M.compare = function(value, text, is_regex) if is_regex and string.match(text, value) then return true elseif text == value then @@ -39,20 +43,40 @@ M.is_equal = function (value,text, is_regex) return false end -M.is_in_quote = function(line, pos, quote) +---check cursor is inside a quote +---@param line string +---@param pos number position in line +---@param quote_type nil|string specify a quote +---@return boolean +M.is_in_quotes = function(line, pos, quote_type) local cIndex = 0 local result = false + local last_char = quote_type or '' - while cIndex < string.len(line) and cIndex < pos do + while cIndex < string.len(line) and cIndex < pos do cIndex = cIndex + 1 local char = line:sub(cIndex, cIndex) + local prev_char = line:sub(cIndex - 1, cIndex - 1) if - result == true and - char == quote and - line:sub(cIndex -1, cIndex -1) ~= "\\" + result == true + and char == last_char + and prev_char ~= "\\" then result = false - elseif result == false and char == quote then + last_char = quote_type or '' + elseif + result == false + and M.is_quote(char) + and (not quote_type or char == quote_type) + --a single quote with a word before is not count unless it is a + -- prefixed string in python (e.g. f'string {with_brackets}') + and not ( + char == "'" + and prev_char:match('%w') + and (vim.bo.filetype ~= 'python' or prev_char:match('[^frbuFRBU]')) + ) + then + last_char = quote_type or char result = true end end @@ -60,19 +84,19 @@ M.is_in_quote = function(line, pos, quote) end M.is_attached = function(bufnr) - local _, check = pcall(api.nvim_buf_get_var, bufnr, "nvim-autopairs") + local _, check = pcall(api.nvim_buf_get_var, bufnr or 0, "nvim-autopairs") return check == 1 end -M.set_attach = function(bufnr,value) +M.set_attach = function(bufnr, value) api.nvim_buf_set_var(bufnr or 0, "nvim-autopairs", value) end M.is_in_table = function(tbl, val) if tbl == nil then return false end for _, value in pairs(tbl) do - if val== value then return true end + if val == value then return true end end return false end @@ -82,13 +106,13 @@ M.check_filetype = function(tbl, filetype) return M.is_in_table(tbl, filetype) end -M.check_disable_ft = function(tbl, filetype) +M.check_not_filetype = function(tbl, filetype) if tbl == nil then return true end return not M.is_in_table(tbl, filetype) end M.is_in_range = function(row, col, range) - local start_row, start_col, end_row, end_col = unpack(range) + local start_row, start_col, end_row, end_col = unpack(range) return (row > start_row or (start_row == row and col >= start_col)) and (row < end_row or (row == end_row and col <= end_col)) @@ -99,17 +123,18 @@ M.get_cursor = function(bufnr) return row - 1, col end M.text_get_line = function(bufnr, lnum) - return api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, false)[1] + return api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, false)[1] or '' end + M.text_get_current_line = function(bufnr) - local row = unpack(api.nvim_win_get_cursor(0)) - return M.text_get_line(bufnr, row -1) + local row = unpack(api.nvim_win_get_cursor(0)) or 1 + return M.text_get_line(bufnr, row - 1) end M.repeat_key = function(key, num) - local text='' + local text = '' for _ = 1, num, 1 do - text=text..key + text = text .. key end return text end @@ -124,7 +149,7 @@ M.text_cusor_line = function(line, col, prev_count, next_count, is_regex) prev_count = col next_count = #line - col end - local prev = M.text_sub_char(line, col, - prev_count) + local prev = M.text_sub_char(line, col, -prev_count) local next = M.text_sub_char(line, col + 1, next_count) return prev, next end @@ -134,14 +159,14 @@ M.text_sub_char = function(line, start, num) if num < 0 then start = start + num + 1 else - finish = start + num -1 + finish = start + num - 1 end return string.sub(line, start, finish) end -- P(M.text_sub_char("aa'' aaa", 3, -1)) M.insert_char = function(text) - api.nvim_put({text}, "c", false, true) + api.nvim_put({ text }, "c", false, true) end M.feed = function(text, num) @@ -153,18 +178,21 @@ M.feed = function(text, num) end log.debug("result" .. result) api.nvim_feedkeys(api.nvim_replace_termcodes( - result, true, false, true), - "n", true) + result, true, false, true), + "n", true) end M.esc = function(cmd) return vim.api.nvim_replace_termcodes(cmd, true, false, true) end +M.is_block_wise_mode = function() + return vim.fn.visualmode() == '' +end --- get prev_char with out key_map M.get_prev_char = function(opt) - return opt.line:sub(opt.col -1, opt.col + #opt.rule.start_pair -2) + return opt.line:sub(opt.col - 1, opt.col + #opt.rule.start_pair - 2) end return M diff --git a/plugin/nvim-autopairs.vim b/plugin/nvim-autopairs.vim deleted file mode 100644 index ce194ec4..00000000 --- a/plugin/nvim-autopairs.vim +++ /dev/null @@ -1 +0,0 @@ -lua require "nvim-autopairs".init() diff --git a/style.toml b/style.toml new file mode 100644 index 00000000..be4a33a2 --- /dev/null +++ b/style.toml @@ -0,0 +1,4 @@ +column_width = 85 +indent_type = "Spaces" +quote_style = "AutoPreferSingle" +indent_width = 4 diff --git a/tests/afterquote_spec.lua b/tests/afterquote_spec.lua index cf4eaea4..05909c17 100644 --- a/tests/afterquote_spec.lua +++ b/tests/afterquote_spec.lua @@ -67,46 +67,33 @@ local data = { name = 'test add close quote on match', filepath = './tests/endwise/init.lua', filetype = 'lua', - linenr = '5', + linenr = 5, key = [[(]], before = [[const abc=|"visu\"dsa" ]], after = [[const abc=(|"visu\"dsa") ]], }, - - { - name = 'test add close quote inside string', - filepath = './tests/endwise/init.lua', - filetype = 'lua', - linenr = '5', - key = [[(]], - before = [[const abc="visu|"dsa"" ]], - after = [[const abc="visu(|"dsa"" ]], - }, { name = 'not add bracket with quote have comma', filepath = './tests/endwise/init.lua', filetype = 'lua', - linenr = '5', + linenr = 5, key = [[(]], before = [[|"data", abcdef]], - after = [[(|"data", abcdef]], + after = [[(|"data", abcdef]], }, { name = 'not add bracket with quote have comma', filepath = './tests/endwise/init.lua', filetype = 'lua', - linenr = '5', + linenr = 5, key = [[(]], before = [[|"data", "abcdef"]], - after = [[(|"data", "abcdef"]], + after = { [[(|"data", "abcdef"]] }, }, } local run_data = _G.Test_filter(data) -local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') -_G.TU = ts_utils - describe('[afterquote tag]', function() _G.Test_withfile(run_data, {}) end) diff --git a/tests/endwise/main.rs b/tests/endwise/main.rs new file mode 100644 index 00000000..9d301b42 --- /dev/null +++ b/tests/endwise/main.rs @@ -0,0 +1,15 @@ +fn main() { + + + + + + + + + + + + + +} diff --git a/tests/endwise/sample.md b/tests/endwise/sample.md new file mode 100644 index 00000000..490b6d83 --- /dev/null +++ b/tests/endwise/sample.md @@ -0,0 +1,9 @@ +# Example Markdown File + +```javascript +let; +let; +let; +let; +let; +``` diff --git a/tests/endwise_spec.lua b/tests/endwise_spec.lua index cc1773f8..54f4987c 100644 --- a/tests/endwise_spec.lua +++ b/tests/endwise_spec.lua @@ -1,69 +1,83 @@ local npairs = require('nvim-autopairs') -local ts = require 'nvim-treesitter.configs' +local ts = require('nvim-treesitter.configs') local log = require('nvim-autopairs._log') -ts.setup { - ensure_installed = {'lua'}, - highlight = {enable = true}, -} -_G.npairs = npairs; -vim.api.nvim_set_keymap('i' , '','v:lua.npairs.check_break_line_char()', {expr = true , noremap = true}) - +ts.setup({ + ensure_installed = { 'lua' }, + highlight = { enable = true }, +}) +_G.npairs = npairs +vim.api.nvim_set_keymap( + 'i', + '', + 'v:lua.npairs.autopairs_cr()', + { expr = true, noremap = true } +) local data = { { - name = "lua function add endwise" , + name = 'lua function add endwise', filepath = './tests/endwise/init.lua', - filetype = "lua", - linenr = 5, - key = [[]], - before = [[function a()| ]], - after = [[ end ]] + filetype = 'lua', + linenr = 5, + key = [[]], + before = [[function a()| ]], + after = { + [[function a() ]], + [[| ]], + [[ end ]], + }, }, { - name = "lua function add endwise" , + name = 'lua function add endwise', filepath = './tests/endwise/init.lua', - filetype = "lua", - linenr = 5, - key = [[]], - before = [[function a()|x ab ]], - after = [[]] + filetype = 'lua', + linenr = 5, + key = [[]], + before = [[function a()|x ab ]], + after = { + [[function a() ]], + [[|x ab]], + }, }, { - name = "add if endwise" , + name = 'add if endwise', filepath = './tests/endwise/init.lua', - filetype = "lua", - linenr = 5, - key = [[]], - before = [[if data== 'fdsafdsa' then| ]], - after = [[end ]] + filetype = 'lua', + linenr = 5, + key = [[]], + before = [[if data== 'fdsafdsa' then| ]], + after = { + [[if data== 'fdsafdsa' then ]], + [[|]], + [[end ]], + }, }, { - name = "don't add endwise on match rule" , + name = 'undo on key', filepath = './tests/endwise/init.lua', - filetype = "lua", - linenr = 5, - key = [[]], - before ={ - [[if data == 'xdsad' then| ]], - [[ local abde='das' ]], - [[end]] + filetype = 'lua', + linenr = 5, + key = [[{u]], + before = [[local abc = | ]], + after = { + [[local abc = {|} ]], + [[]], + [[]], }, - after = [[ local abde='das' ]] }, } local run_data = _G.Test_filter(data) -local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') -_G.TU = ts_utils - - describe('[endwise tag]', function() - _G.Test_withfile(run_data,{ - before = function(value) - npairs.clear_rules() - npairs.add_rules(require('nvim-autopairs.rules.endwise-'..value.filetype)) - end + _G.Test_withfile(run_data, { + -- need to understand this ??? new line make change cursor zzz + cursor_add = 1, + before_each = function(value) + npairs.add_rules( + require('nvim-autopairs.rules.endwise-' .. value.filetype) + ) + end, }) end) diff --git a/tests/fastwrap_spec.lua b/tests/fastwrap_spec.lua index aafcb5de..b34ba32f 100644 --- a/tests/fastwrap_spec.lua +++ b/tests/fastwrap_spec.lua @@ -48,9 +48,6 @@ -- local run_data = _G.Test_filter(data) --- local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') --- _G.TU = ts_utils - -- describe('[endwise tag]', function() -- _G.Test_withfile(run_data, {}) -- end) diff --git a/tests/minimal.vim b/tests/minimal.vim index 5d1ac577..eb442876 100644 --- a/tests/minimal.vim +++ b/tests/minimal.vim @@ -24,6 +24,7 @@ set indentexpr= lua << EOF require("plenary/busted") +require("nvim-treesitter").setup() vim.cmd[[luafile ./tests/test_utils.lua]] require("nvim-autopairs").setup() EOF diff --git a/tests/nvim-autopairs_spec.lua b/tests/nvim-autopairs_spec.lua index 3d1e1efe..5f62e35e 100644 --- a/tests/nvim-autopairs_spec.lua +++ b/tests/nvim-autopairs_spec.lua @@ -1,435 +1,851 @@ -local helpers = {} local npairs = require('nvim-autopairs') local Rule = require('nvim-autopairs.rule') local cond = require('nvim-autopairs.conds') -local ts_conds = require('nvim-autopairs.ts-conds') local log = require('nvim-autopairs._log') +_G.log = log local utils = require('nvim-autopairs.utils') _G.npairs = npairs; -local eq=_G.eq - - -npairs.add_rules({ - Rule("u%d%d%d%d$", "number", "lua") - :use_regex(true), - Rule("x%d%d%d%d$", "number", "lua") - :use_regex(true) - :replace_endpair(function(opts) - log.debug(opts.prev_char) - return opts.prev_char:sub(#opts.prev_char - 3,#opts.prev_char) - end), - Rule("b%d%d%d%d%w$", "", "vim") - :use_regex(true,"") - :replace_endpair(function(opts) - return - opts.prev_char:sub(#opts.prev_char - 4,#opts.prev_char) - .."viwUi" - end), - -}) -vim.api.nvim_set_keymap('i' , '','v:lua.npairs.check_break_line_char()', {expr = true , noremap = true}) -function helpers.feed(text, feed_opts) - feed_opts = feed_opts or 'n' - local to_feed = vim.api.nvim_replace_termcodes(text, true, false, true) - vim.api.nvim_feedkeys(to_feed, feed_opts, true) -end - -function helpers.insert(text) - helpers.feed('i' .. text, 'x') -end +-- use only = true to test 1 case +-- stylua: ignore local data = { { - name = "add normal bracket" , + -- only = true, + name = "1 add normal bracket", key = [[{]], before = [[x| ]], after = [[x{|} ]] }, { - name = "add bracket inside bracket" , + name = "2 add bracket inside bracket", key = [[{]], before = [[{|} ]], after = [[{{|}} ]] }, { - name = "test single quote ", + name = "3 test single quote ", filetype = "lua", - key = "'", - before = [[data,|) ]], - after = [[data,'|') ]] + key = "'", + before = [[data,|) ]], + after = [[data,'|') ]] }, { - name = "add normal bracket" , + name = "4 add normal bracket", key = [[(]], before = [[aaaa| x ]], after = [[aaaa(|) x ]] }, { - name = "add normal quote" , + name = "5 add normal quote", key = [["]], before = [[aa| aa]], after = [[aa"|" aa]] }, { - name = "add python quote" , + name = "6 add python quote", filetype = "python", - key = [["]], - before = [[""| ]], - after = [["""|""" ]] + key = [["]], + before = [[""| ]], + after = [["""|""" ]] + }, + { + name = "7 don't repeat python quote", + filetype = "python", + key = [["]], + before = [[a"""|""" ]], + after = [[a""""|"" ]] }, { - name = "add markdown quote" , + name = "8 add markdown quote", filetype = "markdown", - key = [[`]], - before = [[``| ]], - after = [[```|``` ]] + key = [[`]], + before = [[``| ]], + after = [[```|``` ]] }, { - name = "don't add single quote with previous alphabet char" , + name = "9 don't add single quote with previous alphabet char", key = [[']], before = [[aa| aa ]], after = [[aa'| aa ]] }, { - -- only = true, - name = "don't add single quote with alphabet char" , + name = "10 don't add single quote with alphabet char", key = [[']], before = [[a|x ]], after = [[a'|x ]] }, { - name = "don't add single quote on end line", + name = "11 don't add single quote on end line", key = [[']], before = [[c aa|]], after = [[c aa'|]] }, { - name = "don't add quote after alphabet char" , + name = "12 don't add quote after alphabet char", key = [["]], before = [[aa |aa]], after = [[aa "|aa]] }, { - name = "don't add quote inside quote" , + name = "13 don't add quote inside quote", key = [["]], before = [["aa | aa]], after = [["aa "| aa]] }, { - name = "add quote if not inside quote" , + name = "14 add quote if not inside quote", key = [["]], before = [["aa " | aa]], after = [["aa " "|" aa]] }, { - name = "don't add pair after alphabet char" , + name = "15 don't add pair after alphabet char", key = [[(]], before = [[aa |aa]], after = [[aa (|aa]] }, { - name = "don't add pair after dot char" , + name = "16 don't add pair after dot char", key = [[(]], before = [[aa |.aa]], after = [[aa (|.aa]] }, { - name = "don't add bracket have open bracket in same line" , + name = "17 don't add bracket have open bracket in same line", key = [[(]], before = [[( many char |))]], after = [[( many char (|))]] }, { - name = "move right on quote line " , + filetype = 'vim', + name = "18 add bracket inside quote when nextchar is ignore", + key = [[{]], + before = [["|"]], + after = [["{|}"]] + }, + { + filetype = '', + name = "19 add bracket inside quote when next char is ignore", + key = [[{]], + before = [[" |"]], + after = [[" {|}"]] + }, + { + name = "20 move right on quote line ", key = [["]], before = [["|"]], after = [[""|]] }, { - name = "move right end line " , + name = "21 move right end line ", key = [["]], before = [[aaaa|"]], after = [[aaaa"|]] }, { - name = "move right when inside quote" , + name = "22 move right when inside quote", key = [["]], before = [[("abcd|")]], after = [[("abcd"|)]] }, { - name = "move right when inside quote" , + name = "23 move right when inside quote", key = [["]], before = [[foo("|")]], after = [[foo(""|)]] }, { - name = "move right square bracket" , + name = "24 move right square bracket", key = [[)]], before = [[("abcd|) ]], after = [[("abcd)| ]] }, { - name = "move right bracket" , + name = "25 move right bracket", key = [[}]], before = [[("abcd|}} ]], after = [[("abcd}|} ]] }, { - name = "move right when inside grave with special slash" , + -- ref: issue #331 + name = "26 move right, should not move on non-end-pair char: `§|§` with (", + setup_func = function() + npairs.add_rule(Rule("§", "§"):with_move(cond.done())) + end, + key = [[(]], + before = [[§|§]], + after = [[§(|)§]] + }, + { + -- ref: issue #331 + name = "27 move right, should not move on non-end-pair char: `#|#` with \"", + setup_func = function() + npairs.add_rule(Rule("#", "#"):with_move(cond.done())) + end, + key = [["]], + before = [[#|#]], + after = [[#"|"#]] + }, + { + -- ref: issue #331 and #330 + name = "28 move right, should not move on non-end-pair char: `<|>` with (", + setup_func = function() + npairs.add_rule(Rule("<", ">"):with_move(cond.done())) + end, + key = [[(]], + before = [[<|>]], + after = [[<(|)>]], + }, + { + name = "29 move right when inside grave with special slash", key = [[`]], before = [[(`abcd\"|`)]], after = [[(`abcd\"`|)]] }, { - name = "move right when inside quote with special slash" , + name = "30 move right when inside quote with special slash", key = [["]], before = [[("abcd\"|")]], after = [[("abcd\""|)]] }, { - name = "move right on close bracket", - filetype="javascript", - key = [[)]], - before = [[("(dsadsa|" gs})]], - after = [[("(dsadsa)|" gs})]] + filetype = 'rust', + name = "31 move right double quote after single quote", + key = [["]], + before = [[ ('x').expect("|");]], + after = [[ ('x').expect(""|);]], + }, + { + filetype = 'rust', + name = "32 move right, should not move when bracket not closing", + key = [[}]], + before = [[{{ |} ]], + after = [[{{ }|} ]] }, { - name = "move right when inside single quote with special slash", - filetype="javascript", - key = [[']], - before = [[nvim_set_var('test_thing|')]], - after = [[nvim_set_var('test_thing'|)]] + filetype = 'rust', + name = "33 move right, should move when bracket closing", + key = [[}]], + before = [[{ }|} ]], + after = [[{ }}| ]] }, { - name = "delete bracket", - filetype="javascript", - key = [[]], - before = [[aaa(|) ]], - after = [[aaa| ]] + name = "34 delete bracket", + filetype = "javascript", + key = [[]], + before = [[aaa(|) ]], + after = [[aaa| ]] }, { - name = "breakline on {" , - filetype="javascript", - key = [[]], - before = [[a{|}]], - after = [[}]] + name = "35 breakline on {", + filetype = "javascript", + key = [[]], + before = [[a{|}]], + after = { + "a{", + "|", + "}" + } }, { - name = "breakline on (" , - filetype="javascript", - key = [[]], - before = [[a(|)]], - after = [[)]] + setup_func = function() + vim.opt.indentexpr = "nvim_treesitter#indent()" + end, + name = "36 breakline on (", + filetype = "javascript", + key = [[]], + before = [[function ab(|)]], + after = { + "function ab(", + "|", + ")" + } + }, + { + name = "37 breakline on ]", + filetype = "javascript", + key = [[]], + before = [[a[|] ]], + after = { + "a[", + "|", + "]" + } }, { - name = "breakline on ]" , - filetype="javascript", - key = [[]], - before = [[a[|] ]], - after = "] " + name = "38 move ) inside nested function call", + filetype = "javascript", + key = [[)]], + before = { + "fn(fn(|))", + }, + after = { + "fn(fn()|)", + } }, { - name = "breakline on markdown " , - filetype="markdown", - key = [[]], - before = [[``` lua|```]], - after = [[```]] + name = "39 move } inside singleline function's params", + filetype = "javascript", + key = [[}]], + before = { + "({|}) => {}", + }, + after = { + "({}|) => {}", + } }, { - name = "breakline on < html" , + name = "40 move } inside multiline function's params", + filetype = "javascript", + key = [[}]], + before = { + "({|}) => {", + "", + "}", + }, + after = { + "({}|) => {", + "", + "}", + } + }, + { + name = "41 breakline on markdown ", + filetype = "markdown", + key = [[]], + before = [[``` lua|```]], + after = { + [[``` lua]], + [[|]], + [[```]] + } + }, + { + name = "42 breakline on < html", filetype = "html", - key = [[]], - before = [[
|
]], - after = [[]] + key = [[]], + before = [[
|
]], + after = { + [[
]], + [[|]], + [[
]] + } }, { - name = "do not mapping on > html" , + name = "43 breakline on < html with text", filetype = "html", - key = [[>]], - before = [[| ]] + key = [[]], + before = [[
ads |
]], + after = { + [[
ads]], + [[|]], + [[
]] + }, }, { - name = "press multiple key" , + name = "44 breakline on < html with space after cursor", filetype = "html", - key = [[((((]], - before = [[a| ]], - after = [[a((((|)))) ]] + key = [[]], + before = [[
ads |
]], + after = { + [[
ads]], + [[|]], + [[
]] + }, }, { - name="text regex", - filetype = "lua", - key="4", - before = [[u123| ]], - after = [[u1234|number ]] + name = "45 do not mapping on > html", + filetype = "html", + key = [[>]], + before = [[| ]] }, { - name="text regex with custome end_pair", - filetype = "lua", - key="4", - before = [[x123| ]], - after = [[x1234|1234 ]] + name = "46 press multiple key", + filetype = "html", + key = [[((((]], + before = [[a| ]], + after = [[a((((|)))) ]] + }, + { + setup_func = function() + npairs.add_rules({ + Rule('u%d%d%d%d$', 'number', 'lua'):use_regex(true), + }) + end, + name = "47 text regex", + filetype = "lua", + key = "4", + before = [[u123| ]], + after = [[u1234|number ]] }, { - name="text regex with custome key", - filetype = "vim", - key="", - before = [[b1234s| ]], - after = [[B|1234S1234S ]] + setup_func = function() + npairs.add_rules({ + Rule('x%d%d%d%d$', 'number', 'lua'):use_regex(true):replace_endpair(function(opts) + return opts.prev_char:sub(#opts.prev_char - 3, #opts.prev_char) + end), + }) + end, + name = "48 text regex with custom end_pair", + filetype = "lua", + key = "4", + before = [[x123| ]], + after = [[x1234|1234 ]] }, { - name="test move right custom char", - filetype="vim", - key="", - before = [[b1234s| ]], - after = [[B|1234S1234S ]] + setup_func = function() + npairs.add_rules({ + Rule('b%d%d%d%d%w$', '', 'vim'):use_regex(true, ''):replace_endpair(function(opts) + return opts.prev_char:sub(#opts.prev_char - 4, #opts.prev_char) .. 'viwUi' + end), + }) + end, + name = "49 text regex with custom key", + filetype = "vim", + key = "", + before = [[b1234s| ]], + after = [[B|1234S1234S ]] + }, { setup_func = function() npairs.add_rules({ - Rule("-","+","vim") - :with_move(function(opt) - return utils.get_prev_char(opt) == "x" end) + Rule('b%d%d%d%d%w$', '', 'vim'):use_regex(true, ''):replace_endpair(function(opts) + return opts.prev_char:sub(#opts.prev_char - 4, #opts.prev_char) .. 'viwUi' + end), + }) + end, + name = "50 test move right custom char", + filetype = "vim", + key = "", + before = [[b1234s| ]], + after = [[B|1234S1234S ]] + }, + { + setup_func = function() + npairs.add_rules({ + Rule("-", "+", "vim") + :with_move(function(opt) + return utils.get_prev_char(opt) == "x" + end) :with_move(cond.done()) - }) + }) end, - name = "test move right custom char plus", - filetype="vim", - key="+", - before = [[x|+ ]], - after = [[x+| ]] + name = "51 test move right custom char plus", + filetype = "vim", + key = "+", + before = [[x|+ ]], + after = [[x+| ]] }, { setup_func = function() npairs.add_rules({ Rule("/**", "**/", "javascript") - :with_move(cond.none()) + :with_move(cond.none()) }) end, - name="test javascript comment", - filetype = "javascript", - key="*", - before = [[/*| ]], - after = [[/**|**/ ]] + name = "52 test javascript comment", + filetype = "javascript", + key = "*", + before = [[/*| ]], + after = [[/**|**/ ]] }, { setup_func = function() npairs.add_rules({ - Rule("(",")") + Rule("(", ")") :use_key("") :replace_endpair(function() return "" end, true) }) end, - name = "test map custom key" , - filetype = "latex", - key = [[]], - before = [[ abcde(|) ]], - after = [[ abcde| ]], + name = "53 test map custom key", + filetype = "latex", + key = [[]], + before = [[ abcde(|) ]], + after = [[ abcde| ]], }, { setup_func = function() npairs.add_rules { Rule(' ', ' '):with_pair(function(opts) local pair = opts.line:sub(opts.col, opts.col + 1) - return vim.tbl_contains({'()', '[]', '{}'}, pair) + return vim.tbl_contains({ '()', '[]', '{}' }, pair) end), - Rule('( ',' )') + Rule('( ', ' )') :with_pair(function() return false end) :with_del(function() return false end) :with_move(function() return true end) - :use_regex(false,")") + :use_regex(false, ")") } end, - name = "test multiple move right" , - filetype = "latex", - key = [[)]], - before = [[( | ) ]], - after = [[( )| ]], + name = "54 test multiple move right", + filetype = "latex", + key = [[)]], + before = [[( | ) ]], + after = [[( )| ]], }, { setup_func = function() npairs.setup({ - enable_check_bracket_line=false + enable_check_bracket_line = false }) end, - name = "test disable check bracket line" , - filetype = "latex", - key = [[(]], - before = [[(|))) ]], - after = [[((|)))) ]], + name = "55 test disable check bracket line", + filetype = "latex", + key = [[(]], + before = [[(|))) ]], + after = [[((|)))) ]], }, { setup_func = function() npairs.add_rules({ - Rule("<", ">",{"rust"}) - :with_pair(cond.before_text_check("Vec")) + Rule("<", ">", { "rust" }) + :with_pair(cond.before_text("Vec")) }) end, - name = "test disable check bracket line" , - filetype = "rust", - key = [[<]], - before = [[Vec| ]], - after = [[Vec<|> ]], + name = "56 test disable check bracket line", + filetype = "rust", + key = [[<]], + before = [[Vec| ]], + after = [[Vec<|> ]], + }, + { + setup_func = function() + npairs.add_rule(Rule("!", "!"):with_pair(cond.not_filetypes({ "lua" }))) + end, + name = "57 disable pairs in lua", + filetype = "lua", + key = "!", + before = [[x| ]], + after = [[x!| ]] + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rules({ + Rule("%(.*%)%s*%=>", " { }", { "typescript", "typescriptreact", "javascript" }) + :use_regex(true) + :set_end_pair_length(2) + }) + end, + name = "58 mapping regex with custom end_pair_length", + filetype = "typescript", + key = ">", + before = [[(o)=| ]], + after = [[(o)=> { | } ]] + + }, + { + setup_func = function() + npairs.add_rules({ + Rule('(', ')'):use_key(''):replace_endpair(function() + return '' + end, true), + Rule('(', ')'):use_key(''):replace_endpair(function() + return '' + end, true), + }) + end, + name = "59 mapping same pair with different key", + filetype = "typescript", + key = "(", + before = [[(test|) ]], + after = [[(test(|)) ]] + + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rule(Rule("„", "”")) + end, + name = "60 multibyte character from custom keyboard", + not_replace_term_code = true, + key = "„", + before = [[a | ]], + after = [[a „|” ]], + end_cursor = 3 + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rule(Rule("„", "”"):with_move(cond.done())) + end, + name = "61 multibyte character move_right", + not_replace_term_code = true, + key = "”", + before = [[a „|”xx ]], + after = [[a „”|xx ]], + end_cursor = 6 + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rule(Rule("„", "”"):with_move(cond.done())) + end, + name = "62 multibyte character delete", + key = "", + before = [[a „|” ]], + after = [[a | ]], + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rule(Rule("a„", "”b"):with_move(cond.done())) + end, + not_replace_term_code = true, + name = "63 multibyte character and multiple ", + key = "„", + before = [[a| ]], + after = [[a„|”b ]], + end_cursor = 2 + }, + { + setup_func = function() + npairs.setup({ map_c_h = true }) + end, + name = "64 map ", + key = "", + before = [[aa'|' ]], + after = [[aa| ]], + }, + { + setup_func = function() + npairs.setup({ + map_c_w = true + }) + end, + name = "65 map ", + key = "", + before = [[aa'|' ]], + after = [[aa| ]], + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rule(Rule("x", "x", { '-vim', '-rust' })) + end, + filetype = 'vim', + name = "66 disable filetype vim", + key = [[x]], + before = [[a | ]], + after = [[a x| ]] + }, + { + filetype = 'vim', + name = "67 undo on quote", + key = [[{123u]], + end_cursor = 12, + before = [[local abc=| ]], + after = [[local abc={|} ]] + }, + { + filetype = 'vim', + name = "68 undo on bracket", + key = [['123u]], + end_cursor = 12, + before = [[local abc=| ]], + after = [[local abc='|' ]] + }, + { + filetype = 'vim', + name = "69 double quote on vim after char", + key = [["ab]], + before = [[echo | ]], + after = [[echo "ab|" ]] + }, + { + filetype = 'vim', + name = "70 double quote on vim on begin", + key = [["ab]], + before = [[ | aaa]], + after = [[ "ab| aaa]] + }, + { + setup_func = function() + npairs.add_rule( + Rule('struct%s[a-zA-Z]+%s?{$', '};') + :use_regex(true, "{") + ) + end, + filetype = 'javascript', + name = "71 custom endwise rule", + key = [[{]], + before = [[struct abc | ]], + after = [[struct abc {|};]], + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rule(Rule("{", "}"):end_wise()) + end, + filetype = 'javascript', + name = "72 custom endwise rule", + key = [[]], + before = [[function () {| ]], + after = { + [[function () {]], + [[|]], + [[}]], + }, + }, + { + setup_func = function() + vim.opt.smartindent = true + end, + filetype = 'ps1', + name = "73 indent on powershell", + key = [[]], + before = [[function () {|} ]], + after = { + [[function () {]], + [[|]], + [[}]], + }, + }, + { + setup_func = function() + npairs.clear_rules() + npairs.add_rule( + Rule("{", "") + :replace_endpair(function() + return "}" + end) + :end_wise() + ) + end, + filetype = 'javascript', + name = "74 custom endwise rule with custom end_pair", + key = [[]], + before = [[function () {| ]], + after = { + [[function () {]], + [[|]], + [[}]], + }, + }, + { + name = "75 open bracker on back tick", + key = [[(]], + before = [[ |`abcd`]], + after = [[ (`abcd`) ]] + }, + { + name = "76 should not add bracket on line have bracket ", + key = [[(]], + before = [[ |(abcd))]], + after = [[ ((abcd)) ]] + }, + { + name = "77 not add bracket on line have bracket ", + key = [[(]], + before = [[ |(abcd) ( visual)]], + after = [[ ()(abcd) ( visual)]] + }, + { + name = "78 should add single quote when it have primes char", + key = [[']], + before = [[Ben's friends say: | ]], + after = [[Ben's friends say: '|' ]] + }, + { + name = "79 a quote with single quote string", + key = "'", + before = [[{{("It doesn't name %s", ''), 'ErrorMsg'| }}, ]], + after = [[{{("It doesn't name %s", ''), 'ErrorMsg''|' }}, ]], + end_cursor = 41 + }, + { + name = "80 add normal quote with '", + key = [["]], + before = [[aa| 'aa]], + after = [[aa"|" 'aa]] + }, + { + name = "81 add closing single quote for python prefixed string", + filetype = "python", + key = [[']], + before = [[print(f|)]], + after = [[print(f'|')]] + }, + { + name = "82 add closing single quote for capital python prefixed string", + filetype = "python", + key = [[']], + before = [[print(B|)]], + after = [[print(B'|')]] + }, + { + name = "83 don't add closing single quote for random prefix string", + filetype = "python", + key = [[']], + before = [[print(s|)]], + after = [[print(s'|)]] + }, + { + name = "84 don't add closing single quote for other filetype prefixed string", + filetype = "lua", + key = [[']], + before = [[print(f|)]], + after = [[print(f'|)]] + }, + { + name = "85 allow brackets in prefixed python single quote string", + filetype = "python", + key = [[{]], + before = [[print(f'|')]], + after = [[print(f'{|}')]] + }, + { + name = "86 move ' is working on python", + filetype = "python", + key = [[']], + before = [[('|') ]], + after = [[(''|) ]] + }, + { + setup_func = function() + npairs.add_rules({ + Rule('123456', '789'):with_pair(cond.before_regex('^12345$', 5)), + }) + end, + name = '87 test before_regex with a specific string length', + key = [[123456]], + before = [[ some text before| ]], + after = [[ some text before123456|789 ]], + }, + { + name = "88 disable on count mode", + filetype = "txt", + key = function() + local keys = vim.api.nvim_replace_termcodes('2otest({', true, true, true) + vim.api.nvim_feedkeys(keys, 'x', true) + end, + before = [[ | ]], + after = { + '', + ' test({', + } }, } -local run_data = {} -for _, value in pairs(data) do - if value.only == true then - table.insert(run_data, value) - break - end -end -if #run_data == 0 then run_data = data end +local run_data = _G.Test_filter(data) -local reset_test = function() npairs.setup() end -local function Test(test_data) - for _, value in pairs(test_data) do - it("test "..value.name, function() - if value.setup_func then value.setup_func() end - local before = string.gsub(value.before , '%|' , "") - local after = string.gsub(value.after , '%|' , "") - local p_before = string.find(value.before , '%|') - local p_after = string.find(value.after , '%|') - local line = 1 - if value.filetype ~= nil then - vim.bo.filetype = value.filetype - else - vim.bo.filetype = "text" - end - utils.set_attach(vim.api.nvim_get_current_buf(),0) - npairs.on_attach(vim.api.nvim_get_current_buf()) - vim.fn.setline(line , before) - vim.fn.setpos('.' ,{0, line, p_before , 0}) - helpers.insert(value.key) - vim.wait(10) - helpers.feed("") - local result = vim.fn.getline(line) - local pos = vim.fn.getpos('.') - if value.key ~= '' then - eq(after, result , "\n\n text error: " .. value.name .. "\n") - eq(p_after, pos[3] + 1, "\n\n pos error: " .. value.name .. "\n") - else - local line2 = vim.fn.getline(line + 2) - eq(line + 1, pos[2], '\n\n breakline error:' .. value.name .. "\n") - eq(after, line2 , "\n\n text error: " .. value.name .. "\n") - vim.fn.setline(line, '') - vim.fn.setline(line+ 1, '') - vim.fn.setline(line+ 2, '') +describe("autopairs ", function() + _G.Test_withfile(run_data, { + cursor_add = 0, + before_each = function(value) + npairs.setup() + vim.opt.indentexpr = "" + if value.setup_func then + value.setup_func() end - if value.reset_func then value.reset_func() end - if not value.reset_func and value.setup_func then reset_test() end - end) - end -end - -describe('autopairs ', function() - Test(run_data) + end, + }) end) diff --git a/tests/test_utils.lua b/tests/test_utils.lua index c5cfbe31..e259d99e 100644 --- a/tests/test_utils.lua +++ b/tests/test_utils.lua @@ -1,35 +1,42 @@ local utils = require('nvim-autopairs.utils') -local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') local log = require('nvim-autopairs._log') local api = vim.api +local ts_get_node_text = vim.treesitter.get_node_text or vim.treesitter.query.get_node_text local helpers = {} -function helpers.feed(text, feed_opts) + +function helpers.feed(text, feed_opts, is_replace) feed_opts = feed_opts or 'n' - local to_feed = vim.api.nvim_replace_termcodes(text, true, false, true) - vim.api.nvim_feedkeys(to_feed, feed_opts, true) + if not is_replace then + text = vim.api.nvim_replace_termcodes(text, true, false, true) + end + vim.api.nvim_feedkeys(text, feed_opts, true) end -function helpers.insert(text) - helpers.feed('i' .. text, 'x') +function helpers.insert(text, is_replace) + helpers.feed('i' .. text, 'x', is_replace) end + utils.insert_char = function(text) - api.nvim_put({text}, "c", true, true) + api.nvim_put({ text }, 'c', true, true) end -utils.feed = function(text,num) +utils.feed = function(text, num) local result = '' for _ = 1, num, 1 do result = result .. text end - api.nvim_feedkeys (api.nvim_replace_termcodes( - result, true, false, true), - "x", true) + api.nvim_feedkeys( + ---@diagnostic disable-next-line: param-type-mismatch + api.nvim_replace_termcodes(result, true, false, true), + 'x', + true + ) end _G.eq = assert.are.same -_G.Test_filter = function (data) +_G.Test_filter = function(data) local run_data = {} for _, value in pairs(data) do if value.only == true then @@ -37,85 +44,146 @@ _G.Test_filter = function (data) break end end - if #run_data == 0 then run_data = data end + if #run_data == 0 then + run_data = data + end return run_data end - +local compare_text = function(linenr, text_after, name, cursor_add, end_cursor) + cursor_add = cursor_add or 0 + local new_text = vim.api.nvim_buf_get_lines( + 0, + linenr - 1, + linenr + #text_after - 1, + true + ) + for i = 1, #text_after, 1 do + local t = string.gsub(text_after[i], '%|', '') + if t + and new_text[i] + and t:gsub('%s+$', '') ~= new_text[i]:gsub('%s+$', '') + then + eq(t, new_text[i], '\n\n text error: ' .. name .. '\n') + end + local p_after = string.find(text_after[i], '%|') + if p_after then + local row, col = utils.get_cursor() + if end_cursor then + eq(row, linenr + i - 2, '\n\n cursor row error: ' .. name .. '\n') + eq( + col + 1, + end_cursor, + '\n\n end cursor column error : ' .. name .. '\n' + ) + else + eq(row, linenr + i - 2, '\n\n cursor row error: ' .. name .. '\n') + p_after = p_after + cursor_add + eq( + col, + math.max(p_after - 2, 0), + '\n\n cursor column error : ' .. name .. '\n' + ) + end + end + end + return true +end _G.Test_withfile = function(test_data, cb) for _, value in pairs(test_data) do - it("test "..value.name, function() + it('test ' .. value.name, function(_) local text_before = {} + value.linenr = value.linenr or 1 local pos_before = { linenr = value.linenr, - colnr = 0 + colnr = 0, } if not vim.tbl_islist(value.before) then - value.before = {value.before} + value.before = { value.before } end for index, text in pairs(value.before) do - local txt = string.gsub(text, '%|' , "") - table.insert(text_before, txt ) - if string.match( text, "%|") then - if string.find(text,'%|') then - pos_before.colnr = string.find(text, '%|') - pos_before.linenr = value.linenr + index-1 + local txt = string.gsub(tostring(text), '%|', '') + table.insert(text_before, txt) + if string.match(tostring(text), '%|') then + if string.find(tostring(text), '%|') then + pos_before.colnr = string.find(tostring(text), '%|') + pos_before.linenr = value.linenr + index - 1 end end end - local after = string.gsub(value.after, '%|' , "") - local p_after = string.find(value.after , '%|') - vim.bo.filetype = value.filetype + if not vim.tbl_islist(value.after) then + value.after = { value.after } + end + vim.bo.filetype = value.filetype or 'text' + vim.cmd(':bd!') + if cb.before_each then + cb.before_each(value) + end + ---@diagnostic disable-next-line: missing-parameter if vim.fn.filereadable(vim.fn.expand(value.filepath)) == 1 then - vim.cmd(":bd!") - if cb.before then cb.before(value) end - vim.cmd(":e " .. value.filepath) + vim.cmd(':e ' .. value.filepath) if value.filetype then vim.bo.filetype = value.filetype - vim.cmd(":e") end - vim.api.nvim_buf_set_lines(0, value.linenr -1, value.linenr +#text_before, false, text_before) - vim.fn.cursor(pos_before.linenr, pos_before.colnr) - log.debug("insert:"..value.key) - helpers.insert(value.key) - vim.wait(10) - helpers.feed("") - if value.key == '' then - local result = vim.fn.getline(pos_before.linenr + 2) - local pos = vim.fn.getpos('.') - eq(pos_before.linenr + 1, pos[2], '\n\n breakline error:' .. value.name .. "\n") - eq(after, result , "\n\n text error: " .. value.name .. "\n") - - else - local result = vim.fn.getline(pos_before.linenr) - local pos = vim.fn.getpos('.') - eq(after, result , "\n\n text error: " .. value.name .. "\n") - eq(p_after, pos[3] + 1, "\n\n pos error: " .. value.name .. "\n") + vim.cmd(':e') + else + vim.cmd(':new') + if value.filetype then + vim.bo.filetype = value.filetype end - if cb.after then cb.after(value) end + end + local status, parser = pcall(vim.treesitter.get_parser, 0) + if status then + parser:parse(true) + end + vim.api.nvim_buf_set_lines( + 0, + value.linenr - 1, + value.linenr + #text_before, + false, + text_before + ) + vim.api.nvim_win_set_cursor( + 0, + { pos_before.linenr, pos_before.colnr - 1 } + ) + if type(value.key) == "function" then + log.debug("call key") + value.key() else - eq(false, true, "\n\n file not exist " .. value.filepath .. "\n") + log.debug('insert:' .. value.key) + helpers.insert(value.key, value.not_replace_term_code) + vim.wait(2) + helpers.feed('') + end + compare_text( + value.linenr, + value.after, + value.name, + cb.cursor_add, + value.end_cursor + ) + if cb.after_each then + cb.after_each(value) end + vim.cmd(':bd!') end) end end _G.dump_node = function(node) - local text=ts_utils.get_node_text(node) + local text = ts_get_node_text(node) for _, txt in pairs(text) do print(txt) end end - - _G.dump_node_text = function(target) for node in target:iter_children() do local node_type = node:type() - local text = ts_utils.get_node_text(node) - log.debug("type:" .. node_type .. " ") + local text = ts_get_node_text(node) + log.debug('type:' .. node_type .. ' ') log.debug(text) end end - diff --git a/tests/treesitter_spec.lua b/tests/treesitter_spec.lua index 7fb0f019..5e2af8d3 100644 --- a/tests/treesitter_spec.lua +++ b/tests/treesitter_spec.lua @@ -1,90 +1,131 @@ - local npairs = require('nvim-autopairs') -local ts = require 'nvim-treesitter.configs' +local ts = require('nvim-treesitter.configs') local log = require('nvim-autopairs._log') -local Rule=require('nvim-autopairs.rule') -local ts_conds=require('nvim-autopairs.ts-conds') +local Rule = require('nvim-autopairs.rule') +local ts_conds = require('nvim-autopairs.ts-conds') -_G.npairs = npairs; -npairs.setup({ - check_ts = true, - ts_config={ - javascript = {'template_string', 'comment'} - } -}) +_G.npairs = npairs +vim.api.nvim_set_keymap( + 'i', + '', + 'v:lua.npairs.check_break_line_char()', + { expr = true, noremap = true } +) -npairs.add_rules({ - Rule("%", "%", "lua") - :with_pair(ts_conds.is_ts_node({'string', 'comment'})), +ts.setup({ + ensure_installed = { 'lua', 'javascript', 'rust', 'markdown', 'markdown_inline' }, + highlight = { enable = true }, + autopairs = { enable = true }, }) -vim.api.nvim_set_keymap('i' , '','v:lua.npairs.check_break_line_char()', {expr = true , noremap = true}) - -ts.setup { - ensure_installed = {'lua', 'javascript'}, - highlight = {enable = true}, - autopairs = {enable = true} -} local data = { { - name = "treesitter lua quote" , + name = 'treesitter lua quote', filepath = './tests/endwise/init.lua', - filetype = "lua", - linenr = 5, - key = [["]], - before = { + filetype = 'lua', + linenr = 5, + key = [["]], + before = { [[ [[ aaa| ]], [[ ]], - "]]" + ']]', }, - after = [[ [[ aaa"| ]] + after = [[ [[ aaa"| ]], }, { - name = "treesitter javascript quote" , - filepath = './tests/endwise/javascript.js', - filetype = "javascript", - linenr = 5, - key = [[(]], - before = { - [[ const data= `aaa | ]], - [[ ]], - "`" - }, - after = [[ const data= `aaa (| ]] - }, - { - name = "ts_conds is_ts_node quote" , + setup_func = function() + npairs.add_rules({ + Rule('%', '%', 'lua'):with_pair( + ts_conds.is_ts_node({ 'string', 'comment', 'string_content' }) + ), + }) + end, + name = 'ts_conds is_ts_node quote', filepath = './tests/endwise/init.lua', - filetype = "lua", - linenr = 5, - key = [[%]], - before = { + filetype = 'lua', + linenr = 5, + key = [[%]], + before = { [[ [[ abcde | ]], [[ ]], - "]]" + ']]', }, - after = [[ [[ abcde %|% ]] + after = [[ [[ abcde %|% ]], }, { - name = "ts_conds is_ts_node failed", + name = 'ts_conds is_ts_node failed', filepath = './tests/endwise/init.lua', - linenr = 5, - filetype = "lua", - key="%", - before = {[[local abcd| = ' visual ']]}, - after = [[local abcd%| = ' visual ']] - } + linenr = 5, + filetype = 'lua', + key = '%', + before = { [[local abcd| = ' visual ']] }, + after = [[local abcd%| = ' visual ']], + }, + { + setup_func = function() + npairs.add_rules({ + Rule('<', '>', 'rust'):with_pair(ts_conds.is_ts_node({ + 'type_identifier', + 'let_declaration', + 'parameters', + })), + }) + end, + name = 'ts_conds is_ts_node failed', + filepath = './tests/endwise/main.rs', + linenr = 5, + filetype = 'rust', + key = '<', + before = [[pub fn noop(_inp: Vec|) {]], + after = [[pub fn noop(_inp: Vec<|>) {]], + }, + { + setup_func = function() + npairs.add_rules({ + Rule('*', '*', { 'markdown', 'markdown_inline' }) + :with_pair(ts_conds.is_not_in_context()), + }) + end, + name = 'ts_context markdown `*` success md_context', + filepath = './tests/endwise/sample.md', + linenr = 2, + filetype = 'markdown', + key = '*', + before = [[|]], + after = [[*|*]], + }, + { + setup_func = function() + npairs.add_rules({ + Rule('*', '*', { 'markdown', 'markdown_inline' }) + :with_pair(ts_conds.is_not_in_context()), + }) + end, + name = 'ts_context codeblock `*` fail js_context', + filepath = './tests/endwise/sample.md', + linenr = 6, + filetype = 'markdown', + key = '*', + before = [[let calc = 1 |]], + after = [[let calc = 1 *|]], + }, } local run_data = _G.Test_filter(data) -local _, ts_utils = pcall(require, 'nvim-treesitter.ts_utils') -_G.TU = ts_utils - - describe('[treesitter check]', function() - _G.Test_withfile(run_data,{ - before = function() end + _G.Test_withfile(run_data, { + before_each = function(value) + npairs.setup({ + check_ts = true, + ts_config = { + javascript = { 'template_string', 'comment' }, + }, + }) + if value.setup_func then + value.setup_func() + end + end, }) end) diff --git a/tests/utils_spec.lua b/tests/utils_spec.lua index 5ef4a991..14564da4 100644 --- a/tests/utils_spec.lua +++ b/tests/utils_spec.lua @@ -61,4 +61,5 @@ describe('utils test substring ', function() end) end end) + vim.wait(100)