From 4d0060f22b99c28f12093c22cdb28b1b44127d02 Mon Sep 17 00:00:00 2001 From: Israel Chauca Fuentes Date: Thu, 9 Feb 2017 12:59:33 -0500 Subject: [PATCH] Add support for quotes and a couple of other things. --- autoload/delimitMate.vim | 70 ++++++++++++++++++- plugin/delimitMate.vim | 9 +-- test/_setup.vim | 10 ++- test/autoclose_matchpairs.vim | 2 +- test/autoclose_quotes.vim | 125 +++++++++++++--------------------- 5 files changed, 127 insertions(+), 89 deletions(-) diff --git a/autoload/delimitMate.vim b/autoload/delimitMate.vim index e13221b..3e1ce16 100644 --- a/autoload/delimitMate.vim +++ b/autoload/delimitMate.vim @@ -7,6 +7,10 @@ let s:defaults.delimitMate_expand_space = 0 let s:defaults.delimitMate_smart_pairs = 1 let s:defaults.delimitMate_smart_pairs_extra = [] let s:defaults.delimitMate_balance_pairs = 0 +let s:defaults.delimitMate_nesting_quotes = [] +let s:defaults.delimitMate_smart_quotes = 1 +let s:defaults.delimitMate_smart_quotes_extra = [] +let s:defaults.delimitMate_excluded_regions = ['String', 'Comment'] " Set smart_pairs expressions: let s:exprs = [] @@ -15,6 +19,19 @@ call add(s:exprs, 'next_char =~# "[".escape(v:char,"\\^]")."€£$]"') call add(s:exprs, 'ahead =~# "^[^[:space:][:punct:]]"') let s:defaults.delimitMate_smart_pairs_base = s:exprs +" Set smart_quotes expressions: +let s:exprs = [] +call add(s:exprs, 'prev_char =~# "\\w"') +call add(s:exprs, 'prev_char =~# "[^[:space:][:punct]".escape(join(options.quotes, ""), "\\^[]")."]"') +call add(s:exprs, 'next_char =~# "\\w"') +call add(s:exprs, 'next_char =~# "[^[:space:][:punct]".escape(join(options.quotes, ""), "\\^[]")."]"') +" Balance quotes +call add(s:exprs, 'strchars(substitute(substitute(a:info.cur.text, "\\\\.", "", "g"), "[^".escape(char, "\\^[]")."]", "", "g")) % 2') + +let s:defaults.delimitMate_smart_quotes_base = s:exprs + +unlet s:exprs + function! s:defaults.consolidate() let g = filter(copy(g:), 'v:key =~# "^delimitMate_"') let b = filter(copy(b:), 'v:key =~# "^delimitMate_"') @@ -70,6 +87,14 @@ function! s:option(name, ...) return opt endfunction +function! delimitMate#option(name) + return s:option(a:name) +endfunction + +function! s:synstack(lnum, col) + return map(synstack(a:lnum, a:col), 'synIDattr(v:val, "name")') + [synIDattr(synIDtrans(synID(line('.'), col('.'), 1)), 'name')] +endfunction + function! delimitMate#ex_cmd(global, action) let scope = a:global ? g: : b: if a:action ==# 'enable' @@ -83,6 +108,7 @@ endfunction function! delimitMate#InsertCharPre(str) if s:info.skip_icp + " iabbrev fires this event for every char and the trigger echom 11 return 0 endif @@ -91,6 +117,10 @@ function! delimitMate#InsertCharPre(str) echom 12 return 0 endif + if !empty(filter(s:option('excluded_regions'), 'index(s:synstack(line("."), col(".")), v:val) >= 0')) + echom 13 + return 0 + endif return map(split(a:str, '\zs'), 's:handle_vchar(v:val)') endfunction @@ -109,7 +139,7 @@ function! s:handle_vchar(str) return 0 elseif !empty(filter(copy(opts.quotes), 'v:val ==# a:str')) echom 15 - return 0 + let keys = s:keys4quote(a:str, s:info, opts) elseif !empty(filter(copy(opts.pairs), 'strcharpart(v:val, 0, 1) ==# a:str')) echom 16 let pair = get(filter(copy(opts.pairs), 'strcharpart(v:val, 0, 1) ==# a:str'), 0, '') @@ -169,6 +199,40 @@ function! s:keys4right(char, pair, info, opts) return '' endfunction +function! s:keys4quote(char, info, opts) + let quotes_behind = strchars(matchstr(a:info.cur.behind, '['.escape(a:char, '\^[]').']*$')) + let quotes_ahead = strchars(matchstr(a:info.cur.ahead, '^['.escape(a:char, '\^[]').']*')) + echom 'k4q: ' quotes_behind . ' - ' . quotes_ahead + echom string(a:opts.nesting_quotes) + if a:opts.autoclose && index(a:opts.nesting_quotes, a:char) >= 0 + \&& quotes_behind > 1 + let add2right = quotes_ahead > quotes_behind + 1 ? 0 : quotes_behind - quotes_ahead + 1 + echom 51 + echom add2right + return repeat(a:char, add2right) . repeat("\U\", add2right) + endif + if a:info.cur.n_char ==# a:char + echom 53 + return "\" + endif + let exprs = a:opts.smart_quotes_base + a:opts.smart_quotes_extra + if a:opts.autoclose && a:opts.smart_quotes + \&& s:any_is_true(exprs, a:info, a:opts) + echom 52 + return '' + endif + if !a:opts.autoclose && quotes_behind + echom 54 + return "\" + endif + if !a:opts.autoclose + echom 55 + return '' + endif + echom 59 + return a:char . "\U\" +endfunction + function! s:get_info(...) if a:0 let d = a:1 @@ -189,6 +253,7 @@ function! s:get_info(...) endfunction function! s:any_is_true(expressions, info, options) + let char = a:info.char let info = deepcopy(a:info) let options = deepcopy(a:options) let line = info.cur.text @@ -248,7 +313,8 @@ function! s:is_bs() return feedkeys("\", 'tni') endif let pair = filter(s:option('pairs'), 'v:val ==# s:info.prev.around') - if empty(pair) + let quote = filter(s:option('quotes'), 'v:val . v:val ==# s:info.prev.around') + if empty(pair) && empty(quote) echom 26 return endif diff --git a/plugin/delimitMate.vim b/plugin/delimitMate.vim index 56acc30..8f1f45c 100644 --- a/plugin/delimitMate.vim +++ b/plugin/delimitMate.vim @@ -1,17 +1,10 @@ -if exists("g:loaded_delimitMate") || &cp +if exists("g:loaded_delimitMate") || &cp || v:version < 800 finish endif let g:loaded_delimitMate = 1 let save_cpo = &cpo set cpo&vim -if v:version < 800 - echohl ErrorMsg - echom "delimitMate: this plugin requires vim 8.0 or later!" - echohl None - finish -endif - command! -bar -bang DelimitMateSwitch call delimitMate#ex_cmd(0,'switch') command! -bar -bang DelimitMateOn call delimitMate#ex_cmd(0,'enable') command! -bar -bang DelimitMateOff call delimitMate#ex_cmd(0,'disable') diff --git a/test/_setup.vim b/test/_setup.vim index 6dc05c4..b6f2af3 100644 --- a/test/_setup.vim +++ b/test/_setup.vim @@ -37,6 +37,9 @@ endfunction "function! DMTest_single(setup, typed, expected[, skip_expr[, todo_expr]]) " Runs a single test (add 1 to vimtap#Plan()) function! DMTest_single(setup, typed, expected, ...) + if type(a:typed) != v:t_list + return vimtap#Fail('Second argument should be a list: ' . a:typed) + end if type(a:setup) == v:t_list let setup = copy(a:setup) else @@ -67,6 +70,9 @@ function! DMTest_single(setup, typed, expected, ...) endfunction function! s:do_set(pat, sub, set, setup, typed, expected, ...) + if type(a:typed) != v:t_list + return vimtap#Fail('Second argument should be a list: ' . string(a:typed)) + end let skip_expr = get(a:, '1', '') let todo_expr = get(a:, '2', '') let escaped = '\.*^$' @@ -102,7 +108,7 @@ endfunction function! DMTest_pairs(setup, typed, expected, ...) let skip_expr = get(a:, '1', '') let todo_expr = get(a:, '2', '') - let pairs = ['()','{}','[]','<>','¿?','¡!',',:'] "delimitMate#options('pairs') + let pairs = delimitMate#option('pairs') let pat = '[()]' let sub = '\=submatch(0) == "(" ? left : right' return s:do_set(pat, sub, pairs, a:setup, a:typed, a:expected, skip_expr, todo_expr) @@ -113,7 +119,7 @@ endfunction function! DMTest_quotes(setup, typed, expected, ...) let skip_expr = get(a:, '1', '') let todo_expr = get(a:, '2', '') - let quotes = ['"', "'", '`', '«', '|'] "delimitMate#options('quotes') + let quotes = delimitMate#option('quotes') let pat = "'" let sub = 'quote' return s:do_set(pat, sub, quotes, a:setup, a:typed, a:expected, skip_expr, todo_expr) diff --git a/test/autoclose_matchpairs.vim b/test/autoclose_matchpairs.vim index c5d6d69..56c802e 100644 --- a/test/autoclose_matchpairs.vim +++ b/test/autoclose_matchpairs.vim @@ -11,7 +11,7 @@ " - Add 5 to vimtap#Plan(). call vimtest#StartTap() -call vimtap#Plan(224) +call vimtap#Plan(210) let g:delimitMate_matchpairs = '(:),{:},[:],<:>,¿:?,¡:!,,::' let g:delimitMate_autoclose = 1 diff --git a/test/autoclose_quotes.vim b/test/autoclose_quotes.vim index 69ef615..0bd8d7f 100644 --- a/test/autoclose_quotes.vim +++ b/test/autoclose_quotes.vim @@ -11,94 +11,67 @@ " - Add 5 to vimtap#Plan(). call vimtest#StartTap() -call vimtap#Plan(230) +call vimtap#Plan(140) -let g:delimitMate_quotes = '" '' ` « |' let g:delimitMate_autoclose = 1 -DelimitMateReload -call DMTest_quotes('', "'x", "'x'") -call DMTest_quotes('', "'x\u", "") -call DMTest_quotes('', "''x", "''x") -call DMTest_quotes('', "'\x", "x") -call DMTest_quotes('', "'\gx", "''x") +call DMTest_quotes('', ["i'", "ax"], "'x'") +call DMTest_quotes('', ["i'x", "u"], "") +call DMTest_quotes('', ["i'", "a'", "ax"], "''x", 'a:typed[0] == "i«"') +call DMTest_quotes('', ["a'", "a\", "ax"], "x") +"call DMTest_quotes('', "'\gx", "''x") " This will fail for double quote. -call DMTest_quotes('', "'\"x", "'\"x\"'", "a:typed == '\"\"x'") -call DMTest_quotes('', "@'x", "@'x'") -call DMTest_quotes('', "@#\'x", "@'x'#") -call DMTest_quotes('', "'\x", "''x") -call DMTest_quotes('', "abc'", "abc'") -call DMTest_quotes('', "abc\\'x", "abc\\'x") -call DMTest_quotes('', "u'Привет'", "u'Привет'") -call DMTest_quotes('', "u'string'", "u'string'") +call DMTest_quotes('', ["i'", "a\"", "ax"], "'\"x\"'", 'a:typed[0] =~ "i[\"«]"') +call DMTest_quotes('', ["i@", "a'", "ax"], "@'x'") +call DMTest_quotes('', ["i@#", "i'", "ax"], "@'x'#") +"call DMTest_quotes('', "'\x", "''x") +call DMTest_quotes('', ["iabc", "a'"], "abc'") +call DMTest_quotes('abc\', ["A'", "ax"], "abc\\'x") +" TODO find out why this test doesn't work when it does interactively. +call DMTest_quotes('', ["au", "a'", "aПривет", "a'"], "u'Привет'", '', 1) +call DMTest_quotes('', ["au", "a'", "astring", "a'"], "u'string'") let g:delimitMate_autoclose = 0 -DelimitMateReload -call DMTest_quotes('', "'x", "'x") -call DMTest_quotes('', "''x", "'x'") -call DMTest_quotes('', "'''x", "''x") -call DMTest_quotes('', "''\x", "x") -call DMTest_quotes('', "@''x", "@'x'") -call DMTest_quotes('', "@#\''x", "@'x'#") -let g:delimitMate_expand_space = 1 +call DMTest_quotes('', ["a'", "ax"], "'x") +call DMTest_quotes('', ["a'", "a'", "ax"], "'x'") +call DMTest_quotes('', ["a'", "a'", "a'", "ax"], "''x") +call DMTest_quotes('', ["a'", "a'", "a\", "ax"], "x") +call DMTest_quotes('', ["a@", "a'", "a'", "ax"], "@'x'") +call DMTest_quotes('', ["a@#", "i'", "a'", "ax"], "@'x'#") let g:delimitMate_autoclose = 1 -DelimitMateReload -call DMTest_quotes('', "'\x", "' x'") -let g:delimitMate_expand_inside_quotes = 1 -DelimitMateReload -call DMTest_quotes('', "'\x", "' x '") -call DMTest_quotes('', "'\\x", "'x'") -call DMTest_quotes('', "abc\\''\x", "abc\\' x'") -let g:delimitMate_autoclose = 0 -DelimitMateReload -call DMTest_quotes('', "''\\x", "'x'") -let g:delimitMate_autoclose = 1 -DelimitMateReload +"let g:delimitMate_expand_space = 1 +"call DMTest_quotes('', "'\x", "' x'") +"let g:delimitMate_expand_inside_quotes = 1 +"call DMTest_quotes('', "'\x", "' x '") +"call DMTest_quotes('', "'\\x", "'x'") +"call DMTest_quotes('', "abc\\''\x", "abc\\' x'") +"let g:delimitMate_autoclose = 0 +"call DMTest_quotes('', "''\\x", "'x'") +"let g:delimitMate_autoclose = 1 " Handle backspace gracefully. set backspace= -call DMTest_quotes('', "'\a\x", "'x'") +call DMTest_quotes('', ["a'", "a\", "ax"], "'x'") set backspace=2 -set cpo=ces$ -call DMTest_quotes('', "'x", "'x'") +"set cpo=ces$ +"call DMTest_quotes('', "'x", "'x'") " Make sure smart quote works beyond first column. -call DMTest_quotes('', " 'x", " 'x'") +call DMTest_quotes(' ', ["a'", "ax"], " 'x'") " smart quote, check fo char on the right. -call DMTest_quotes('', "a\b\'", "a 'b") +call DMTest_quotes('a b', ["la'"], "a 'b") " Make sure we jump over a quote on the right. #89. -call DMTest_quotes('', "('test'x", "('test'x)") +call DMTest_quotes('', ["a(", "a'", "atest", "a'", "ax"], "('test'x)") " Duplicate whole line when inserting quote at bol #105 -call DMTest_quotes('', "}\'", "''}") -call DMTest_quotes('', "'\abc '", "'abc '") -call DMTest_quotes('', "''abc '", "''abc ''") -" Nesting quotes: -let g:delimitMate_nesting_quotes = split(g:delimitMate_quotes, '\s\+') -DelimitMateReload -call DMTest_quotes('', "'''x", "'''x'''") -call DMTest_quotes('', "''''x", "''''x''''") -call DMTest_quotes('', "''x", "''x") -call DMTest_quotes('', "'x", "'x'") +call DMTest_quotes('}', ["i'"], "''}") +call DMTest_quotes("'abc ", ["A'"], "'abc '") +call DMTest_quotes("''abc ", ["A'"], "''abc ''") +"" Nesting quotes: +let g:delimitMate_nesting_quotes = delimitMate#option('quotes') +call DMTest_quotes("'' ", ["la'\", "ix"], "'''x''' ") +call DMTest_quotes("''' ", ["lla'\", "ix"], "''''x'''' ") +call DMTest_quotes(' ', ["i'", "a'\", "ix"], "''x ") +call DMTest_quotes('', ["i'", "ax"], "'x'") unlet g:delimitMate_nesting_quotes -DelimitMateReload -" expand iabbreviations -iabb def ghi -call DMTest_quotes('', "def'", "ghi'") -let g:delimitMate_smart_quotes = '\w\%#\_.' -DelimitMateReload -call DMTest_quotes('', "xyz'x", "xyz'x") -call DMTest_quotes('', "xyz 'x", "xyz 'x'") -let g:delimitMate_smart_quotes = '\s\%#\_.' -DelimitMateReload -call DMTest_quotes('', "abc'x", "abc'x'") -call DMTest_quotes('', "abc 'x", "abc 'x") -" let's try the negated form -let g:delimitMate_smart_quotes = '!\w\%#\_.' -DelimitMateReload -call DMTest_quotes('', "cba'x", "cba'x'") -call DMTest_quotes('', "cba 'x", "cba 'x") -let g:delimitMate_smart_quotes = '!\s\%#\_.' -DelimitMateReload -call DMTest_quotes('', "zyx'x", "zyx'x") -call DMTest_quotes('', "zyx 'x", "zyx 'x'") -unlet g:delimitMate_smart_quotes -DelimitMateReload -call DMTest_quotes('', "'\\", "''") +"" expand iabbreviations +"iabb def ghi +"call DMTest_quotes('', "def'", "ghi'") +"call DMTest_quotes('', "'\\", "''") call vimtest#Quit()