From 490a24daf028549358a13067fcd72c5bbec1af52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Yngve=20Lerv=C3=A5g?= Date: Sat, 6 Feb 2016 20:21:27 +0100 Subject: [PATCH] A huge refactoring of the delimiter engine See entry on 2016-02-06 in the changelog in doc/vimtex.txt, :h vimtex-changelog, for more details. Issues: #258 #314 #316 #329 --- README.md | 2 +- autoload/vimtex.vim | 20 +- autoload/vimtex/change.vim | 483 -------------------------- autoload/vimtex/cmd.vim | 190 +++++++++++ autoload/vimtex/delim.vim | 640 +++++++++++++++++++++++++++++++++++ autoload/vimtex/env.vim | 135 ++++++++ autoload/vimtex/motion.vim | 150 ++------ autoload/vimtex/text_obj.vim | 150 +++----- autoload/vimtex/util.vim | 142 -------- doc/vimtex.txt | 175 +++++----- indent/tex.vim | 31 +- 11 files changed, 1141 insertions(+), 977 deletions(-) delete mode 100644 autoload/vimtex/change.vim create mode 100644 autoload/vimtex/cmd.vim create mode 100644 autoload/vimtex/delim.vim create mode 100644 autoload/vimtex/env.vim diff --git a/README.md b/README.md index 1bc553f..0c66b2c 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ disabled if desired. - Change the surrounding command or environment with `csc`/`cse` - Toggle starred environment with `tse` - Toggle between e.g. `()` and `\left(\right)` with `tsd` - - Close the current environment in insert mode with `]]` + - Close the current environment/delimiter in insert mode with `]]` - Insert new command with `` - Convenient insert mode mappings for faster typing of e.g. maths - Improved folding (`:h 'foldexpr'`) diff --git a/autoload/vimtex.vim b/autoload/vimtex.vim index c73e743..f1fcea1 100644 --- a/autoload/vimtex.vim +++ b/autoload/vimtex.vim @@ -341,15 +341,17 @@ function! s:init_mappings() " {{{1 call s:map('n', 'lx', '(vimtex-reload)') call s:map('n', 'ls', '(vimtex-toggle-main)') - call s:map('n', 'dse', '(vimtex-delete-env)') - call s:map('n', 'dsc', '(vimtex-delete-cmd)') - call s:map('n', 'cse', '(vimtex-change-env)') - call s:map('n', 'csc', '(vimtex-change-cmd)') - call s:map('n', 'tse', '(vimtex-toggle-star)') - call s:map('n', 'tsd', '(vimtex-toggle-delim)') - call s:map('n', '', '(vimtex-create-cmd)') - call s:map('i', '', '(vimtex-create-cmd)') - call s:map('i', ']]', '(vimtex-close-env)') + call s:map('n', 'dse', '(vimtex-env-delete)') + call s:map('n', 'cse', '(vimtex-env-change)') + call s:map('n', 'tse', '(vimtex-env-toggle-star)') + + call s:map('n', 'dsc', '(vimtex-cmd-delete)') + call s:map('n', 'csc', '(vimtex-cmd-change)') + call s:map('n', '', '(vimtex-cmd-create)') + call s:map('i', '', '(vimtex-cmd-create)') + + call s:map('n', 'tsd', '(vimtex-delim-toggle-modifier)') + call s:map('i', ']]', '(vimtex-delim-close)') if g:vimtex_latexmk_enabled call s:map('n', 'll', '(vimtex-compile-toggle)') diff --git a/autoload/vimtex/change.vim b/autoload/vimtex/change.vim deleted file mode 100644 index 37d15a1..0000000 --- a/autoload/vimtex/change.vim +++ /dev/null @@ -1,483 +0,0 @@ -" vimtex - LaTeX plugin for Vim -" -" Maintainer: Karl Yngve Lervåg -" Email: karl.yngve@gmail.com -" - -function! vimtex#change#init_options() " {{{1 - call vimtex#util#set_default('g:vimtex_change_complete_envs', [ - \ 'itemize', - \ 'enumerate', - \ 'description', - \ 'center', - \ 'figure', - \ 'table', - \ 'equation', - \ 'multline', - \ 'align', - \ 'split', - \ '\[', - \ ]) - call vimtex#util#set_default('g:vimtex_change_toggled_delims', - \ [['\\left', '\\right']]) - call vimtex#util#set_default('g:vimtex_change_ignored_delims_pattern', - \ '\c\\bigg\?') -endfunction - -" }}}1 -function! vimtex#change#init_script() " {{{1 -endfunction - -" }}}1 -function! vimtex#change#init_buffer() " {{{1 - nnoremap (vimtex-delete-env) - \ :call vimtex#change#env('') - - nnoremap (vimtex-delete-cmd) - \ :call vimtex#change#command_delete() - - nnoremap (vimtex-change-env) - \ :call vimtex#change#env_prompt() - - nnoremap (vimtex-change-cmd) - \ :call vimtex#change#command() - - nnoremap (vimtex-toggle-star) - \ :call vimtex#change#toggle_env_star() - - nnoremap (vimtex-toggle-delim) - \ :call vimtex#change#toggle_delim() - - nnoremap (vimtex-create-cmd) - \ :call vimtex#change#to_command()i - - inoremap (vimtex-create-cmd) - \ =vimtex#change#to_command() - - inoremap (vimtex-close-env) - \ =vimtex#change#close_environment() -endfunction - -" }}}1 - -function! vimtex#change#get_command(...) " {{{1 - let l:position = a:0 > 0 ? a:1 : searchpos('\S', 'bcn') - let l:line = getline(l:position[0]) - let l:char = l:line[l:position[1]-1] - - " Lists of relevant syntax regions - let l:commands = [ - \ 'texStatement', - \ 'texTypeSize', - \ 'texTypeStyle', - \ 'texBeginEnd', - \ ] - let l:argument = [ - \ 'texMatcher', - \ 'texItalStyle', - \ 'texRefZone', - \ 'texBeginEndName', - \ ] - - for l:syntax in reverse(map(call('synstack', l:position), - \ 'synIDattr(v:val, ''name'')')) - if index(l:commands, l:syntax) >= 0 - let l:p = searchpos('\\', 'bcn') - let l:c = matchstr(l:line, '\\\zs\w\+', l:p[1]-1) - return [l:c] + l:p - elseif index(l:argument, l:syntax) >= 0 - \ || (l:syntax ==# 'Delimiter' && l:char =~# '{\|}') - let l:curpos = exists('*getcurpos') ? getcurpos() : getpos('.') - keepjumps normal! vaBoh - let l:result = vimtex#change#get_command(searchpos('\S', 'bcn')) - call setpos('.', l:curpos) - return l:result - endif - endfor - - return ['', 0, 0] -endfunction - -function! vimtex#change#command() " {{{1 - " Get old command - let [l:old, l:line, l:col] = vimtex#change#get_command() - if l:old ==# '' | return | endif - - " Get new command - let l:new = input('Change ' . old . ' for: ') - let l:new = empty(l:new) ? l:old : l:new - - " Store current cursor position - let l:curpos = exists('*getcurpos') ? getcurpos() : getpos('.') - if l:line == l:curpos[1] - let l:curpos[2] += len(l:new) - len(l:old) - endif - - " This is a hack to make undo restore the correct position - normal! ix - normal! x - - " Perform the change - let l:tmppos = copy(l:curpos) - let l:tmppos[1:2] = [l:line, l:col+1] - cal setpos('.', l:tmppos) - let l:savereg = @a - let @a = l:new - normal! cea - let @a = l:savereg - - " Restore cursor position and create repeat hook - call setpos('.', l:curpos) - silent! call repeat#set("\(vimtex-change-cmd)" . new . ' ', v:count) -endfunction - -function! vimtex#change#command_delete() " {{{1 - " Get old command - let [l:old, l:line, l:col] = vimtex#change#get_command() - if l:old ==# '' | return | endif - - " Store current cursor position - let l:curpos = exists('*getcurpos') ? getcurpos() : getpos('.') - if l:line == l:curpos[1] - let l:curpos[2] -= len(l:old)+1 - endif - - " Save selection - let l:vstart = [l:curpos[0], line("'<"), col("'<"), l:curpos[3]] - let l:vstop = [l:curpos[0], line("'<"), col("'>"), l:curpos[3]] - - " This is a hack to make undo restore the correct position - normal! ix - normal! x - - " Use temporary cursor position - let l:tmppos = copy(l:curpos) - let l:tmppos[1:2] = [l:line, l:col] - call setpos('.', l:tmppos) - normal! de - - " Delete surrounding braces if present - if getline('.')[l:col-1 :] =~# '^\s*{' - call searchpos('{', 'c') - keepjumps normal! vaBomzoxg`zx - if l:line == l:curpos[1] - let l:curpos[2] -= 1 - if l:curpos[2] < 0 - let l:curpos[2] = 0 - endif - endif - endif - - " Restore cursor position and visual selection - call setpos('.', l:curpos) - call setpos("'<", l:vstart) - call setpos("'>", l:vstop) - - " Create repeat hook - silent! call repeat#set("\(vimtex-delete-cmd)", v:count) -endfunction - -function! vimtex#change#close_environment() " {{{1 - " Get environment and delimiter info - let [env, env_l1, env_c1, env_l2, env_c2] = vimtex#util#get_env(1,0) - let [del_l1, del_c1] = searchpairpos('\C\\left\>', '', '\C\\right\>', 'bnW', - \ 'vimtex#util#in_comment()') - - " Calculate scores - let env_score = env_l1*1000 + env_c1 - let del_score = del_l1*1000 + del_c1 - - " Complete environment or delimiter, if any - if env_score > del_score - if env ==# '\[' - return '\]' - elseif env ==# '\(' - return '\)' - elseif env !=# '' - return '\end{' . env . '}' - endif - elseif del_score > 0 - let line = strpart(getline(del_l1), del_c1 - 1) - let bracket = matchstr(line, '^\\left\zs\((\|\[\|\\{\||\|\.\)\ze') - for [open, close] in [ - \ ['(', ')'], - \ ['\[', '\]'], - \ ['\\{', '\\}'], - \ ['|', '|'], - \ ['\.', '|'], - \ ] - let bracket = substitute(bracket, open, close, 'g') - endfor - return '\right' . bracket - endif - - return '' -endfunction - -function! vimtex#change#delim(open, close) " {{{1 - let [d1, l1, c1, d2, l2, c2] = vimtex#util#get_delim() - - let line = getline(l1) - let line = strpart(line,0,c1 - 1) . a:open . strpart(line, c1 + len(d1) - 1) - call setline(l1, line) - - if l1 ==# l2 - let n = len(a:open) - len(d1) - let c2 += n - let pos = getpos('.') - let pos[2] += n - call setpos('.', pos) - endif - - let line = getline(l2) - let line = strpart(line,0,c2 - 1) . a:close . strpart(line, c2 + len(d2) - 1) - call setline(l2, line) -endfunction - -function! vimtex#change#env(new) " {{{1 - let [env, l1, c1, l2, c2] = vimtex#util#get_env(1) - - if a:new ==# '' - let beg = '' - let end = '' - elseif a:new ==# '\[' || a:new ==# '[' - let beg = '\[' - let end = '\]' - elseif a:new ==# '\(' || a:new ==# '(' - let beg = '\(' - let end = '\)' - else - let beg = '\begin{' . a:new . '}' - let end = '\end{' . a:new . '}' - endif - - let n1 = len(env) - 1 - let n2 = len(env) - 1 - if env !=# '\[' && env !=# '\(' - let n1 += 8 - let n2 += 6 - endif - - let line = getline(l1) - let line = strpart(line, 0, c1 - 1) . l:beg . strpart(line, c1 + n1) - call setline(l1, line) - let line = getline(l2) - let line = strpart(line, 0, c2 - 1) . l:end . strpart(line, c2 + n2) - call setline(l2, line) - - if a:new ==# '' - silent! call repeat#set("\(vimtex-delete-env)", v:count) - else - silent! call repeat#set( - \ "\(vimtex-change-env)" . a:new . ' ', v:count) - endif -endfunction - -function! vimtex#change#env_prompt() " {{{1 - let new_env = input('Change ' . vimtex#util#get_env() . ' for: ', '', - \ 'customlist,' . s:sidwrap('input_complete')) - if empty(new_env) - return - else - call vimtex#change#env(new_env) - endif -endfunction - -function! vimtex#change#to_command() " {{{1 - " Get current line - let line = getline('.') - - " Get cursor position - let pos = getpos('.') - - " Return if there is no word at cursor - if mode() ==# 'n' - let column = pos[2] - 1 - else - let column = pos[2] - 2 - endif - if column <= 1 || line[column] =~# '\s' - return '' - endif - - " Prepend a backslash to beginning of the current word - normal! B - let column = getpos('.')[2] - if line[column - 1] !=# '\' - let line = strpart(line, 0, column - 1) . '\' . strpart(line, column - 1) - call setline('.', line) - endif - - " Append opening braces to the end of the current word - normal! E - let column = getpos('.')[2] - let pos[2] = column + 1 - if line[column - 1] !=# '{' - let line = strpart(line, 0, column) . '{' . strpart(line, column) - call setline('.', line) - let pos[2] += 1 - endif - - " Restore cursor position - call setpos('.', pos) - return '' -endfunction - -function! vimtex#change#toggle_delim() " {{{1 - " - " Toggle \left and \right variants of delimiters - " - let [d1, l1, c1, d2, l2, c2] = vimtex#util#get_delim() - let [newd1, newd2] = s:toggle_delim_get_new(d1, d2) - if newd1 ==# '' | return 0 | endif - - let line = getline(l1) - let line = strpart(line, 0, c1 - 1) . newd1 . strpart(line, c1 + len(d1) - 1) - call setline(l1, line) - - if l1 ==# l2 - let n = len(newd1) - len(d1) - let c2 += n - let pos = getpos('.') - let pos[2] += n - call setpos('.', pos) - endif - - let line = getline(l2) - let line = strpart(line, 0, c2 - 1) . newd2 . strpart(line, c2 + len(d2) - 1) - call setline(l2, line) - - silent! call repeat#set("\(vimtex-toggle-delim)", v:count) -endfunction - -function! vimtex#change#toggle_env_star() " {{{1 - let env = vimtex#util#get_env() - - if env ==# '\(' - return - elseif env ==# '\[' - let new_env = equation - elseif env[-1:] ==# '*' - let new_env = env[:-2] - else - let new_env = env . '*' - endif - - call vimtex#change#env(new_env) - - silent! call repeat#set("\(vimtex-toggle-star)", v:count) -endfunction - - -function! vimtex#change#wrap_selection(wrapper) " {{{1 - keepjumps normal! `>a} - execute 'keepjumps normal! `o\end{' . env . '}' - execute 'keepjumps normal! ` - normal! gvgq - endif - else - execute 'keepjumps normal! `>a\end{' . env . '}' - execute 'keepjumps normal! `'), '\zs\d\+_\ze.*$') . a:func -endfunction - -function! s:input_complete(lead, cmdline, pos) " {{{1 - return filter(g:vimtex_change_complete_envs, 'v:val =~# ''^' . a:lead . '''') -endfunction - -function! s:search_and_skip_comments(pat, ...) " {{{1 - " Usage: s:search_and_skip_comments(pat, [flags, stopline]) - let flags = a:0 >= 1 ? a:1 : '' - let stopline = a:0 >= 2 ? a:2 : 0 - let saved_pos = getpos('.') - - " search once - let ret = search(a:pat, flags, stopline) - - if ret - " do not match at current position if inside comment - let flags = substitute(flags, 'c', '', 'g') - - " keep searching while in comment - while vimtex#util#in_comment() - let ret = search(a:pat, flags, stopline) - if !ret - break - endif - endwhile - endif - - if !ret - " if no match found, restore position - call setpos('.', saved_pos) - endif - - return ret -endfunction -" }}}1 -function! s:toggle_delim_get_new(d1,d2) " {{{1 - if a:d1 =~# g:vimtex_change_ignored_delims_pattern - return ['', ''] - endif - - let newd1 = '' - let newd2 = '' - let delim = g:vimtex_change_toggled_delims - - for i in range(len(delim)) - let d1_1 = type(delim[i]) == 3 ? delim[i][0] : delim[i] - let d2_1 = type(delim[i]) == 3 ? delim[i][1] : delim[i] - if a:d1 =~# d1_1 . '\>' - if i+1 == len(delim) - let newd1 = substitute(a:d1, d1_1, '', '') - let newd2 = substitute(a:d2, d2_1, '', '') - else - let d1_2 = type(delim[i+1]) == 3 ? delim[i+1][0] : delim[i+1] - let d2_2 = type(delim[i+1]) == 3 ? delim[i+1][1] : delim[i+1] - let newd1 = substitute(a:d1, d1_1, d1_2, '') - let newd2 = substitute(a:d2, d2_1, d2_2, '') - endif - break - endif - endfor - - if newd1 ==# '' - if len(a:d1) > 0 - let d1_1 = type(delim[0]) == 3 ? delim[0][0] : delim[0] - let d2_1 = type(delim[0]) == 3 ? delim[0][1] : delim[0] - let newd1 = substitute(a:d1, '^', d1_1, '') - let newd2 = substitute(a:d2, '^', d2_1, '') - endif - endif - - return [newd1, newd2] -endfunction - -" }}}1 - -" vim: fdm=marker sw=2 diff --git a/autoload/vimtex/cmd.vim b/autoload/vimtex/cmd.vim new file mode 100644 index 0000000..9f233f6 --- /dev/null +++ b/autoload/vimtex/cmd.vim @@ -0,0 +1,190 @@ +" vimtex - LaTeX plugin for Vim +" +" Maintainer: Karl Yngve Lervåg +" Email: karl.yngve@gmail.com +" + +function! vimtex#cmd#init_options() " {{{1 +endfunction + +" }}}1 +function! vimtex#cmd#init_script() " {{{1 +endfunction + +" }}}1 +function! vimtex#cmd#init_buffer() " {{{1 + nnoremap (vimtex-cmd-delete) + \ :call vimtex#cmd#delete() + + nnoremap (vimtex-cmd-change) + \ :call vimtex#cmd#change() + + nnoremap (vimtex-cmd-create) + \ :call vimtex#cmd#create()i + + inoremap (vimtex-cmd-create) + \ =vimtex#cmd#create() +endfunction + +" }}}1 + +function! vimtex#cmd#get_command(...) " {{{1 + let l:position = a:0 > 0 ? a:1 : searchpos('\S', 'bcn') + let l:line = getline(l:position[0]) + let l:char = l:line[l:position[1]-1] + + " Lists of relevant syntax regions + let l:commands = [ + \ 'texStatement', + \ 'texTypeSize', + \ 'texTypeStyle', + \ 'texBeginEnd', + \ ] + let l:argument = [ + \ 'texMatcher', + \ 'texItalStyle', + \ 'texRefZone', + \ 'texBeginEndName', + \ ] + + for l:syntax in reverse(map(call('synstack', l:position), + \ 'synIDattr(v:val, ''name'')')) + if index(l:commands, l:syntax) >= 0 + let l:p = searchpos('\\', 'bcn') + let l:c = matchstr(l:line, '\\\zs\w\+', l:p[1]-1) + return [l:c] + l:p + elseif index(l:argument, l:syntax) >= 0 + \ || (l:syntax ==# 'Delimiter' && l:char =~# '{\|}') + let l:curpos = exists('*getcurpos') ? getcurpos() : getpos('.') + keepjumps normal! vaBoh + let l:result = vimtex#cmd#get_command() + call setpos('.', l:curpos) + return l:result + endif + endfor + + return ['', 0, 0] +endfunction + +function! vimtex#cmd#change() " {{{1 + " Get old command + let [l:old, l:line, l:col] = vimtex#cmd#get_command() + if l:old ==# '' | return | endif + + " Get new command + let l:new = input('Change ' . old . ' for: ') + let l:new = empty(l:new) ? l:old : l:new + + " Store current cursor position + let l:curpos = exists('*getcurpos') ? getcurpos() : getpos('.') + if l:line == l:curpos[1] + let l:curpos[2] += len(l:new) - len(l:old) + endif + + " This is a hack to make undo restore the correct position + normal! ix + normal! x + + " Perform the change + let l:tmppos = copy(l:curpos) + let l:tmppos[1:2] = [l:line, l:col+1] + cal setpos('.', l:tmppos) + let l:savereg = @a + let @a = l:new + normal! cea + let @a = l:savereg + + " Restore cursor position and create repeat hook + call setpos('.', l:curpos) + silent! call repeat#set("\(vimtex-cmd-change)" . new . ' ', v:count) +endfunction + +function! vimtex#cmd#delete() " {{{1 + " Get old command + let [l:old, l:line, l:col] = vimtex#cmd#get_command() + if l:old ==# '' | return | endif + + " Store current cursor position + let l:curpos = exists('*getcurpos') ? getcurpos() : getpos('.') + if l:line == l:curpos[1] + let l:curpos[2] -= len(l:old)+1 + endif + + " Save selection + let l:vstart = [l:curpos[0], line("'<"), col("'<"), l:curpos[3]] + let l:vstop = [l:curpos[0], line("'<"), col("'>"), l:curpos[3]] + + " This is a hack to make undo restore the correct position + normal! ix + normal! x + + " Use temporary cursor position + let l:tmppos = copy(l:curpos) + let l:tmppos[1:2] = [l:line, l:col] + call setpos('.', l:tmppos) + normal! de + + " Delete surrounding braces if present + if getline('.')[l:col-1 :] =~# '^\s*{' + call searchpos('{', 'c') + keepjumps normal! vaBomzoxg`zx + if l:line == l:curpos[1] + let l:curpos[2] -= 1 + if l:curpos[2] < 0 + let l:curpos[2] = 0 + endif + endif + endif + + " Restore cursor position and visual selection + call setpos('.', l:curpos) + call setpos("'<", l:vstart) + call setpos("'>", l:vstop) + + " Create repeat hook + silent! call repeat#set("\(vimtex-cmd-delete)", v:count) +endfunction + +function! vimtex#cmd#create() " {{{1 + " Get current line + let line = getline('.') + + " Get cursor position + let pos = getpos('.') + + " Return if there is no word at cursor + if mode() ==# 'n' + let column = pos[2] - 1 + else + let column = pos[2] - 2 + endif + if column <= 1 || line[column] =~# '\s' + return '' + endif + + " Prepend a backslash to beginning of the current word + normal! B + let column = getpos('.')[2] + if line[column - 1] !=# '\' + let line = strpart(line, 0, column - 1) . '\' . strpart(line, column - 1) + call setline('.', line) + endif + + " Append opening braces to the end of the current word + normal! E + let column = getpos('.')[2] + let pos[2] = column + 1 + if line[column - 1] !=# '{' + let line = strpart(line, 0, column) . '{' . strpart(line, column) + call setline('.', line) + let pos[2] += 1 + endif + + " Restore cursor position + call setpos('.', pos) + return '' +endfunction + +" }}}1 + +" vim: fdm=marker sw=2 diff --git a/autoload/vimtex/delim.vim b/autoload/vimtex/delim.vim new file mode 100644 index 0000000..4264bf0 --- /dev/null +++ b/autoload/vimtex/delim.vim @@ -0,0 +1,640 @@ +" vimtex - LaTeX plugin for Vim +" +" Maintainer: Karl Yngve Lervåg +" Email: karl.yngve@gmail.com +" + +function! vimtex#delim#init_options() " {{{1 +endfunction + +" }}}1 +function! vimtex#delim#init_script() " {{{1 + let s:delims = {} + let s:re = {} + + let s:delims.env = { + \ 'list' : [ + \ ['begin', 'end'], + \ ['\(', '\)'], + \ ['\[', '\]'], + \ ['$$', '$$'], + \ ['$', '$'], + \ ], + \ 're' : [ + \ ['\\begin\s*{[^}]*}', '\\end\s*{[^}]*}'], + \ ['\\(', '\\)'], + \ ['\\\[', '\\\]'], + \ ['\$\$', '\$\$'], + \ ['\$', '\$'], + \ ], + \} + + let s:delims.delim_tex = { + \ 'list' : [ + \ ['(', ')'], + \ ['[', ']'], + \ ['{', '}'], + \ ], + \ 're' : [ + \ ['(', ')'], + \ ['\[', '\]'], + \ ['{', '}'], + \ ], + \} + + let s:delims.delim_mods = { + \ 'list' : [ + \ ['\left', '\right'], + \ ['\bigl', '\bigr'], + \ ['\Bigl', '\Bigr'], + \ ['\biggl', '\biggr'], + \ ['\Biggl', '\Biggr'], + \ ['\big', '\big'], + \ ['\Big', '\Big'], + \ ['\bigg', '\bigg'], + \ ['\Bigg', '\Bigg'], + \ ], + \ 're' : [ + \ ['\\left', '\\right'], + \ ['\\bigl', '\\bigr'], + \ ['\\Bigl', '\\Bigr'], + \ ['\\biggl', '\\biggr'], + \ ['\\Biggl', '\\Biggr'], + \ ['\\big', '\\big'], + \ ['\\Big', '\\Big'], + \ ['\\bigg', '\\bigg'], + \ ['\\Bigg', '\\Bigg'], + \ ], + \} + + let s:delims.delim_math = { + \ 'list' : [ + \ ['(', ')'], + \ ['[', ']'], + \ ['\{', '\}'], + \ ['\|', '\|'], + \ ['|', '|'], + \ ['\langle', '\rangle'], + \ ['\lvert', '\rvert'], + \ ['\lfloor', '\rfloor'], + \ ['\lceil', '\rceil'], + \ ['\ulcorner', '\urcorner'], + \ ], + \ 're' : [ + \ ['(', ')'], + \ ['\[', '\]'], + \ ['\\{', '\\}'], + \ ['\\|', '\\|'], + \ ['|', '|'], + \ ['\\langle', '\\rangle'], + \ ['\\lvert', '\\rvert'], + \ ['\\lfloor', '\\rfloor'], + \ ['\\lceil', '\\rceil'], + \ ['\\ulcorner', '\\urcorner'], + \ ], + \} + + let s:re.env = { + \ 'open' : '\%(' + \ . join(map(copy(s:delims.env.re), 'v:val[0]'), '\|') + \ . '\)', + \ 'close' : '\%(' + \ . join(map(copy(s:delims.env.re), 'v:val[1]'), '\|') + \ . '\)', + \ 'both' : '\%(' + \ . join(map(copy(s:delims.env.re), 'v:val[0]'), '\|') . '\|' + \ . join(map(copy(s:delims.env.re), 'v:val[1]'), '\|') + \ . '\)' + \} + + let s:re.delim_tex = { + \ 'open' : '\%(' + \ . join(map(copy(s:delims.delim_tex.re), 'v:val[0]'), '\|') + \ . '\)', + \ 'close' : '\%(' + \ . join(map(copy(s:delims.delim_tex.re), 'v:val[1]'), '\|') + \ . '\)', + \ 'both' : '\%(' + \ . join(map(copy(s:delims.delim_tex.re), 'v:val[0]'), '\|') . '\|' + \ . join(map(copy(s:delims.delim_tex.re), 'v:val[1]'), '\|') + \ . '\)' + \} + + let s:re.delim_mods = { + \ 'open' : '\\left\|\\[bB]igg\?l\?', + \ 'close' : '\\right\|\\[bB]igg\?r\?', + \ 'both' : '\\left\|\\right\|\\[bB]igg\?[lr]\?', + \} + + let s:re.delim_math = { + \ 'open' : '\%(\%(' . s:re.delim_mods.open . '\)\s*\)\?\%(' + \ . join(map(copy(s:delims.delim_math.re), 'v:val[0]'), '\|') + \ . '\)', + \ 'close' : '\%(\%(' . s:re.delim_mods.close . '\)\s*\)\?\%(' + \ . join(map(copy(s:delims.delim_math.re), 'v:val[1]'), '\|') + \ . '\)', + \ 'both' : '\%(\%(' . s:re.delim_mods.both . '\)\s*\)\?\%(' + \ . join(map(copy(s:delims.delim_math.re), 'v:val[0]'), '\|') . '\|' + \ . join(map(copy(s:delims.delim_math.re), 'v:val[1]'), '\|') + \ . '\)' + \} + + let s:delims.delim_all = {} + let s:delims.all = {} + let s:re.delim_all = {} + let s:re.all = {} + for k in ['list', 're'] + let s:delims.delim_all[k] = s:delims.delim_math[k] + let s:delims.all[k] = s:delims.env[k] + s:delims.delim_all[k] + endfor + for k in ['open', 'close', 'both'] + let s:re.delim_all[k] = s:re.delim_math[k] + let s:re.all[k] = s:re.env[k] . '\|' . s:re.delim_all[k] + endfor + + let s:types = [ + \ { + \ 're' : '\\\%(begin\|end\)\>', + \ 'parser' : function('s:parser_env'), + \ }, + \ { + \ 're' : '\$\$\?', + \ 'parser' : function('s:parser_tex'), + \ }, + \ { + \ 're' : '\\\%((\|)\|\[\|\]\)', + \ 'parser' : function('s:parser_latex'), + \ }, + \ { + \ 're' : s:re.delim_all.both, + \ 'parser' : function('s:parser_delim'), + \ }, + \] +endfunction + +" }}}1 +function! vimtex#delim#init_buffer() " {{{1 + nnoremap (vimtex-delim-toggle-modifier) + \ :call vimtex#delim#toggle_modifier() + + inoremap (vimtex-delim-close) + \ =vimtex#delim#close() +endfunction + +" }}}1 + +function! vimtex#delim#get_valid_regexps(...) " {{{1 + " + " Arguments: (Optional) + " line number + " column number + " + " Returns: + " [regexp_open_delims, regexp_close_delims] + " + + return call('vimtex#util#in_mathzone', a:000) + \ ? [s:re.delim_math.open, s:re.delim_math.close] + \ : [s:re.delim_tex.open, s:re.delim_tex.close] +endfunction + +" }}}1 +function! vimtex#delim#close() " {{{1 + let l:delim = vimtex#delim#get_prev('all', 'open') + + return empty(l:delim) + \ || get(l:delim, 'name', '') ==# 'document' + \ ? '' + \ : l:delim.corr +endfunction + +" }}}1 +function! vimtex#delim#toggle_modifier() " {{{1 + let [l:open, l:close] = vimtex#delim#get_surrounding('delim_math') + if empty(l:open) | return | endif + + let newmods = [] + let modlist = [['', '']] + \ + get(g:, 'vimtex_delim_toggle_mod_list', + \ s:delims.delim_mods.list) + let n = len(modlist) + for i in range(n) + let j = (i + 1) % n + if l:open.mod ==# modlist[i][0] + let newmods = modlist[j] + break + endif + endfor + + let line = getline(l:open.lnum) + let line = strpart(line, 0, l:open.cnum - 1) + \ . newmods[0] + \ . strpart(line, l:open.cnum + len(l:open.mod) - 1) + call setline(l:open.lnum, line) + + let l:cnum = l:close.cnum + if l:open.lnum == l:close.lnum + let n = len(newmods[0]) - len(l:open.mod) + let l:cnum += n + let pos = getpos('.') + if pos[2] > l:open.cnum + len(l:open.mod) + let pos[2] += n + call setpos('.', pos) + endif + endif + + let line = getline(l:close.lnum) + let line = strpart(line, 0, l:cnum - 1) + \ . newmods[1] + \ . strpart(line, l:cnum + len(l:close.mod) - 1) + call setline(l:close.lnum, line) + + silent! call repeat#set("\(vimtex-delim-toggle-modifier)", v:count) +endfunction + +" }}}1 + +function! vimtex#delim#get_next(type, side) " {{{1 + return s:get_delim('next', a:type, a:side) +endfunction + +" }}}1 +function! vimtex#delim#get_prev(type, side) " {{{1 + return s:get_delim('prev', a:type, a:side) +endfunction + +" }}}1 +function! vimtex#delim#get_current(type, side) " {{{1 + return s:get_delim('current', a:type, a:side) +endfunction + +" }}}1 +function! vimtex#delim#get_matching(delim) " {{{1 + if empty(a:delim) || !has_key(a:delim, 'lnum') | return {} | endif + + " + " Get the matching position + " + let l:save_pos = getpos('.') + call setpos('.', [0, a:delim.lnum, a:delim.cnum, 0]) + let [l:match, l:lnum, l:cnum] = a:delim.get_matching() + call setpos('.', l:save_pos) + + " + " Create the match result + " + let l:matching = deepcopy(a:delim) + let l:matching.lnum = l:lnum + let l:matching.cnum = l:cnum + let l:matching.match = l:match + let l:matching.corr = a:delim.match + let l:matching.side = a:delim.is_open ? 'close' : 'open' + let l:matching.is_open = !a:delim.is_open + if l:matching.type ==# 'delim' + let l:matching.corr_delim = a:delim.delim + let l:matching.corr_mod = a:delim.mod + let l:matching.delim = a:delim.corr_delim + let l:matching.mod = a:delim.corr_mod + endif + return l:matching +endfunction + +" }}}1 +function! vimtex#delim#get_surrounding(type) " {{{1 + let l:save_pos = getpos('.') + let l:lnum = l:save_pos[1] + let l:pos_val_cursor = 10000*l:save_pos[1] + l:save_pos[2] + + while l:lnum > 1 + let l:open = vimtex#delim#get_prev(a:type, 'open') + if empty(l:open) | break | endif + let l:close = vimtex#delim#get_matching(l:open) + let l:pos_val_try = 10000*l:close.lnum + \ + l:close.cnum + strlen(l:close.match) + if l:pos_val_try > l:pos_val_cursor + call setpos('.', l:save_pos) + return [l:open, l:close] + else + let l:lnum = l:open.lnum + call setpos('.', s:pos_prev(l:open.lnum, l:open.cnum)) + endif + endwhile + + call setpos('.', l:save_pos) + return [{}, {}] +endfunction + +" }}}1 + +function! s:get_delim(direction, type, side) " {{{1 + " + " Arguments: + " direction next + " prev + " current + " type env + " delim_tex + " delim_math + " delim_all + " all + " side open + " close + " both + " + " Returns: + " delim = { + " type : env | delim | $ | $$ | \( | \[ + " side : open | close + " name : name of environment [only for type env] + " lnum : number + " cnum : number + " match : unparsed matched delimiter + " corr : corresponding delimiter + " re : { + " open : regexp for the opening part + " close : regexp for the closing part + " } + " } + " + let l:re = s:re[a:type][a:side] + let [l:lnum, l:cnum] = a:direction ==# 'next' + \ ? searchpos(l:re, 'cnW') + \ : a:direction ==# 'prev' + \ ? searchpos(l:re, 'bcnW') + \ : searchpos(l:re, 'bcnW', line('.')) + let l:match = matchstr(getline(l:lnum), '^' . l:re, l:cnum-1) + + if a:direction ==# 'current' + \ && l:cnum + strlen(l:match) + (mode() ==# 'i' ? 1 : 0) <= col('.') + let l:match = '' + let l:lnum = 0 + let l:cnum = 0 + endif + + let l:result = { + \ 'type' : '', + \ 'lnum' : l:lnum, + \ 'cnum' : l:cnum, + \ 'match' : l:match, + \} + + for l:type in s:types + if l:match =~# '^' . l:type.re + let l:result = extend( + \ l:type.parser(l:match, l:lnum, l:cnum, a:side, a:type, a:direction), + \ l:result, 'keep') + break + endif + endfor + + return empty(l:result.type) ? {} : l:result +endfunction + +" }}}1 + +function! s:parser_env(match, lnum, cnum, ...) " {{{1 + let result = {} + + let result.type = 'env' + let result.name = matchstr(a:match, '{\zs.*\ze}') + let result.side = a:match =~# '\\begin' ? 'open' : 'close' + let result.is_open = result.side ==# 'open' + let result.get_matching = function('s:get_matching_env') + + let result.corr = result.is_open + \ ? substitute(a:match, 'begin', 'end', '') + \ : substitute(a:match, 'end', 'begin', '') + + let result.re = { + \ 'open' : '\\begin\s*{' . result.name . '}', + \ 'close' : '\\end\s*{' . result.name . '}', + \} + + let result.re.this = result.is_open ? result.re.open : result.re.close + let result.re.corr = result.is_open ? result.re.close : result.re.open + + return result +endfunction + +" }}}1 +function! s:parser_tex(match, lnum, cnum, side, type, direction) " {{{1 + " + " TeX shorthand are these + " + " $ ... $ (inline math) + " $$ ... $$ (displayed equations) + " + " The notation does not provide the delimiter side directly, which provides + " a slight problem. However, we can utilize the syntax information to parse + " the side. + " + let result = {} + let result.type = a:match + let result.corr = a:match + let result.get_matching = function('s:get_matching_tex') + let result.re = { + \ 'this' : escape(a:match, '$'), + \ 'corr' : escape(a:match, '$'), + \ 'open' : escape(a:match, '$'), + \ 'close' : escape(a:match, '$'), + \} + let result.side = vimtex#util#in_syntax( + \ (a:match ==# '$' ? 'texMathZoneX' : 'texMathZoneY'), + \ a:lnum, a:cnum+1) + \ ? 'open' : 'close' + let result.is_open = result.side ==# 'open' + + if (a:side !=# 'both') && (a:side !=# result.side) + " + " The current match ($ or $$) is not the correct side, so we must + " continue the search recursively. We do this by changing the cursor + " position, since the function searchpos relies on the current cursor + " position. + " + let l:save_pos = getpos('.') + + " Move the cursor + call setpos('.', a:direction ==# 'next' + \ ? s:pos_next(a:lnum, a:cnum) + \ : s:pos_prev(a:lnum, a:cnum)) + + " Get new result + let result = s:get_delim(a:direction, a:type, a:side) + + " Restore the cursor + call setpos('.', l:save_pos) + endif + + return result +endfunction + +" }}}1 +function! s:parser_latex(match, lnum, cnum, ...) " {{{1 + let result = {} + + let result.type = a:match =~# '\\(\|\\)' ? '\(' : '\[' + let result.side = a:match =~# '\\(\|\\\[' ? 'open' : 'close' + let result.is_open = result.side ==# 'open' + let result.get_matching = function('s:get_matching_latex') + + let result.corr = result.is_open + \ ? substitute(substitute(a:match, '\[', ']', ''), '(', ')', '') + \ : substitute(substitute(a:match, '\]', '[', ''), ')', '(', '') + + let result.re = { + \ 'open' : result.type ==# '\(' ? '\\(' : '\\\[', + \ 'close' : result.type ==# '\(' ? '\\)' : '\\\]', + \} + + let result.re.this = result.is_open ? result.re.open : result.re.close + let result.re.corr = result.is_open ? result.re.close : result.re.open + + return result +endfunction + +" }}}1 +function! s:parser_delim(match, lnum, cnum, ...) " {{{1 + let result = {} + let result.type = 'delim' + let result.side = a:match =~# s:re.delim_all.open ? 'open' : 'close' + let result.is_open = result.side ==# 'open' + let result.get_matching = function('s:get_matching_delim') + + " + " Find corresponding delimiter and the regexps + " + if a:match =~# '^' . s:re.delim_mods.both + let m1 = matchstr(a:match, '^' . s:re.delim_mods.both) + let d1 = substitute(strpart(a:match, len(m1)), '^\s*', '', '') + let m2 = s:parser_delim_get_corr(m1, 'delim_mods') + let d2 = s:parser_delim_get_corr(d1, 'delim_math') + let re = [ + \ s:parser_delim_get_regexp(m1, 'delim_mods') + \ . '\s*' . s:parser_delim_get_regexp(d1, 'delim_math'), + \ s:parser_delim_get_regexp(m2, 'delim_mods') + \ . '\s*' . s:parser_delim_get_regexp(d2, 'delim_math') + \] + else + let d1 = a:match + let m1 = '' + let d2 = s:parser_delim_get_corr(a:match) + let m2 = '' + let re = [ + \ s:parser_delim_get_regexp(a:match), + \ s:parser_delim_get_regexp(d2) + \] + endif + + let result.delim = d1 + let result.mod = m1 + let result.corr = m2 . d2 + let result.corr_delim = d2 + let result.corr_mod = m2 + let result.re = { + \ 'this' : re[0], + \ 'corr' : re[1], + \ 'open' : result.is_open ? re[0] : re[1], + \ 'close' : result.is_open ? re[1] : re[0], + \} + + return result +endfunction + +" }}}1 +function! s:parser_delim_get_regexp(delim, ...) " {{{1 + let l:type = a:0 > 0 ? a:1 : 'delim_all' + + " Check open delimiters + let index = index(map(copy(s:delims[l:type].list), 'v:val[0]'), a:delim) + if index >= 0 + return s:delims[l:type].re[index][0] + endif + + " Check close delimiters + let index = index(map(copy(s:delims[l:type].list), 'v:val[1]'), a:delim) + if index >= 0 + return s:delims[l:type].re[index][1] + endif +endfunction + +" }}}1 +function! s:parser_delim_get_corr(delim, ...) " {{{1 + let l:type = a:0 > 0 ? a:1 : 'delim_all' + + for l:pair in s:delims[l:type].list + if a:delim ==# l:pair[0] + return l:pair[1] + elseif a:delim ==# l:pair[1] + return l:pair[0] + endif + endfor +endfunction + +" }}}1 + +function! s:get_matching_env() dict " {{{1 + let [re, flags] = self.is_open + \ ? [self.re.close, 'nW'] + \ : [self.re.open, 'bnW'] + + let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close, flags) + let match = matchstr(getline(lnum), '^' . re, cnum-1) + + return [match, lnum, cnum] +endfunction + +" }}}1 +function! s:get_matching_tex() dict " {{{1 + let [re, flags] = self.is_open + \ ? [self.re.open, 'nW'] + \ : [self.re.open, 'bnW'] + + let [lnum, cnum] = searchpos(re, flags) + let match = matchstr(getline(lnum), '^' . re, cnum-1) + + return [match, lnum, cnum] +endfunction + +" }}}1 +function! s:get_matching_latex() dict " {{{1 + let [re, flags] = self.is_open + \ ? [self.re.close, 'nW'] + \ : [self.re.open, 'bnW'] + + let [lnum, cnum] = searchpos(re, flags) + let match = matchstr(getline(lnum), '^' . re, cnum-1) + + return [match, lnum, cnum] +endfunction + +" }}}1 +function! s:get_matching_delim() dict " {{{1 + let [re, flags] = self.is_open + \ ? [self.re.close, 'nW'] + \ : [self.re.open, 'bnW'] + + let [lnum, cnum] = searchpairpos(self.re.open, '', self.re.close, flags, + \ 'vimtex#util#in_comment()') + let match = matchstr(getline(lnum), '^' . re, cnum-1) + + return [match, lnum, cnum] +endfunction + +" }}}1 + +function! s:pos_next(lnum, cnum) " {{{1 + return a:cnum < strlen(getline(a:lnum)) + \ ? [0, a:lnum, a:cnum+1, 0] + \ : [0, a:lnum+1, 1, 0] +endfunction + +" }}}1 +function! s:pos_prev(lnum, cnum) " {{{1 + return a:cnum > 1 + \ ? [0, a:lnum, a:cnum-1, 0] + \ : [0, max([a:lnum-1, 1]), strlen(getline(a:lnum-1)), 0] +endfunction + +" }}}1 + +" vim: fdm=marker sw=2 diff --git a/autoload/vimtex/env.vim b/autoload/vimtex/env.vim new file mode 100644 index 0000000..3ad03ea --- /dev/null +++ b/autoload/vimtex/env.vim @@ -0,0 +1,135 @@ +" vimtex - LaTeX plugin for Vim +" +" Maintainer: Karl Yngve Lervåg +" Email: karl.yngve@gmail.com +" + +function! vimtex#env#init_options() " {{{1 + call vimtex#util#set_default('g:vimtex_env_complete_list', [ + \ 'itemize', + \ 'enumerate', + \ 'description', + \ 'center', + \ 'figure', + \ 'table', + \ 'equation', + \ 'multline', + \ 'align', + \ 'split', + \ '\[', + \ ]) +endfunction + +" }}}1 +function! vimtex#env#init_script() " {{{1 +endfunction + +" }}}1 +function! vimtex#env#init_buffer() " {{{1 + nnoremap (vimtex-env-delete) + \ :call vimtex#env#change('') + + nnoremap (vimtex-env-change) + \ :call vimtex#env#change_prompt() + + nnoremap (vimtex-env-toggle-star) + \ :call vimtex#env#toggle_star() +endfunction + +" }}}1 + +function! vimtex#env#change(new) " {{{1 + let [l:open, l:close] = vimtex#delim#get_surrounding('env') + + " + " Set target environment + " + if a:new ==# '' + let [l:beg, l:end] = ['', ''] + elseif a:new ==# '$' + let [l:beg, l:end] = ['$', '$'] + elseif a:new ==# '$$' + let [l:beg, l:end] = ['$$', '$$'] + elseif a:new ==# '\[' + let [l:beg, l:end] = ['\[', '\]'] + elseif a:new ==# '\(' + let [l:beg, l:end] = ['\(', '\)'] + else + let l:beg = '\begin{' . a:new . '}' + let l:end = '\end{' . a:new . '}' + endif + + let l:line = getline(l:open.lnum) + call setline(l:open.lnum, + \ strpart(l:line, 0, l:open.cnum-1) + \ . l:beg + \ . strpart(l:line, l:open.cnum + len(l:open.match) - 1)) + + let l:c1 = l:close.cnum + let l:c2 = l:close.cnum + len(l:close.match) - 1 + if l:open.lnum == l:close.lnum + let n = len(l:beg) - len(l:open.match) + let l:c1 += n + let l:c2 += n + let pos = getpos('.') + if pos[2] > l:open.cnum + len(l:open.match) - 1 + let pos[2] += n + call setpos('.', pos) + endif + endif + + let l:line = getline(l:close.lnum) + call setline(l:close.lnum, + \ strpart(l:line, 0, l:c1-1) . l:end . strpart(l:line, l:c2)) + + if a:new ==# '' + silent! call repeat#set("\(vimtex-env-delete)", v:count) + else + silent! call repeat#set( + \ "\(vimtex-env-change)" . a:new . ' ', v:count) + endif +endfunction + +function! vimtex#env#change_prompt() " {{{1 + let [l:open, l:close] = vimtex#delim#get_surrounding('env') + let l:name = l:open.type ==# 'env' ? l:open.name : l:open.type + + call vimtex#echo#status(['Change surrounding environment: ', + \ ['VimtexWarning', l:name]]) + echohl VimtexMsg + let l:new_env = input('> ', '', 'customlist,' . s:sidwrap('input_complete')) + echohl None + + if empty(l:new_env) + return + else + call vimtex#env#change(l:new_env) + endif +endfunction + +function! vimtex#env#toggle_star() " {{{1 + let [l:open, l:close] = vimtex#delim#get_surrounding('env') + if l:open.type !=# 'env' | return | endif + + call vimtex#env#change(l:open.name[-1:] ==# '*' + \ ? l:open.name[:-2] + \ : l:open.name . '*' + \) + + silent! call repeat#set("\(vimtex-env-toggle-star)", v:count) +endfunction + +" }}}1 + +function! s:sidwrap(func) " {{{1 + return matchstr(expand(''), '\zs\d\+_\ze.*$') . a:func +endfunction + +" }}}1 +function! s:input_complete(lead, cmdline, pos) " {{{1 + return filter(g:vimtex_env_complete_list, 'v:val =~# ''^' . a:lead . '''') +endfunction + +" }}}1 + +" vim: fdm=marker sw=2 diff --git a/autoload/vimtex/motion.vim b/autoload/vimtex/motion.vim index bad0110..526219f 100644 --- a/autoload/vimtex/motion.vim +++ b/autoload/vimtex/motion.vim @@ -19,7 +19,7 @@ function! vimtex#motion#init_script() " {{{1 if g:vimtex_motion_matchparen augroup vimtex_motion autocmd! - autocmd! CursorMoved *.tex call s:highlight_matching_pair(1) + autocmd! CursorMoved *.tex call s:highlight_matching_pair() autocmd! CursorMovedI *.tex call s:highlight_matching_pair() augroup END endif @@ -34,32 +34,6 @@ function! vimtex#motion#init_script() " {{{1 " Not in a comment let s:notcomment = '\%(\%(\\\@ 0 ? 1 : 0 - 2match none - " Save position - let nl = line('.') - let nc = col('.') - let line = getline(nl) + let l:d1 = vimtex#delim#get_current('all', 'both') + let l:d2 = vimtex#delim#get_matching(l:d1) + if empty(l:d2) | return | endif - " Find delimiter under cursor - let cnum = searchpos(s:delimiters, 'cbnW', nl)[1] - let delim = matchstr(line, '^'. s:delimiters, cnum-1) + let [l1, c1, l2, c2] = l:d1.side ==# 'open' + \ ? [l:d1.lnum, l:d1.cnum, l:d2.lnum, l:d2.cnum] + \ : [l:d2.lnum, l:d2.cnum, l:d1.lnum, l:d1.cnum] - " Only highlight when cursor is on delimiters - if empty(delim) || strlen(delim)+cnum-hmode < nc - return - endif - - if delim =~# '^\$' - " - " Match inline math - " - let [lnum0, cnum0] = searchpos('.', 'nW') - if lnum0 && vimtex#util#in_syntax('texMathZoneX', lnum0, cnum0) - let [lnum2, cnum2] = searchpos(s:notcomment . s:notbslash . '\$', - \ 'nW', line('w$'), 200) - else - let [lnum2, cnum2] = searchpos('\%(\%'. nl . 'l\%' - \ . cnum . 'c\)\@!'. s:notcomment . s:notbslash . '\$', - \ 'bnW', line('w0'), 200) - endif - - execute '2match MatchParen /\%(\%' . nl . 'l\%' - \ . cnum . 'c\$' . '\|\%' . lnum2 . 'l\%' . cnum2 . 'c\$\)/' - else - " - " Match other delimitors - " - for i in range(len(s:delimiters_open)) - let open_pat = '\C' . s:notbslash . s:delimiters_open[i] - let close_pat = '\C' . s:notbslash . s:delimiters_close[i] - - if delim =~# '^' . open_pat - let [lnum2, cnum2] = searchpairpos(open_pat, '', close_pat, - \ 'nW', 'vimtex#util#in_comment()', line('w$'), 200) - execute '2match MatchParen /\%(\%' . nl . 'l\%' . cnum - \ . 'c' . s:delimiters_open[i] . '\|\%' - \ . lnum2 . 'l\%' . cnum2 . 'c' - \ . s:delimiters_close[i] . '\)/' - return - elseif delim =~# '^' . close_pat - let [lnum2, cnum2] = searchpairpos(open_pat, '', - \ '\C\%(\%'. nl . 'l\%' . cnum . 'c\)\@!' . close_pat, - \ 'bnW', 'vimtex#util#in_comment()', line('w0'), 200) - execute '2match MatchParen /\%(\%' . lnum2 . 'l\%' . cnum2 - \ . 'c' . s:delimiters_open[i] . '\|\%' - \ . nl . 'l\%' . cnum . 'c' - \ . s:delimiters_close[i] . '\)/' - return - endif - endfor - endif + execute '2match MatchParen /' + \ . '\%' . l1 . 'l\%' . c1 . 'c' . l:d1.re.open + \ . '\|\%' . l2 . 'l\%' . c2 . 'c' . l:d1.re.close . '/' endfunction " }}}1 diff --git a/autoload/vimtex/text_obj.vim b/autoload/vimtex/text_obj.vim index ea0330a..ba6809a 100644 --- a/autoload/vimtex/text_obj.vim +++ b/autoload/vimtex/text_obj.vim @@ -56,108 +56,43 @@ endfunction " }}}1 function! vimtex#text_obj#delimiters(...) " {{{1 - let inner = a:0 > 0 - - let [d1, l1, c1, d2, l2, c2] = vimtex#delim#get_surrounding() - - if inner - let c1 += len(d1) - if c1 != len(getline(l1)) - let l1 += 1 - let c1 = 1 - endif - endif - - if inner - let c2 -= 1 - if c2 < 1 - let l2 -= 1 - let c2 = len(getline(l2)) - endif - else - let c2 += len(d2) - 1 - endif - - if l1 < l2 || (l1 == l2 && c1 < c2) - call cursor(l1,c1) - if visualmode() ==# 'V' - normal! V - else - normal! v - endif - call cursor(l2,c2) - endif + let [l:open, l:close] = vimtex#delim#get_surrounding('delim_all') + call s:text_obj_delim(l:open, l:close, a:0 > 0) endfunction " }}}1 function! vimtex#text_obj#environments(...) " {{{1 - let inner = a:0 > 0 + let l:inner = a:0 > 0 - let [env, lnum, cnum, lnum2, cnum2] = vimtex#util#get_env(1) - call cursor(lnum, cnum) - if inner - if env =~# '^\' - call search('\\.\_\s*\S', 'eW') - else - call search('}\(\_\s*\(\[\_[^]]*\]\|{\_\S\{-}}\)\)\?\_\s*\S', 'eW') - endif + let [l:open, l:close] = vimtex#delim#get_surrounding('env') + if l:open.type !=# 'env' | return | endif + + if l:inner + call cursor(l:open.lnum, l:open.cnum + strlen(l:open.match)) + call search('}\%(\_\s*\%(\[\_[^]]*\]\)\)\?\_\s*\S', 'eW') + else + call cursor(l:open.lnum, l:open.cnum) endif + if visualmode() ==# 'V' normal! V else normal! v endif - call cursor(lnum2, cnum2) - if inner + + if l:inner + call cursor(l:close.lnum, l:close.cnum) call search('\S\_\s*', 'bW') else - if env =~# '^\' - normal! l - else - call search('}', 'eW') - endif + call cursor(l:close.lnum, l:close.cnum + strlen(l:close.match) - 1) endif endfunction " }}}1 function! vimtex#text_obj#inline_math(...) " {{{1 - let l:inner = a:0 > 0 - - let l:flags = 'bW' - let l:dollar = 0 - let l:dollar_pat = '\\\@ 0) endfunction " }}}1 function! vimtex#text_obj#paragraphs(...) " {{{1 @@ -177,34 +112,33 @@ endfunction " }}}1 -function! s:search_and_skip_comments(pat, ...) " {{{1 - " Usage: s:search_and_skip_comments(pat, [flags, stopline]) - let flags = a:0 >= 1 ? a:1 : '' - let stopline = a:0 >= 2 ? a:2 : 0 - let saved_pos = getpos('.') +function! s:text_obj_delim(open, close, inner) " {{{1 + let [l1, c1, l2, c2] = [a:open.lnum, a:open.cnum, a:close.lnum, a:close.cnum] - " search once - let ret = search(a:pat, flags, stopline) - - if ret - " do not match at current position if inside comment - let flags = substitute(flags, 'c', '', 'g') - - " keep searching while in comment - while vimtex#util#in_comment() - let ret = search(a:pat, flags, stopline) - if !ret - break - endif - endwhile + if a:inner + let c1 += len(a:open.match) + let c2 -= 1 + if c1 >= len(getline(l1)) + let l1 += 1 + let c1 = 1 + endif + if c2 < 1 + let l2 -= 1 + let c2 = len(getline(l2)) + endif + else + let c2 += len(a:close.match) - 1 endif - if !ret - " if no match found, restore position - call setpos('.', saved_pos) + if l1 < l2 || (l1 == l2 && c1 < c2) + call cursor(l1, c1) + if visualmode() ==# 'V' + normal! V + else + normal! v + endif + call cursor(l2, c2) endif - - return ret endfunction " }}}1 diff --git a/autoload/vimtex/util.vim b/autoload/vimtex/util.vim index aeb0606..31f77e7 100644 --- a/autoload/vimtex/util.vim +++ b/autoload/vimtex/util.vim @@ -9,21 +9,6 @@ endfunction " }}}1 function! vimtex#util#init_script() " {{{1 - let s:delimiters_open = [ - \ '(', - \ '\[', - \ '\\{', - \ '\\\Cleft\s*\%([^\\a-zA-Z0-9]\|\\.\|\\\a*\)', - \ '\\\cbigg\?\((\|\[\|\\{\)', - \ ] - - let s:delimiters_close = [ - \ ')', - \ '\]', - \ '\\}', - \ '\\\Cright\s*\%([^\\a-zA-Z0-9]\|\\.\|\\\a*\)', - \ '\\\cbigg\?\()\|\]\|\\}\)', - \ ] endfunction " }}}1 @@ -141,133 +126,6 @@ function! vimtex#util#shellescape(cmd) " {{{1 endif endfunction -" }}}1 -function! vimtex#util#get_env(...) " {{{1 - " vimtex#util#get_env([with_pos]) - " Returns: - " - environment - " if with_pos is not given - " - [environment, lnum_begin, cnum_begin, lnum_end, cnum_end] - " if with_pos is nonzero - let with_pos = a:0 > 0 ? a:1 : 0 - let move_crs = a:0 > 1 ? a:2 : 1 - - let begin_pat = '\C\\begin\_\s*{[^}]*}\|\\\@ 1 && line[cnum - 1] !=# '\' - let cnum -= 1 - endwhile - call cursor(lnum, cnum) - endif - - " match begin/end pairs but skip comments - let flags = 'bnW' - if strpart(getline('.'), col('.') - 1) =~ '^\%(' . begin_pat . '\)' - let flags .= 'c' - endif - let [lnum1, cnum1] = searchpairpos(begin_pat, '', end_pat, flags, - \ 'vimtex#util#in_comment()') - - let env = '' - - if lnum1 - let line = strpart(getline(lnum1), cnum1 - 1) - - if empty(env) - let env = matchstr(line, '^\C\\begin\_\s*{\zs[^}]*\ze}') - endif - if empty(env) - let env = matchstr(line, '^\\\[') - endif - if empty(env) - let env = matchstr(line, '^\\(') - endif - endif - - if with_pos == 1 - let flags = 'nW' - if !(lnum1 == lnum && cnum1 == cnum) - let flags .= 'c' - endif - - let [lnum2, cnum2] = searchpairpos(begin_pat, '', end_pat, flags, - \ 'vimtex#util#in_comment()') - - call setpos('.', saved_pos) - return [env, lnum1, cnum1, lnum2, cnum2] - else - call setpos('.', saved_pos) - return env - endif -endfunction - -" }}}1 -function! vimtex#util#get_delim() " {{{1 - " Save position in order to restore before finishing - let pos_original = getpos('.') - - " Save position for more work later - let pos_save = getpos('.') - - " Check if the cursor is on top of a closing delimiter - let close_pats = '\(' . join(s:delimiters_close, '\|') . '\)' - let lnum = pos_save[1] - let cnum = pos_save[2] - let [lnum, cnum] = searchpos(close_pats, 'cbnW', lnum) - let delim = matchstr(getline(lnum), '^'. close_pats, cnum-1) - if pos_save[2] <= (cnum + len(delim) - 1) - let pos_save[1] = lnum - let pos_save[2] = cnum - call setpos('.', pos_save) - endif - - let d1='' - let d2='' - let l1=1000000 - let l2=1000000 - let c1=1000000 - let c2=1000000 - for i in range(len(s:delimiters_open)) - call setpos('.', pos_save) - let open = s:delimiters_open[i] - let close = s:delimiters_close[i] - let flags = 'W' - - " Check if the cursor is on top of an opening delimiter. If it is not, - " then we want to include matches at cursor position to match closing - " delimiters. - if searchpos(open, 'cn') != pos_save[1:2] - let flags .= 'c' - endif - - " Search for closing delimiter - let pos = searchpairpos(open, '', close, flags, 'vimtex#util#in_comment()') - - " Check if the current is pair is the closest pair - if pos[0] && pos[0]*1000 + pos[1] < l2*1000 + c2 - let l2=pos[0] - let c2=pos[1] - let d2=matchstr(strpart(getline(l2), c2 - 1), close) - - let pos = searchpairpos(open,'',close,'bW', 'vimtex#util#in_comment()') - let l1=pos[0] - let c1=pos[1] - let d1=matchstr(strpart(getline(l1), c1 - 1), open) - endif - endfor - - " Restore cursor position and return delimiters and positions - call setpos('.', pos_original) - return [d1,l1,c1,d2,l2,c2] -endfunction - " }}}1 function! vimtex#util#get_os() " {{{1 if has('win32') diff --git a/doc/vimtex.txt b/doc/vimtex.txt index 385539a..911b088 100644 --- a/doc/vimtex.txt +++ b/doc/vimtex.txt @@ -103,7 +103,7 @@ Feature overview~ - Change the surrounding command or environment with `csc`/`cse` - Toggle starred environment with `tse` - Toggle between e.g. `()` and `\left(\right)` with `tsd` - - Close the current environment in insert mode with `]]` + - Close the current environment/delimiter in insert mode with `]]` - Insert new command with `` - Convenient insert mode mappings for faster typing of e.g. maths - Folding @@ -276,53 +276,53 @@ In addition to the mappings listed below, |vimtex| provides convenient insert mode mappings to make it easier and faster to type mathematical equations. This feature is explained in more detail later, see |vimtex-imaps|. - -----------------------------------------------------------~ - LHS RHS MODE~ - -----------------------------------------------------------~ - li |(vimtex-info)| `n` - lI |(vimtex-info-full)| `n` - lt |(vimtex-toc-open)| `n` - lT |(vimtex-toc-toggle)| `n` - ly |(vimtex-labels-open)| `n` - lY |(vimtex-labels-toggle)| `n` - lv |(vimtex-view)| `n` - lr |(vimtex-reverse-search)| `n` - ll |(vimtex-compile-toggle)| `n` - lk |(vimtex-stop)| `n` - lK |(vimtex-stop-all)| `n` - le |(vimtex-errors)| `n` - lo |(vimtex-compile-output)| `n` - lg |(vimtex-status)| `n` - lG |(vimtex-status-all)| `n` - lc |(vimtex-clean)| `n` - lC |(vimtex-clean-full)| `n` - lm |(vimtex-imaps-list)| `n` - lx |(vimtex-reload)| `n` - ls |(vimtex-toggle-main)| `n` - dse |(vimtex-delete-env)| `n` - dsc |(vimtex-delete-cmd)| `n` - cse |(vimtex-change-env)| `n` - csc |(vimtex-change-cmd)| `n` - tse |(vimtex-toggle-star)| `n` - tsd |(vimtex-toggle-delim)| `n` - |(vimtex-create-cmd)| `ni` - ]] |(vimtex-close-env)| `i` - a$ |(vimtex-a$)| `nxo` - i$ |(vimtex-i$)| `nxo` - ae |(vimtex-ae)| `nxo` - ie |(vimtex-ie)| `nxo` - ad |(vimtex-ad)| `nxo` - id |(vimtex-id)| `nxo` - ap |(vimtex-ap)| `nxo` - ip |(vimtex-ip)| `nxo` - % |(vimtex-%)| `nxo` - ]] |(vimtex-]])| `nxo` - ][ |(vimtex-][)| `nxo` - [] |(vimtex-[])| `nxo` - [[ |(vimtex-[[)| `nxo` - } |(vimtex-})| `nxo` - { |(vimtex-{)| `nxo` - -----------------------------------------------------------~ + -------------------------------------------------------------~ + LHS RHS MODE~ + -------------------------------------------------------------~ + li |(vimtex-info)| `n` + lI |(vimtex-info-full)| `n` + lt |(vimtex-toc-open)| `n` + lT |(vimtex-toc-toggle)| `n` + ly |(vimtex-labels-open)| `n` + lY |(vimtex-labels-toggle)| `n` + lv |(vimtex-view)| `n` + lr |(vimtex-reverse-search)| `n` + ll |(vimtex-compile-toggle)| `n` + lk |(vimtex-stop)| `n` + lK |(vimtex-stop-all)| `n` + le |(vimtex-errors)| `n` + lo |(vimtex-compile-output)| `n` + lg |(vimtex-status)| `n` + lG |(vimtex-status-all)| `n` + lc |(vimtex-clean)| `n` + lC |(vimtex-clean-full)| `n` + lm |(vimtex-imaps-list)| `n` + lx |(vimtex-reload)| `n` + ls |(vimtex-toggle-main)| `n` + dse |(vimtex-env-delete)| `n` + dsc |(vimtex-cmd-delete)| `n` + cse |(vimtex-env-change)| `n` + csc |(vimtex-cmd-change)| `n` + tse |(vimtex-env-toggle-star)| `n` + tsd |(vimtex-delim-toggle-modifier)| `n` + |(vimtex-cmd-create)| `ni` + ]] |(vimtex-delim-close)| `i` + a$ |(vimtex-a$)| `nxo` + i$ |(vimtex-i$)| `nxo` + ae |(vimtex-ae)| `nxo` + ie |(vimtex-ie)| `nxo` + ad |(vimtex-ad)| `nxo` + id |(vimtex-id)| `nxo` + ap |(vimtex-ap)| `nxo` + ip |(vimtex-ip)| `nxo` + % |(vimtex-%)| `nxo` + ]] |(vimtex-]])| `nxo` + ][ |(vimtex-][)| `nxo` + [] |(vimtex-[])| `nxo` + [[ |(vimtex-[[)| `nxo` + } |(vimtex-})| `nxo` + { |(vimtex-{)| `nxo` + -------------------------------------------------------------~ ------------------------------------------------------------------------------ Options~ @@ -363,12 +363,12 @@ Options~ Default value: 0 -*g:vimtex_change_complete_envs* +*g:vimtex_env_complete_list* Define a list of environments that will be completed when changing the - surrounding environments (see |(vimtex-change-env)|). + surrounding environments (see |(vimtex-env-change)|). Default value: > - let g:vimtex_change_complete_envs = [ + let g:vimtex_env_complete_list = [ \ 'itemize', \ 'enumerate', \ 'description', @@ -382,21 +382,17 @@ Options~ \ '\[', \ ] < -*g:vimtex_change_toggled_delims* - Define a list of delimiters to toggle through with - |(vimtex-toggle-delim)|. +*g:vimtex_delim_toggle_mod_list* + Define list of delimiter modifiers to toggle through with + |(vimtex-delim-toggle-modifier)|. If the list is not defined, then it uses + a default match list. - - Default value: > - let g:vimtex_change_toggled_delims = [['\\left', '\\right']] + For example, to only toggle between no modifier and the `\left/\right` pair, + use: > + let g:vimtex_delim_toggle_mod_list = [['\\left', '\\right']] < -*g:vimtex_change_ignored_delims_pattern* - A pattern for delimiters that will be ignored when toggling delimiters, see - |(vimtex-toggle-delim)|. + Default value: `Undefined` - Default value: > - let g:vimtex_change_ignored_delims_pattern = '\c\\bigg\?' -< *g:vimtex_format_enabled* If enabled, |vimtex| uses a custom |formatexpr| that should handle inline comments. That is, if it is enabled, then comments at end of lines will not @@ -1082,32 +1078,31 @@ Map definitions~ (|g:vimtex_fold_manual|), then |vimtex| remaps |zx| and |zX| in such that the folds are refreshed appropriately. -*(vimtex-delete-env)* -*(vimtex-change-env)* +*(vimtex-env-delete)* +*(vimtex-env-change)* Delete/Change surrounding environment. -*(vimtex-delete-cmd)* -*(vimtex-change-cmd)* - Delete/Change surrounding command. Note: (vimtex-delete-cmd) depends +*(vimtex-cmd-delete)* +*(vimtex-cmd-change)* + Delete/Change surrounding command. Note: |(vimtex-cmd-delete)| depends on |surround.vim| (https://github.com/tpope/vim-surround). -*(vimtex-toggle-star)* +*(vimtex-env-toggle-star)* Toggle starred environment. -*(vimtex-toggle-delim)* - Toggle delimiters, for instance between `(...)` and `\left(...\right)`. The - behaviour is controlled through |g:vimtex_change_toggled_delims| and - |g:vimtex_change_ignored_delims_pattern|. +*(vimtex-delim-toggle-modifier)* + Toggle delimiter modifiers, for instance between `(...)` and `\left(...\right)`. + See also |g:vimtex_delim_toggle_mod_list|. -*(vimtex-create-cmd)* +*(vimtex-cmd-create)* Convert the current word (word under or right before the cursor) into a LaTeX command. That is, it prepends a backslash and adds an opening brace. From normal mode, the mapping will leave you in insert mode. The mapping works both in normal and in insert mode. It is by default mapped to (see |vimtex-default-mappings| for a list of the default mappings). -*(vimtex-close-env)* - Close the current environment (insert mode). +*(vimtex-delim-close)* + Close the current environment or delimiter (insert mode). *(vimtex-a$)* Text object for inline math (inclusive). @@ -1784,6 +1779,34 @@ The following changelog only logs particularly important changes, such as changes that break backwards compatibility. See the git log for the detailed changelog. +2016-02-06: Large refactoring of delimiter parsing~ +I've refactored a lot of the code in order to make the parsing of delimiters +and features that rely on delimiter detection and similar more consistent. +This results in some changes in option names and similar, but it should make +it easier to provide improved and more robust features. + +There is one feature change: The delimiter toggle now consistently toggles the +modifier, not the delimiter itself, and it toggles between a range of +modifiers by default. For customization, see |g:vimtex_delim_toggle_mod_list|. + +The following options have changed names: + *g:vimtex_change_set_formatexpr* ---> |g:vimtex_format_enabled| + *g:vimtex_change_complete_envs* ---> |g:vimtex_env_complete_list| + *g:vimtex_change_toggled_delims* ---> |g:vimtex_delim_toggle_mod_list| + +The following options have been removed: + *g:vimtex_change_ignored_delims_pattern* --- It was no longer necessary + +The following mappings have been renamed: + *(vimtex-delete-env)* ---> |(vimtex-env-delete)| + *(vimtex-delete-cmd)* ---> |(vimtex-cmd-delete)| + *(vimtex-change-env)* ---> |(vimtex-env-change)| + *(vimtex-change-cmd)* ---> |(vimtex-cmd-change)| + *(vimtex-toggle-star)* ---> |(vimtex-env-toggle-star)| + *(vimtex-toggle-delim)* ---> |(vimtex-delim-toggle-modifier)| + *(vimtex-create-cmd)* ---> |(vimtex-cmd-create)| + *(vimtex-close-env)* ---> |(vimtex-delim-close)| + 2015-10-19: Added convenient insert mode mappings~ I've merged the `math_mappings` branch (see #172 and #251). It adds the feature that is explained in |vimtex-imaps|. diff --git a/indent/tex.vim b/indent/tex.vim index 36c4903..2dbfd9c 100644 --- a/indent/tex.vim +++ b/indent/tex.vim @@ -89,13 +89,10 @@ function! VimtexIndent() " {{{1 endif " Indent opening and closing delimiters - let l:delims = match(map(synstack(v:lnum, max([col('.') - 1, 1])), - \ 'synIDattr(v:val, ''name'')'), '^texMathZone') >= 0 - \ ? [s:delimiters_open_math, s:delimiters_close_math] - \ : [s:delimiters_open_tex, s:delimiters_close_tex] + let [l:re_open, l:re_close] = vimtex#delim#get_valid_regexps(v:lnum, col('.')) let ind += &sw*( - \ max([s:count(pline, l:delims[0]) - s:count(pline, l:delims[1]), 0]) - \ - max([s:count(cline, l:delims[1]) - s:count(cline, l:delims[0]), 0])) + \ max([s:count(pline, l:re_open) - s:count(pline, l:re_close), 0]) + \ - max([s:count(cline, l:re_close) - s:count(cline, l:re_open), 0])) " Indent list items if pline =~# '^\s*\\\(bib\)\?item' @@ -138,28 +135,6 @@ let s:tikz_indented = 0 " Define some common patterns let s:envs_lists = 'itemize\|description\|enumerate\|thebibliography' let s:envs_noindent = 'document\|verbatim\|lstlisting' -let s:delimiters_open_tex = '\(' . join([ - \ '{', - \ '\[', - \ '\\(', - \ ], '\|') . '\)' -let s:delimiters_close_tex = '\(' . join([ - \ '}', - \ '\]', - \ '\\)', - \ ], '\|') . '\)' -let s:delimiters_open_math = '\(' . join([ - \ '\\\[\s*$', - \ '\\{', - \ '\\\Cleft\s*\%([^\\]\|\\.\|\\\a*\)', - \ '\\\cbigg\?\((\|\[\|\\{\)', - \ ], '\|') . '\)' -let s:delimiters_close_math = '\(' . join([ - \ '\\\]\s*$', - \ '\\}', - \ '\\\Cright\s*\%([^\\]\|\\.\|\\\a*\)', - \ '\\\cbigg\?\()\|\]\|\\}\)', - \ ], '\|') . '\)' let s:tikz_commands = '\\\(' . join([ \ 'draw', \ 'fill',