on master help sign in
11 files changed+255−127
since splitsnapshots
unifiedsplit
.gitignore+1 README.md+18−3 lua/nvim-autopairs.lua+76−84 lua/nvim-autopairs/completion/cmp.lua+2−1 lua/nvim-autopairs/completion/handlers.lua+1−1 lua/nvim-autopairs/conds.lua+77−14 lua/nvim-autopairs/fastwrap.lua+4−12 lua/nvim-autopairs/rule.lua+1−5 lua/nvim-autopairs/rules/basic.lua+10−4 lua/nvim-autopairs/utils.lua+3−2 tests/nvim-autopairs_spec.lua+62−1
.gitignorenew file+1
@@ -0,0 +1 @@
1+doc/tags
README.md+18−3
@@ -2,7 +2,7 @@
22
33A super powerful autopair plugin for Neovim that supports multiple characters.
44
5−Requires neovim 0.5+
5+Requires neovim 0.7
66
77## Installation
88
@@ -33,11 +33,13 @@ use {
3333local disable_filetype = { "TelescopePrompt" }
3434local disable_in_macro = false -- disable when recording or executing a macro
3535local disable_in_visualblock = false -- disable when insert after visual block mode
36−local ignored_next_char = [=[[%w%%%'%[%"%.]]=]
36+local disable_in_replace_mode = true
37+local ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=]
3738local enable_moveright = true
3839local enable_afterquote = true -- add bracket pairs after quote
3940local enable_check_bracket_line = true --- check bracket in same line
4041local enable_bracket_in_quote = true --
42+local enable_abbr = false -- trigger abbreviation
4143local break_undo = true -- switch for basic rule break undo sequence
4244local check_ts = false
4345local map_cr = true
@@ -338,9 +340,17 @@ Before Input After
338340 require('nvim-autopairs').enable()
339341 require('nvim-autopairs').remove_rule('(') -- remove rule (
340342 require('nvim-autopairs').clear_rules() -- clear all rules
341− require('nvim-autopairs').get_rule('"') -- get rule " then modify it
343+ -- get rule " then modify it. It can return a list of rule or just a rule
344+ require('nvim-autopairs').get_rule('"')
345+```
342346
347+* Sample
348+```lua
349+-- remove add single quote on filetype scheme or lisp
350+require("nvim-autopairs").get_rule("'")[1].not_filetypes = { "scheme", "lisp" }
351+require("nvim-autopairs").get_rule("'")[1]:with_pair(cond.not_after_text("["}))
343352```
353+
344354### FastWrap
345355
346356``` text
@@ -381,3 +391,8 @@ npairs.setup({
381391
382392### Custom rules
383393[rules](https://github.com/windwp/nvim-autopairs/wiki/Custom-rules)
394+
395+## Sponsors
396+
397+Thanks to everyone who sponsors my projects and makes continued development maintenance possible!
398+<!-- patreon --><a href="https://github.com/t4t5"><img src="https://github.com/t4t5.png" width="60px" alt="" /></a><!-- patreon-->
lua/nvim-autopairs.lua+76−84
@@ -19,13 +19,15 @@ local default = {
1919 disable_filetype = { 'TelescopePrompt', 'spectre_panel' },
2020 disable_in_macro = false,
2121 disable_in_visualblock = false,
22− ignored_next_char = [=[[%w%%%'%[%"%.]]=],
22+ disable_in_replace_mode = true,
23+ ignored_next_char = [=[[%w%%%'%[%"%.%`%$]]=],
2324 break_undo = true,
2425 check_ts = false,
2526 enable_moveright = true,
2627 enable_afterquote = true,
2728 enable_check_bracket_line = true,
2829 enable_bracket_in_quote = true,
30+ enable_abbr = false,
2931 ts_config = {
3032 lua = { 'string', 'source' },
3133 javascript = { 'string', 'template_string' },
@@ -52,18 +54,21 @@ M.setup = function(opt)
5254 end
5355
5456 M.force_attach()
55−
56− api.nvim_exec(
57− [[
58− augroup autopairs_buf
59− autocmd!
60− autocmd BufEnter,BufWinEnter * lua require("nvim-autopairs").on_attach()
61− autocmd BufDelete * lua require("nvim-autopairs").set_buf_rule(nil,tonumber(vim.fn.expand("<abuf>")))
62− autocmd FileType * lua require("nvim-autopairs").force_attach()
63− augroup end
64− ]],
65− false
66− )
57+ local group = api.nvim_create_augroup('autopairs_buf', { clear = true })
58+ api.nvim_create_autocmd({ 'BufEnter', 'BufWinEnter' }, {
59+ group = group, pattern = '*',
60+ callback = function() M.on_attach() end
61+ })
62+ api.nvim_create_autocmd('BufDelete', {
63+ group = group, pattern = '*',
64+ callback = function(data)
65+ M.set_buf_rule(nil, tonumber(data.buf) or 0)
66+ end,
67+ })
68+ api.nvim_create_autocmd('FileType', {
69+ group = group, pattern = '*',
70+ callback = function() M.force_attach() end
71+ })
6772end
6873
6974M.add_rule = function(rule)
@@ -98,7 +103,7 @@ M.remove_rule = function(pair)
98103 if r.start_pair ~= pair then
99104 table.insert(state_tbl, r)
100105 elseif r.key_map and r.key_map ~= '' then
101− vim.api.nvim_buf_del_keymap(0, 'i', r.key_map)
106+ api.nvim_buf_del_keymap(0, 'i', r.key_map)
102107 end
103108 end
104109 M.set_buf_rule(state_tbl, 0)
@@ -132,6 +137,15 @@ M.force_attach = function(bufnr)
132137 M.on_attach(bufnr)
133138end
134139
140+local del_keymaps = function()
141+ local status, autopairs_keymaps = pcall(api.nvim_buf_get_var, 0, 'autopairs_keymaps')
142+ if status and autopairs_keymaps and #autopairs_keymaps > 0 then
143+ for _, key in pairs(autopairs_keymaps) do
144+ pcall(api.nvim_buf_del_keymap, 0, 'i', key)
145+ end
146+ end
147+end
148+
135149local function is_disable()
136150 if M.state.disabled then
137151 return true
@@ -152,14 +166,16 @@ local function is_disable()
152166 return true
153167 end
154168
169+ if M.config.disable_in_replace_mode and vim.api.nvim_get_mode().mode == "R" then
170+ return true
171+ end
172+
155173 if M.config.disable_in_visualblock and utils.is_block_wise_mode() then
156174 return true
157175 end
158176
159177 if utils.check_filetype(M.config.disable_filetype, vim.bo.filetype) then
160− -- should have a way to remove the mapping when vim.bo.filetype = ''
161− -- now we only remove a rule
162− -- the event FileType happen after BufEnter
178+ del_keymaps()
163179 M.set_buf_rule({}, 0)
164180 return true
165181 end
@@ -168,14 +184,14 @@ end
168184
169185---@return table <number, Rule>
170186M.get_buf_rules = function(bufnr)
171− return M.state.rules[bufnr or vim.api.nvim_get_current_buf()] or {}
187+ return M.state.rules[bufnr or api.nvim_get_current_buf()] or {}
172188end
173189
174−---@param rules table list or rule
190+---@param rules nil|table list or rule
175191---@param bufnr number buffer number
176192M.set_buf_rule = function(rules, bufnr)
177193 if bufnr == 0 or bufnr == nil then
178− bufnr = vim.api.nvim_get_current_buf()
194+ bufnr = api.nvim_get_current_buf()
179195 end
180196 M.state.rules[bufnr] = rules
181197end
@@ -225,78 +241,50 @@ M.on_attach = function(bufnr)
225241 if utils.is_attached(bufnr) then
226242 return
227243 end
244+ del_keymaps()
228245 local enable_insert_auto = false
246+ local autopairs_keymaps = {}
247+ local expr_map = function(key)
248+ api.nvim_buf_set_keymap(bufnr, 'i', key, '', {
249+ expr = true,
250+ noremap = true,
251+ desc = "autopairs map key",
252+ callback = function() return M.autopairs_map(bufnr, key) end,
253+ })
254+ table.insert(autopairs_keymaps, key)
255+ end
229256 for _, rule in pairs(rules) do
230257 if rule.key_map ~= nil then
231258 if rule.is_regex == false then
232259 if rule.key_map == '' then
233260 rule.key_map = rule.start_pair:sub(#rule.start_pair)
234261 end
235− local key = string.format('"%s"', rule.key_map)
236− if rule.key_map == '"' then
237− key = [['"']]
238− end
239− local mapping = string.format('v:lua.MPairs.autopairs_map(%d,%s)', bufnr, key)
240− api.nvim_buf_set_keymap(
241− bufnr,
242− 'i',
243− rule.key_map,
244− mapping,
245− { expr = true, noremap = true }
246− )
247−
262+ expr_map(rule.key_map)
248263 local key_end = rule.key_end or rule.end_pair:sub(1, 1)
249264 if #key_end >= 1 and key_end ~= rule.key_map and rule.move_cond ~= nil then
250− mapping = string.format(
251− [[v:lua.MPairs.autopairs_map(%d, '%s')]],
252− bufnr,
253− key_end
254− )
255− vim.api.nvim_buf_set_keymap(
256− bufnr,
257− 'i',
258− key_end,
259− mapping,
260− { expr = true, noremap = true }
261− )
265+ expr_map(key_end)
262266 end
263267 else
264268 if rule.key_map ~= '' then
265− local mapping = string.format(
266− "v:lua.MPairs.autopairs_map(%d,'%s')",
267− bufnr,
268− rule.key_map
269− )
270− api.nvim_buf_set_keymap(
271− bufnr,
272− 'i',
273− rule.key_map,
274− mapping,
275− { expr = true, noremap = true }
276− )
269+ expr_map(rule.key_map)
277270 elseif rule.is_endwise == false then
278271 enable_insert_auto = true
279272 end
280273 end
281274 end
282275 end
276+ api.nvim_buf_set_var(bufnr, 'autopairs_keymaps', autopairs_keymaps)
283277
284278 if enable_insert_auto then
285279 -- capture all key use it to trigger regex pairs
286280 -- it can make an issue with paste from register
287− api.nvim_exec(
288− string.format(
289− [[
290− augroup autopairs_insert_%d
291− autocmd!
292− autocmd InsertCharPre <buffer=%d> call luaeval("require('nvim-autopairs').autopairs_insert(%d, _A)", v:char)
293− augroup end ]],
294− bufnr,
295− bufnr,
296− bufnr
297− ),
298− false
299− )
281+ api.nvim_create_autocmd('InsertCharPre', {
282+ group = api.nvim_create_augroup(string.format("autopairs_insert_%d", bufnr), { clear = true }),
283+ buffer = bufnr,
284+ callback = function()
285+ M.autopairs_insert(bufnr, vim.v.char)
286+ end
287+ })
300288 end
301289
302290 if M.config.fast_wrap and M.config.fast_wrap.map then
@@ -314,7 +302,7 @@ M.on_attach = function(bufnr)
314302 bufnr,
315303 'i',
316304 '<bs>',
317− string.format('v:lua.MPairs.autopairs_bs(%d)', bufnr),
305+ 'v:lua.MPairs.autopairs_bs()',
318306 { expr = true, noremap = true }
319307 )
320308 end
@@ -345,7 +333,7 @@ local autopairs_delete = function(bufnr, key)
345333 if is_disable() then
346334 return utils.esc(key)
347335 end
348− bufnr = bufnr or 0
336+ bufnr = bufnr or api.nvim_get_current_buf()
349337 local line = utils.text_get_current_line(bufnr)
350338 local _, col = utils.get_cursor()
351339 local rules = M.get_buf_rules(bufnr)
@@ -362,17 +350,19 @@ local autopairs_delete = function(bufnr, key)
362350 and utils.compare(rule.end_pair, next_char, rule.is_regex)
363351 and rule:can_del({
364352 ts_node = M.state.ts_node,
353+ rule = rule,
365354 bufnr = bufnr,
366355 prev_char = prev_char,
367356 next_char = next_char,
368357 line = line,
358+ col = col,
369359 })
370360 then
371361 local input = ''
372− for _ = 1, vim.api.nvim_strwidth(rule.start_pair), 1 do
362+ for _ = 1, api.nvim_strwidth(rule.start_pair), 1 do
373363 input = input .. utils.key.bs
374364 end
375− for _ = 1, vim.api.nvim_strwidth(rule.end_pair), 1 do
365+ for _ = 1, api.nvim_strwidth(rule.end_pair), 1 do
376366 input = input .. utils.key.del
377367 end
378368 return utils.esc('<c-g>U' .. input)
@@ -405,8 +395,8 @@ M.autopairs_map = function(bufnr, char)
405395 local rules = M.get_buf_rules(bufnr)
406396 for _, rule in pairs(rules) do
407397 if rule.start_pair then
408− if rule.key_map and rule.key_map:match('<.*>') then
409− new_text = line:sub(1, col) .. line:sub(col + 1, #line)
398+ if char:match('<.*>') then
399+ new_text = line
410400 add_char = 0
411401 else
412402 new_text = line:sub(1, col) .. char .. line:sub(col + 1, #line)
@@ -443,7 +433,8 @@ M.autopairs_map = function(bufnr, char)
443433 return utils.esc(utils.repeat_key(utils.key.join_right, end_pair_length))
444434 end
445435
446− if utils.compare(rule.start_pair, prev_char, rule.is_regex)
436+ if rule.key_map == char
437+ and utils.compare(rule.start_pair, prev_char, rule.is_regex)
447438 and rule:can_pair(cond_opt)
448439 then
449440 local end_pair = rule:get_end_pair(cond_opt)
@@ -460,12 +451,15 @@ M.autopairs_map = function(bufnr, char)
460451 if rule.is_undo then
461452 result = utils.esc(utils.key.undo_sequence) .. result .. utils.esc(utils.key.undo_sequence)
462453 end
454+ if M.config.enable_abbr then
455+ result = utils.esc(utils.key.abbr) .. result
456+ end
463457 log.debug("key_map :" .. result)
464458 return result
465459 end
466460 end
467461 end
468− return M.autopairs_afterquote(new_text, char)
462+ return M.autopairs_afterquote(new_text, utils.esc(char))
469463end
470464
471465M.autopairs_insert = function(bufnr, char)
@@ -551,10 +545,6 @@ M.autopairs_cr = function(bufnr)
551545 prev_char = prev_char,
552546 next_char = next_char,
553547 }
554−
555− local end_pair = rule:get_end_pair(cond_opt)
556− local end_pair_length = rule:get_end_pair_length(end_pair)
557−
558548 -- log.debug('prev_char' .. rule.start_pair)
559549 -- log.debug('prev_char' .. prev_char)
560550 -- log.debug('next_char' .. next_char)
@@ -562,6 +552,8 @@ M.autopairs_cr = function(bufnr)
562552 and utils.compare(rule.start_pair, prev_char, rule.is_regex)
563553 and rule:can_cr(cond_opt)
564554 then
555+ local end_pair = rule:get_end_pair(cond_opt)
556+ local end_pair_length = rule:get_end_pair_length(end_pair)
565557 return utils.esc(
566558 end_pair
567559 .. utils.repeat_key(utils.key.join_left, end_pair_length)
@@ -608,7 +600,7 @@ M.autopairs_afterquote = function(line, key_char)
608600 is_prev_slash = char == '\\'
609601 end
610602 if count == 2 and index >= (#line - 2) then
611− local rules = M.get_buf_rules(vim.api.nvim_get_current_buf())
603+ local rules = M.get_buf_rules(api.nvim_get_current_buf())
612604 for _, rule in pairs(rules) do
613605 if rule.start_pair == prev_char and char_end ~= rule.end_pair then
614606 local new_text = line:sub(0, index)
@@ -646,7 +638,7 @@ M.map_cr = function()
646638 return M.autopairs_cr()
647639 end
648640 end
649− vim.api.nvim_set_keymap(
641+ api.nvim_set_keymap(
650642 'i',
651643 '<CR>',
652644 'v:lua.MPairs.completion_confirm()',
lua/nvim-autopairs/completion/cmp.lua+2−1
@@ -38,7 +38,8 @@ M.filetypes = {
3838 handler = handlers.lisp
3939 }
4040 },
41− tex = false
41+ tex = false,
42+ haskell = false
4243}
4344
4445M.on_confirm_done = function(opts)
lua/nvim-autopairs/completion/handlers.lua+1−1
@@ -12,7 +12,7 @@ M["*"] = function(char, item, bufnr, commit_character)
1212 local char_before, char_after = utils.text_cusor_line(line, col, 1, 1, false)
1313
1414 if char == '' or char_before == char or char_after == char
15− or (item.data and item.data.funcParensDisabled)
15+ or (item.data and type(item.data) == 'table' and item.data.funcParensDisabled)
1616 or (item.textEdit and item.textEdit.newText and item.textEdit.newText:match "[%(%[%$]")
1717 or (item.insertText and item.insertText:match "[%(%[%$]")
1818 or char == commit_character
lua/nvim-autopairs/conds.lua+77−14
@@ -1,5 +1,16 @@
11local utils = require('nvim-autopairs.utils')
22local log = require('nvim-autopairs._log')
3+---@class CondOpts
4+---@field ts_node table
5+---@field text string
6+---@field rule table
7+---@field bufnr number
8+---@field col number
9+---@field char string
10+---@field line string
11+---@field prev_char string
12+---@field next_char string
13+---@field is_endwise string
314
415local cond = {}
516
@@ -31,6 +42,7 @@ cond.before_regex = function(regex, length)
3142 if not regex then
3243 return cond.none()
3344 end
45+ ---@param opts CondOpts
3446 return function(opts)
3547 log.debug('before_regex')
3648 if length < 0 then
@@ -46,6 +58,7 @@ end
4658
4759cond.before_text = function(text)
4860 local length = #text
61+ ---@param opts CondOpts
4962 return function(opts)
5063 log.debug('before_text')
5164 local str = utils.text_sub_char(opts.line, opts.col - 1, -length)
@@ -58,6 +71,7 @@ end
5871
5972cond.after_text = function(text)
6073 local length = #text
74+ ---@param opts CondOpts
6175 return function(opts)
6276 log.debug('after_text')
6377 local str = utils.text_sub_char(opts.line, opts.col, length)
@@ -73,6 +87,7 @@ cond.after_regex = function(regex, length)
7387 if not regex then
7488 return cond.none()
7589 end
90+ ---@param opts CondOpts
7691 return function(opts)
7792 log.debug('after_regex')
7893 if length < 0 then
@@ -99,6 +114,7 @@ end
99114
100115cond.not_after_text = function(text)
101116 local length = #text
117+ ---@param opts CondOpts
102118 return function(opts)
103119 log.debug('not_after_text')
104120 local str = utils.text_sub_char(opts.line, opts.col, length)
@@ -113,6 +129,7 @@ cond.not_before_regex = function(regex, length)
113129 if not regex then
114130 return cond.none()
115131 end
132+ ---@param opts CondOpts
116133 return function(opts)
117134 log.debug('not_before_regex')
118135 if length < 0 then
@@ -130,6 +147,7 @@ cond.not_after_regex = function(regex, length)
130147 if not regex then
131148 return cond.none()
132149 end
150+ ---@param opts CondOpts
133151 return function(opts)
134152 log.debug('not_after_regex')
135153 if length < 0 then
@@ -156,16 +174,41 @@ local function count_bracket_char(line, prev_char, next_char)
156174 return count_prev_char, count_next_char
157175end
158176
177+-- Checks if bracket chars are balanced around specific postion.
178+---@param line string
179+---@param open_char string
180+---@param close_char string
181+---@param col integer position
182+local function is_brackets_balanced_around_position(line, open_char, close_char, col)
183+ local balance = 0
184+ for i = 1, #line, 1 do
185+ local c = line:sub(i, i)
186+ if c == open_char then
187+ balance = balance + 1
188+ elseif balance > 0 and c == close_char then
189+ balance = balance - 1
190+ if col <= i and balance == 0 then
191+ break
192+ end
193+ end
194+ end
195+ return balance == 0
196+end
197+
159198cond.is_bracket_line = function()
199+ ---@param opts CondOpts
160200 return function(opts)
161201 log.debug('is_bracket_line')
162− if utils.is_bracket(opts.char) and opts.next_char == opts.rule.end_pair then
202+ if utils.is_bracket(opts.char) and
203+ (opts.next_char == opts.rule.end_pair
204+ or opts.next_char == opts.rule.start_pair)
205+ then
163206 -- (( many char |)) => add
164207 -- ( many char |)) => not add
165208 local count_prev_char, count_next_char = count_bracket_char(
166209 opts.line,
167− opts.char,
168− opts.next_char
210+ opts.rule.start_pair,
211+ opts.rule.end_pair
169212 )
170213 if count_prev_char ~= count_next_char then
171214 return false
@@ -175,26 +218,27 @@ cond.is_bracket_line = function()
175218end
176219
177220cond.is_bracket_line_move = function()
221+ ---@param opts CondOpts
178222 return function(opts)
179223 log.debug('is_bracket_line_move')
180− if
181− utils.is_close_bracket(opts.char)
224+ if utils.is_close_bracket(opts.char)
182225 and opts.char == opts.rule.end_pair
183226 then
227+ -- (( many char |)) => move
184228 -- (( many char |) => not move
185− local count_prev_char, count_next_char = count_bracket_char(
229+ local is_balanced = is_brackets_balanced_around_position(
186230 opts.line,
187231 opts.rule.start_pair,
188− opts.char
232+ opts.char,
233+ opts.col
189234 )
190− if count_prev_char ~= 0 and count_prev_char > count_next_char then
191− return false
192− end
235+ return is_balanced
193236 end
194237 end
195238end
196239
197240cond.not_inside_quote = function()
241+ ---@param opts CondOpts
198242 return function(opts)
199243 log.debug('not_inside_quote')
200244 if utils.is_in_quotes(opts.text, opts.col - 1) then
@@ -204,6 +248,7 @@ cond.not_inside_quote = function()
204248end
205249
206250cond.is_inside_quote = function()
251+ ---@param opts CondOpts
207252 return function(opts)
208253 log.debug('is_inside_quote')
209254 if utils.is_in_quotes(opts.text, opts.col - 1) then
@@ -213,10 +258,10 @@ cond.is_inside_quote = function()
213258end
214259
215260cond.not_add_quote_inside_quote = function()
261+ ---@param opts CondOpts
216262 return function(opts)
217263 log.debug('not_add_quote_inside_quote')
218− if
219− utils.is_quote(opts.char)
264+ if utils.is_quote(opts.char)
220265 and utils.is_in_quotes(opts.text, opts.col - 1)
221266 then
222267 return false
@@ -225,6 +270,7 @@ cond.not_add_quote_inside_quote = function()
225270end
226271
227272cond.move_right = function()
273+ ---@param opts CondOpts
228274 return function(opts)
229275 log.debug('move_right')
230276 if opts.next_char == opts.char then
@@ -249,6 +295,7 @@ cond.move_right = function()
249295end
250296
251297cond.is_end_line = function()
298+ ---@param opts CondOpts
252299 return function(opts)
253300 log.debug('is_end_line')
254301 local end_text = opts.line:sub(opts.col + 1)
@@ -261,10 +308,10 @@ end
261308
262309--- Check the next char is quote and cursor is inside quote
263310cond.is_bracket_in_quote = function()
311+ ---@param opts CondOpts
264312 return function(opts)
265313 log.debug("is_bracket_in_quote")
266− if
267− utils.is_bracket(opts.char)
314+ if utils.is_bracket(opts.char)
268315 and utils.is_quote(opts.next_char)
269316 and utils.is_in_quotes(opts.line, opts.col - 1, opts.next_char)
270317 then
@@ -284,6 +331,22 @@ cond.not_filetypes = function(filetypes)
284331 end
285332end
286333
334+--- Check the character before the cursor is not equal
335+---@param char string character to compare
336+---@param index number the position of character before current curosr
337+cond.not_before_char = function(char, index)
338+ index = index or 1
339+ ---@param opts CondOpts
340+ return function(opts)
341+ log.debug('not_before_char')
342+ local match_char = #opts.line > index
343+ and opts.line:sub(#opts.line - index, #opts.line - index) or ''
344+ if match_char == char and match_char ~= "" then
345+ return false
346+ end
347+ end
348+end
349+
287350---@deprecated
288351cond.not_after_regex_check = cond.not_after_regex
289352---@deprecated
lua/nvim-autopairs/fastwrap.lua+4−12
@@ -56,7 +56,6 @@ M.show = function(line)
5656 local index = 1
5757 local str_length = #line
5858 local offset = -1
59− local is_end_key = true
6059 for i = col + 2, #line, 1 do
6160 local char = line:sub(i, i)
6261 local char2 = line:sub(i - 1, i)
@@ -74,11 +73,6 @@ M.show = function(line)
7473 offset = 0
7574 end
7675
77− if i == str_length then
78− is_end_key = false
79− key = config.end_key
80− offset = 0
81− end
8276 table.insert(
8377 list_pos,
8478 { col = i + offset, key = key, char = char, pos = i }
@@ -86,12 +80,10 @@ M.show = function(line)
8680 end
8781 end
8882
89− if is_end_key then
90− table.insert(
91− list_pos,
92− { col = str_length + 1, key = config.end_key, pos = str_length + 1 }
93− )
94− end
83+ table.insert(
84+ list_pos,
85+ { col = str_length + 1, key = config.end_key, pos = str_length + 1 }
86+ )
9587
9688 M.highlight_wrap(list_pos, row, col, #line)
9789 vim.defer_fn(function()
lua/nvim-autopairs/rule.lua+1−5
@@ -169,11 +169,7 @@ end
169169---@return Rule
170170function Rule:with_pair(cond, pos)
171171 if self.pair_cond == nil then self.pair_cond = {} end
172− if pos then
173− table.insert(self.pair_cond, pos, cond)
174− else
175− table.insert(self.pair_cond, cond)
176− end
172+ self.pair_cond[pos or (#self.pair_cond+1)] = cond
177173 return self
178174end
179175
lua/nvim-autopairs/rules/basic.lua+10−4
@@ -36,24 +36,30 @@ local function setup(opt)
3636 Rule("```.*$", "```", { 'markdown', 'vimwiki', 'rmarkdown', 'rmd', 'pandoc' })
3737 :only_cr()
3838 :use_regex(true),
39− Rule('"""', '"""', { 'python', 'elixir', 'julia', 'kotlin' }),
39+ Rule('"""', '"""', { 'python', 'elixir', 'julia', 'kotlin' })
40+ :with_pair(cond.not_before_char('"', 3)),
41+ Rule("'''", "'''", { 'python' })
42+ :with_pair(cond.not_before_char('"', 3)),
4043 basic("'", "'", '-rust')
4144 :with_pair(cond.not_before_regex("%w")),
4245 basic("'", "'", 'rust')
4346 :with_pair(cond.not_before_regex("[%w<&]"))
4447 :with_pair(cond.not_after_text(">")),
4548 basic("`", "`"),
46− basic('"', '"','-vim'),
47− basic('"', '"','vim')
49+ basic('"', '"', '-vim'),
50+ basic('"', '"', 'vim')
4851 :with_pair(cond.not_before_regex("^%s*$", -1)),
4952 bracket("(", ")"),
5053 bracket("[", "]"),
5154 bracket("{", "}"),
5255 Rule(">[%w%s]*$", "^%s*</",
53− { 'html', 'typescript', 'typescriptreact', 'javascript' , 'javascriptreact', 'svelte', 'vue', 'xml', 'rescript'})
56+ { 'html', 'typescript', 'typescriptreact', 'javascript', 'javascriptreact', 'svelte', 'vue', 'xml',
57+ 'rescript' })
5458 :only_cr()
5559 :use_regex(true)
5660 }
5761 return rules
5862end
63+
5964return { setup = setup }
65+
lua/nvim-autopairs/utils.lua+3−2
@@ -11,7 +11,8 @@ M.key = {
1111 join_left = "<c-g>U<left>",
1212 join_right = "<c-g>U<right>",
1313 undo_sequence = "<c-g>u",
14− noundo_sequence = "<c-g>U"
14+ noundo_sequence = "<c-g>U",
15+ abbr = "<c-]>"
1516}
1617
1718M.set_vchar = function(text)
@@ -73,7 +74,7 @@ M.is_in_quotes = function (line, pos, quote_type)
7374end
7475
7576M.is_attached = function(bufnr)
76− local _, check = pcall(api.nvim_buf_get_var, bufnr, "nvim-autopairs")
77+ local _, check = pcall(api.nvim_buf_get_var, bufnr or 0, "nvim-autopairs")
7778 return check == 1
7879end
7980
tests/nvim-autopairs_spec.lua+62−1
@@ -1,4 +1,3 @@
1−local helpers = {}
21local npairs = require('nvim-autopairs')
32local Rule = require('nvim-autopairs.rule')
43local cond = require('nvim-autopairs.conds')
@@ -50,6 +49,13 @@ local data = {
5049 before = [[""| ]],
5150 after = [["""|""" ]]
5251 },
52+ {
53+ name = "don't repeat python quote" ,
54+ filetype = "python",
55+ key = [["]],
56+ before = [[a"""|""" ]],
57+ after = [[a""""|"" ]]
58+ },
5359
5460 {
5561 name = "add markdown quote" ,
@@ -237,6 +243,43 @@ local data = {
237243 "]"
238244 }
239245 },
246+ {
247+ name = "move ) inside nested function call" ,
248+ filetype="javascript",
249+ key = [[)]],
250+ before = {
251+ "fn(fn(|))",
252+ },
253+ after = {
254+ "fn(fn()|)",
255+ }
256+ },
257+ {
258+ name = "move } inside singleline function's params" ,
259+ filetype="javascript",
260+ key = [[}]],
261+ before = {
262+ "({|}) => {}",
263+ },
264+ after = {
265+ "({}|) => {}",
266+ }
267+ },
268+ {
269+ name = "move } inside multiline function's params" ,
270+ filetype="javascript",
271+ key = [[}]],
272+ before = {
273+ "({|}) => {",
274+ "",
275+ "}",
276+ },
277+ after = {
278+ "({}|) => {",
279+ "",
280+ "}",
281+ }
282+ },
240283 {
241284 name = "breakline on markdown " ,
242285 filetype="markdown",
@@ -645,6 +688,24 @@ local data = {
645688 [[}]],
646689 },
647690 },
691+ {
692+ name='open bracker on back tick',
693+ key = [[(]],
694+ before = [[ |`abcd`]],
695+ after = [[ (`abcd`) ]]
696+ },
697+ {
698+ name='should not add bracket on line have bracket ',
699+ key = [[(]],
700+ before = [[ |(abcd))]],
701+ after = [[ ((abcd)) ]]
702+ },
703+ {
704+ name='not add bracket on line have bracket ',
705+ key = [[(]],
706+ before = [[ |(abcd) ( visual)]],
707+ after = [[ ()(abcd) ( visual)]]
708+ }
648709}
649710
650711local run_data = _G.Test_filter(data)