vim folds for asciidoc: include the line _previous_ to the section-title line - vim

Summary. How (and if?) can I get vim folds to automatically associate the asciidoc [id=''] lines found immediately prior to a section-title line with the fold created for said section?
Details. asciidoc content enables section-title identification (that, among other things, maps to rendered-HTML-anchor-tag names and also enables intra-document cross-reference), otherwise known (I think?) as a block identifier. However, the [id=''] line blurb must be place prior to the section header line, even though it's part of the section. eg:
[id='under_construction', reftext='Under Construction']
## DISCLAIMER: This Document is Under Construction
This makes vim-folding of an asciidoc file much harder to manage, as folded-section moves "lose" the previous line (and all the section id's get shuffled), because in the eyes of the vim fold, the previous line belongs to the previous section.

I'm not sure how vim foldings asciidoc, but I assume that new section starts with ## (or ==, judging from a brief look at the link you provided), and [id=...] provides additional information on the section.
So you could have a look at Steve Losh's markdown folding and :h fold-expr.
So here is the modified code for markdown folding:
function! Fold_askiidoc(lnum)
let l1 = getline(a:lnum)
if l1 =~ '^\s*$'
" assume sections are separated by blank lines
return '0'
endif
" check next line
let l2 = getline(a:lnum+1)
if l2 =~ '^#'
" next line starts with hashes (or '=', or any symbol)
" number of hashes specifies indent level
return '>'.matchend(l2, '^#\+')
else
" otherwise keep previous foldlevel
return '='
endif
endfunction
setlocal foldexpr=Fold_test(v:lnum)
setlocal foldmethod=expr
It checks each line, if it's blank, suppose it's the end of a section. If the next line starts with #, it means the fold starts on the current line. The amount of #'s specify folding level.
It folds any non-blank line before a section title, if you want it only for specific lines, like id=[''], you would have to add additional string comparison.
You can save it to $HOME/.vim/after/ftplugin/asciidoc.vim. I'm not sure about file type, if it exists or you have to create it separately. From there it should be loaded automatically each time you open specific file. Or you can just put function in your vimrc and specify
setlocal foldexpr=Fold_test(v:lnum)
setlocal foldmethod=expr
as auto-comand for this file type.

Related

vim slows down when using fold-expr

I'm trying to create a simple, fast folding method for large markdown files. I'm using the fold-expr method in vim. For example, if I wanted to start folds on H1 and H2 markdown entries, my vimscript code is:
function! MarkdownLevel()
if getline(v:lnum) =~ '^# '
" begin a fold of level one here
return ">1"
elseif getline(v:lnum) =~ '^## '
" begin a fold of level two
return ">2"
else
return "="
endif
endfunction
This works perfectly, and I get nested folds. However, when I have a large markdown file, vim slows down considerably. This is unsurprising and is, in fact, indicated in the fold-expr help in vim. It's because the = sign tells vim to scan backwards in the file until the first line with an explicitly defined foldlevel can be found; this could be thousands of lines away.
I tried to replace the last line with
else
" set foldlevel to foldlevel of previous line
return foldlevel(v:lnum-1)
endif
But this doesn't work as expected. Does anyone know how to fix this? It's clear I'm not understanding how the foldlevel function works, or how the folding algorithm in vim is implemented.
I figured out how to fix the slowdown and learned somethings about how fold-expr works in vim. I tested the performance issues on a 3000 line md file.
I was relying on the following automatic folding functionality that fold-expr is supposed to have: it starts a fold if foldlevel of the current line is smaller than the foldlevel of the next. It ends a fold if the foldlevel of the current line is larger than the foldlevel of the next. Turns out that this does not work as intended, as far as I can tell.
What worked was to explicitly tell vim that a fold starts here using return ">1", where 1 is replaced by the appropriate number.
After learning how to profile vim scripts from #PeterRinker, I figured out that the return "=" statement was being evaluated many, many times when I was editing line (for example) 3000.
This was my fix: if the foldlevel of the current line does not fall into any of the heading types and the foldlevel of the previous line has already been defined, the current line should just inherit the foldlevel of the previous line. This is an obvious solution, but it does not work if I used return "1" instead of return ">1" above. It needs the return "=" statement on the first pass to figure out the foldlevel.
So my startup times are a little big (about 1 second) for a 3000 line file, but now editing is very smooth. The following is the finished, simplistic code. Other more elaborate markdown projects do not have this useful simplification.
function! MarkdownLevel()
let theline = getline(v:lnum)
let nextline = getline(v:lnum+1)
if theline =~ '^# '
" begin a fold of level one here
return ">1"
elseif theline =~ '^## '
" begin a fold of level two here
return ">2"
elseif theline =~ '^### '
" begin a fold of level three here
return ">3"
elseif nextline =~ '^===*'
" elseif the next line starts with at least two ==
return ">1"
elseif nextline =~ '^---*'
" elseif the line ends with at least two --
return ">2"
elseif foldlevel(v:lnum-1) != "-1"
return foldlevel(v:lnum-1)
else
return "="
endif
end
Have you thought about using Drew Nelstrom's vim-markdown-folding plugin?
You may also want to look that the Vimcast episode: Profiling Vimscript performance. This episode actually talks about folding markdown.
Cautionary thoughts
I can not be for certain because I have not profiled your code (and you should really profile your code), but as the fold expression gets called on every line every time things get redrawn it can be very taxing on Vim. Some guesses:
Using relative fold expressions like = means we need to compute the previous line so as you can imagine this can become problematic. Try and use exact depths without computing other lines if you can.
You are using getline() twice in your function needlessly
Some files are just going to cause problems accept this fact and disable folding via zi
That is to be expected, since Vim has to compute your expression a lot for every line. This is also mentioned in the help below :h fold-expr
Note: Since the expression has to be evaluated for every line,
this fold method can be very slow!
Try to avoid the "=", "a" and "s" return values, since Vim often
has to search backwards for a line for which the fold level is
defined. This can be slow.

Comment / uncomment multiple fixed lines in vim

In my code I have multiple scattered lines which help me to debug my program and show me what is going on during execution. Is there an easy and fast way to comment and uncomment (toggle) these fixed lines in vim? I thought about marking these lines with a special sign (e.g. //) like this in python:
print "Debug!" # //
and everytime a sepcific shortcut is pressed all lines which end with a "# 'some optional descriptive text' //" are commented or uncommented, respectively.
I've looked at NERD Commenter, but from what I read the lines to be commented / uncommented have to be selected each time?
First, find a pattern that selects the right lines. If you have :set hls, it will help spot the matches. I think something like /#.*\/\/$ is what you want.
Next, comment out the selected lines with
:g/<pattern>/s/^/# /
if # will comment out the line, and un-comment them with
:g/<pattern>/s/^# //
Now, you want a single command to toggle. You can either use a variable to keep track of the toggle state, or you can try to figure out the current state by examining the lines that match. Using a variable seems simpler.
The variable could be global, local to the buffer, or local to the script. I like using script-local variables in order to avoid cluttering the namespace. In this case, using a script-local variable might mean that vim will get confused when you switch buffers, so let's use a buffer-local variable, say b:commentToggle.
The first time the function is called, it notices that the variable is not set, so use search() to look for a line that starts with # (There is a space there!) and ends with the pattern we already have. The n flag means not to move the cursor, and w means to wrap around the end of the file (like searching with 'wrapscan' set). The search() function returns the line number (1-based!) if the pattern is found, 0 if not. See :help search().
This seems to work in a small test:
fun! CommentToggle()
if !exists('b:commentToggle')
let b:commentToggle = !search('^# .*#.*\/\/$', 'nw')
endif
if b:commentToggle == 1
g/#.*\/\/$/s/^/# /
else
g/#.*\/\/$/s/^# //e
endif
let b:commentToggle = !b:commentToggle
endfun
nnoremap <F4> :call CommentToggle()<CR>
If you want to put # in front of the first non-blank, then use ^\s*# in the search() command; s/\ze\S/# / or s/\S/\1# / in the first :g line; and s/^\s*\zs# // in the second :g line. See :help /\zs, :help /\ze, and :help sub-replace-special.

Using Vim, isn't there a more efficient way to format LaTeX paragraphs according to this best practice?

The best practice mentioned in the title is the one suggested by Uri:
When writing paragraphs, start each
sentence at the beginning of a line,
and if it spills over, each subsequent
line is tabbed.
I use gVim with Vim-LaTeX, which comes with an indent/tex.vim file, to edit LaTeX files. The way I currently implement the practice mentioned above is as follows:
I :set textwidth=79 to automatically break lines before they become too long.
I manually hit Enter after I finish inserting each sentence.
If I'm done with revising and editing a sentence, I manually shift any spillovers using >>, prefixing it with a count if necessary.
Occasionally, that last step will make one or more spillovers go over the maximum line width. In this case, I
gqq the faulty line.
J my way through to the end of the sentence.
repeat steps 1 and 2 as necessary.
As you can imagine, this can become tedious. Isn't there a more efficient way to achieve the same result? Ultimately, I want to be able to write the sentences without worrying about their format, and then use gqap, or gqip, to automatically produce the result that I currently produce manually.
To do that, I suspect that I will need to write a formatexpr of my own, but I'm not sure how to proceed. I have found a number of plugins, Latex Text Formatter and Text (Especially LaTeX) Formatter, and a tip, but none of them seem to suit my needs, and I'm not sure how to modify them to do so.
I may well be oversimplifying the problem, but does this mapping do what you want?
nnoremap \z (j>>gq)
So pressing \z in normal mode will do the following: From the cursor position, jump to the start of the sentence. Then go to the next line and indent it. Then reformat from this line to the end of the sentence. Reformatting sentence-wise is the way to go, rather than reformatting each line individually, as your method seems to do.
Of course you can use an insert-mode mapping if you prefer, or even try redefining the behaviour of the Enter key to do this automatically (although I don't know if this will have unintended consequences...).
One way to do this is not by actually breaking the lines in the file but instead doing the following:
set wrap linebreak
let &showbreak='===> '
The wrap option makes long lines wrap instead of extending off the screen and linebreak makes the line breaks happen only at characters specified in the breakat option.
You can set showbreak to anything that is pleasing to your eye. My favorite when I'm using vim where unicode characters work right is:
let &showbreak="\u21aa "
This puts a ↪ symbol at the beginning of each wrapped line.
I also like to turn on line numbers (set number) to give another indicator of what the actual lines in the file are.
To make navigating the file easier you might want to use
noremap j gj
noremap k gk
noremap gj j
noremap gk k
This makes k and j move up and down by displayed lines not file lines. To affect the cursor keys as well replace k with <Up> and j with <Down>.
One option that takes different tack than tabbing subsequent lines would be to set the w flag in formatoptions. When you do that it changes the way Vim identifies new paragraphs, and lines ending in a space are understood to continue on a new line as part of same paragraph. See :h fo-table.
If you set the w flag and enter your text so that continued sentence lines are the only ones ending in a space (and abandon completely practice of entering tabs at beginning of any text lines) then I think you should be able to use gqap to format text paragraphs as you want. To get visual cues to logical structure you can then set listchars to display the eol (i.e., <cr>) character and set different highlightings for <space><cr> and for <non-space><cr> so that sentence/paragraph ends are easily spotted.
Another benefit of this method is that you can just type your text naturally and let line breaks be entered automatically by textwidth setting. (Just make sure that LaTeX formatting lines don't break automatically in textwidth area; you want them to have non-space char as last char in line.)
That tip also caught my eye. Here's how I solved the problem (a diff of the changed lines in tex.vim):
*** tex.vim.old 2011-08-16 08:26:56.845046457 +0200
--- tex.vim 2011-08-16 08:59:14.736306930 +0200
***************
*** 90,95 ****
--- 90,96 ----
" LH modification : \begin does not always start a line
if line =~ '\\begin{\(.*\)}' && line !~ 'verbatim'
\ && line !~ 'document'
+ \ || line =~ '^\s*[A-Z].*[a-zA-Z0-9,]\s*$\C'
let ind = ind + &sw
***************
*** 105,110 ****
--- 106,112 ----
" Subtract a 'shiftwidth' when an environment ends
if cline =~ '^\s*\\end' && cline !~ 'verbatim'
\&& cline !~ 'document'
+ \|| line =~ '\.\s*$'
if g:tex_indent_items == 1
" Remove another sw for item-environments
Basically it indents new lines when the previous line starts with a capital letter and ends with a letter, digit, or comma, and "unindents" new lines with the previous line ends with a period.
There is definitely room for improvement (better criteria) but for me it works all right so far.
I find the suggestion from #kev (and the people commented) at this post to be the most satisfying.
There, it is explained that by setting
:set fo+=n
followed by either
:let &flp='^\s*\\(item\|end\|begin)*\s*'
or
:let &l:flp='^\s*\\\(item\|end\|begin\)\s*'
lets you type gggqG to reformat the entire file.
I use the vim-textobj-usr plugin to define a "LaTeXPar" text-object. Then I can use gwal to format.
There is already a vim-textobj-latex plugin, but the biggest text-object it defines is "environment". This is not what I (and OP) want.
A "LaTeXPar" is delimited by
an empty line
a line begin with \[, \], \begin, \end, }
a line end with {
this is adapted to my writing habit: I always have an empty line after \section, always use \[ \] on a single line, and so on. You can easily write one for yourself.
Here is the relative part in my ~/.vim/ftplugin/tex.vim.
call textobj#user#plugin('latexpar', {
\ 'par': {
\ 'select-a-function': 'LaTeXPar',
\ 'select-a': 'al',
\ },
\ })
function! LaTeXPar()
let pattern='\v^$|^\s*(\\\[|\\\]|\\begin|\\end|\})|\{$'
if search(pattern,"bW")
normal! j
else
normal! gg
endif
let head_pos = getpos('.')
if search(pattern,"W")
normal! k
else
normal! G
endif
let tail_pos = getpos('.')
" echo head_pos[2]
" echo tail_pos[2]
return ["V", head_pos, tail_pos]
endfunction

How to diff two lines in an open file in vim?

I occasionally see very long lines in my code that I need to check if they are the same. Is there a way in vim to select two lines and diff them to show any differences between the two?
For example, given the two lines in vim:
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *lists);
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *1ists);
I would like vim to tell me that the two lines are in fact different because each spells "lists" differently. Is this possible, and if so, how do I do it?
A quick and dirty solution is to just select both lines and sort them while removing duplicates:
select lines
":sort u"
if only one line remains, both were equal
if both remain, there most be some difference
An undo recovers everything again.
An alternative to #sehe's approach would not require the use of temp files:
funct! DiffTwoTexts(text1, text2)
new
put =a:text1
normal ggdd
diffthis
new
put =a:text2
normal ggdd
diffthis
endfunct
funct! DiffTwoLines(line1, line2)
let text1 = getline(a:line1)
let text2 = getline(a:line2)
call DiffTwoTexts(text1, text2)
endfunct
comma! DiffWithNext call DiffTwoLines('.', line('.') + 1)
This will still be pretty hard to read, since it keeps everything on a single line, so I came up with this modification:
funct! EvalTextPreprocessor(expr, text)
let text = a:text
return eval(a:expr)
endfunct
comma! -nargs=1 DiffWithNextPre call DiffTwoTexts(
\ EvalTextPreprocessor(<q-args>, getline('.')),
\ EvalTextPreprocessor(<q-args>, getline(line('.') + 1)))
This new command takes a vimscript expression as its argument, wherein the variable text refers to whichever line is being preprocessed. So you can call, e.g.
DiffWithNextPre split(text, '[(,)]\zs')
For your sample data, this gives the two buffers
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *lists)
;
and
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *1ists)
;
Only the lines that start with Parameter are highlighted.
You can even build up from there, creating a command
comma! DiffTwoCFunctionSigs DiffWithNextPre split(text, '[(,)]\s*\zs')
Notice that I modified the regexp a bit so that it will keep trailing spaces at the end of lines. You could get it to ignore them entirely by moving the \s* to after the \zs. See :help /\zs if you're unfamiliar with what that vim-specific RE atom does.
A nicety would be to make the command take a range (see :help command-range), which you could use by diffing the first line of the range with the last line. So then you just visual-select from the first line to the second and call the command.
I used linediff.vim.
This plugin provides a simple command, ":Linediff", which is used to diff two separate blocks of text.
That is not a feature, however it is easily scripted, e.g. in your vimrc:
function! DiffLineWithNext()
let f1=tempname()
let f2=tempname()
exec ".write " . f1
exec ".+1write " . f2
exec "tabedit " . f1
exec "vert diffsplit " . f2
endfunction
This will open the current and next lines in vertical split in another tab.
Note that this code is a sample
it doesn't check whether next line exists (there are any following lines)
it doesn't cleanup the tempfiles created
a nice improvement would be to take a range, or use the '' mark to select the other line
You can leave off the 'vert' in order to have a horizontal split
Map it to something fancy so you don't have to :call it manually:
:nnoremap <F10> :call DiffLineWithNext()^M
you could also just create a new empty window buffer and copy line, then make command:
:windo diffthis
this should open a new window showing the differences of those 2 lines

VIM: Zero-Indexed Line Numbers in :set number

Using VIM, it is easy to display line numbers for any given file with:
:set number
However, the line numbering is 1-indexed, this means that the numbering starts at 1.
Normally this is exactly what i want, however a particular tool I am using to compile my code reports zero-indexed line numbers - that is, line numbers start at zero.
How do I change vim's line numbering to compensate so that viewing the numbers in the vim buffer corresponds to the errors provided by the tool, and in addition jumping to a particular number corresponds to that of the tool as well.
That is, if the tool tells me that there is an error on line 98, I want to jump to that line by typing "98G", not "97G", and I want that line (which is really line 97 in vim) to display "98" in the line number list.
Edit:
While I can filter the output of the tools, while fairly simple it is not a trivial task because the tool also outputs hex values that correspond to each line, which has the same zero-indexed form, and this is is output in informational messages as well, not just for errors, and I have many such projects.
I don't think this is possible; however, assuming the output of your external tool is just text, it would probably be fairly easy to filter the output such that the numbering is 1-indexed to match Vim. Can you give us an example of your output?
Edit
Alternatively, if you call the external command from Vim, you could do something like (basically, add a blank line, run the external command and then delete the blank line):
command! RunMyExternalProgramme call RunMyExternalProgramme()
func! RunMyExternalProgramme()
" Save the old setting of makeprg
let s:savedMakePrg = &makeprg
" Save the screen layout
let s:savedView = winsaveview()
if config_file != ''
" Put a blank line at the start of the file
:1put! =''
" Save
write
" Change makeprg and run it
let &makeprg = '/path/to/programme -options etc'
make
" Delete the blank line
1d
" Save
write
endif
" Restore the screen layout (optional)
call winrestview(s:savedView)
" Restore the old setting of 'makeprg'
let &makeprg = s:savedMakePrg
endfunc
I suggest to wrap your tool in a small script. In the script, either copy the source code and add an empty line at the top of the source or use awk to fix the output (parse the error messages and add 1).
The only way to make vim display line numbers starting with 0 is to patch the source and create a new option (say lno for line number offset) and add this value to the line number. You'd need to patch this in quite a few places (display, goto commands, search patterns, etc). Shouldn't take more than one or two years to make it work :) Good luck on getting the author of Vim to accept this as a patch.
set relativenumber or for short "set rnu"
This starts the count from 0

Resources