From 0fdaee31ed673e754fe462bbe78410b830df9de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Yngve=20Lerv=C3=A5g?= Date: Mon, 1 Aug 2016 22:57:43 +0200 Subject: [PATCH] Completed a fully custom formatexpr (#436) --- autoload/vimtex/format.vim | 108 ++++++++++++++++++++++++++++++++++--- indent/tex.vim | 22 ++++---- test/vader/format.vader | 9 ++-- 3 files changed, 115 insertions(+), 24 deletions(-) diff --git a/autoload/vimtex/format.vim b/autoload/vimtex/format.vim index bb55288..4e0fd1d 100644 --- a/autoload/vimtex/format.vim +++ b/autoload/vimtex/format.vim @@ -44,7 +44,8 @@ function! vimtex#format#formatexpr() " {{{1 let l:top = v:lnum let l:bottom = v:lnum + v:count - 1 - let l:mark = l:bottom + let l:lines_old = getline(l:top, l:bottom) + let l:tries = 5 " This is a hack to make undo restore the correct position if mode() !=# 'i' @@ -52,7 +53,43 @@ function! vimtex#format#formatexpr() " {{{1 normal! x endif - for l:current in range(l:bottom, l:top, -1) + " Main formatting algorithm + while l:tries > 0 + " Format the range of lines + let l:bottom = s:format(l:top, l:bottom) + + " Ensure proper indentation + silent! execute printf('normal! %sG=%sG', l:top, l:bottom) + + " Check if any lines have changed + let l:lines_new = getline(l:top, l:bottom) + let l:index = s:compare_lines(l:lines_new, l:lines_old) + let l:top += l:index + if l:top > l:bottom | break | endif + let l:lines_old = l:lines_new[l:index:] + let l:tries -= 1 + endwhile + + " Move cursor to first non-blank of the last formatted line + if mode() !=# 'i' + execute 'normal!' l:bottom . 'G^' + endif + + " Don't change the text if the formatting algorithm failed + if l:tries == 0 + silent! undo + call vimtex#echo#warning('Formatting of selected text failed!') + endif + + let &l:foldenable = l:foldenable +endfunction + +" }}}1 + +function! s:format(top, bottom) " {{{1 + let l:bottom = a:bottom + let l:mark = a:bottom + for l:current in range(a:bottom, a:top, -1) let l:line = getline(l:current) if vimtex#util#in_mathzone(l:current, 1) @@ -63,24 +100,79 @@ function! vimtex#format#formatexpr() " {{{1 if l:line =~# s:border_end if l:current < l:mark - execute 'normal!' (l:current+1) . 'Ggw' . l:mark . 'G' + let l:bottom += s:format_build_lines(l:current+1, l:mark) endif let l:mark = l:current endif if l:line =~# s:border_beginning if l:current < l:mark - execute 'normal!' l:current . 'Ggw' . l:mark . 'G' + let l:bottom += s:format_build_lines(l:current, l:mark) endif let l:mark = l:current-1 endif - endwhile - if l:top <= l:mark - execute 'normal!' l:top . 'Ggw' . l:mark . 'G' + if l:line =~# '^\s*$' + let l:bottom += s:format_build_lines(l:current+1, l:mark) + let l:mark = l:current-1 + endif + endfor + + if a:top <= l:mark + let l:bottom += s:format_build_lines(a:top, l:mark) endif - let &l:foldenable = l:foldenable + return l:bottom +endfunction + +" }}}1 +function! s:format_build_lines(start, end) " {{{1 + " + " Get the desired text to format as a list of words + " + let l:words = split(join(map(getline(a:start, a:end), + \ 'substitute(v:val, ''^\s*'', '''', '''')'), ' '), ' ') + if empty(l:words) | return 0 | endif + + " + " Add the words in properly indented and formatted lines + " + let l:lnum = a:start-1 + let l:current = repeat(' ', VimtexIndent(a:start)) + for l:word in l:words + if len(l:word) + len(l:current) > &tw + call append(l:lnum, substitute(l:current, '\s$', '', '')) + let l:lnum += 1 + let l:current = repeat(' ', VimtexIndent(a:start)) + endif + let l:current .= l:word . ' ' + endfor + if l:current !~# '^\s*$' + call append(l:lnum, substitute(l:current, '\s$', '', '')) + let l:lnum += 1 + endif + + " + " Remove old text + " + silent! execute printf('%s;+%s delete', l:lnum+1, a:end-a:start) + + " + " Return the difference between number of lines of old and new text + " + return l:lnum - a:end +endfunction + +" }}}1 + +function! s:compare_lines(new, old) " {{{1 + let l:min_length = min([len(a:new), len(a:old)]) + for l:i in range(l:min_length) + if a:new[l:i] !=# a:old[l:i] + return l:i + endif + endfor + return l:min_length endfunction " }}}1 diff --git a/indent/tex.vim b/indent/tex.vim index b0a3c20..687f10d 100644 --- a/indent/tex.vim +++ b/indent/tex.vim @@ -17,31 +17,31 @@ let s:cpo_save = &cpo set cpo&vim setlocal autoindent -setlocal indentexpr=VimtexIndent() +setlocal indentexpr=VimtexIndent(v:lnum) setlocal indentkeys& setlocal indentkeys+=[,(,{,),},],\&,=item -function! VimtexIndent() " {{{1 - let l:nprev = s:get_prev_line(prevnonblank(v:lnum - 1)) +function! VimtexIndent(lnum) " {{{1 + let l:nprev = s:get_prev_line(prevnonblank(a:lnum - 1)) if l:nprev == 0 | return 0 | endif " Get current and previous line and remove comments - let l:cur = substitute(getline(v:lnum), '\\\@