Use vim code folding markers to generate index (contents) - vim

In my .vimrc I have the lines
:set foldmethod=marker
:set foldmarker=SECTION:,ENDSECTION:
for custom code folding. In my files the comment character in the respecting language preceeds the code folding marker and they are followed by the title of the corresponding section. E.g.
# SECTION: First Section
some code
# SECTIION: Subsection
some more code
# ENDSECTION:
# ENDSECTION:
# SECTION: Second Section
some other code
# ENDSECTION:
This structure has all whats needed to generate a content for the file like
First Section
Subsection
Second Section
(ideally this index has markers similar to the vim help system so I can jump to the corresponding SECTION easily; I have no idea how to achieve this).
I can think of an easy perl script which generates this text, but I would prefere a solution based on a vim script which shows the index in a new window. Maybe there exists a solution already which does this?

Put this in your vimrc and run :MkIdx or <leader>z. You can also pass a range to the command, but the default is the whole buffer.
function! MkIdx() range
let items = filter(getline(a:firstline, a:lastline), 'v:val =~ ''\C^\s*#\s\+SECTION''')
new
call setline(1, map(items, 'substitute(v:val, ''^\(\s*\)[^:]\+:\(.\+\)'', ''\1\2'', '''')'))
" Mapping to jump to section:
nnore <silent><buffer><leader>x :<C-U>call Go2Section()<CR>
endfunction
function! Go2Section()
let section = matchstr(getline('.'), '^\s*\zs.*')
quit
call search('\C# SECTION:\s\+'.section, 'cw')
endfunction
command! -bar -range=% MkIdx <line1>,<line2>call MkIdx()
" Mapping to build the index:
nnore <silent><leader>z :<C-U>MkIdx<CR>
Edit: Put index on a new buffer.
Edit 2: Don't leave an empty line.
edit 3: Allow jumping back to sections with <leader>x.

Related

Is it possible to change the Vim background color after a certain line in the file (`private` in a Ruby class definition)?

I would love to be able to see in Vim whether I'm in the "private" section of a Ruby class definition or not. Class definitions look like this:
class Foo
def a_public_method
end
private
def a_private_method
end
end
Would it be possible to get Vim to change the background color of the private line and everything after?
A possible solution is:
:call matchadd('Error', '^.*private\_.\{-}\(^end\)\#=')
assuming the end closing class starts always at the beginning of a line (not a very elegant solution).
You can use another highlight group listed by :hi instead of 'Error'.
I've been trying to mark private methods in ruby for years, but I always start and stop, and, now, I finally have a reasonably-working idea, thanks to your question. So, thanks for that.
Now, first off, the ideal solution would use Vim's built-in highlighting to extend ruby's syntax file. The way I'd imagine this would work is by defining a syntax region (:help syn-region) that starts with a "private" and ends with an "end". The tricky bit is that the "private" needs to have been marked with a particular syntax group, rubyAccess, or it might just be any "private" in a string, or comment. And the end needs to be rubyClass, or it might be closing not a class, but a method's def or a do.
Unfortunately, I couldn't figure out how to pull this off. I can't find a way to define a syntax region from a particular group to another one. If you want to study syntax highlighting, and maybe someday come up with a solution to the problem, here's ruby's syntax highlighting: https://github.com/vim-ruby/vim-ruby/blob/f792ee3b2d005660da2e7303e43721ef222d7047/syntax/ruby.vim
Now, that said, after I defined the problem in the terms above, I realized I could just find the relevant "private" and "end" keywords, with the right syntax groups, upon saving the buffer or something. Considering you're thinking of an explicit command anyway, this might be a feasible solution to you as well.
I have a gist here, if you find that more readable: https://gist.github.com/AndrewRadev/d848ad9621b47b4151bbc61ab6c5765f. It needs to go in ~/.vim/ftplugin/ruby.vim. Here it is, annotated with some comments:
" Define what color the private area will be
hi rubyPrivateArea ctermbg=darkgray
function! s:MarkPrivateArea()
" Clear out any previous matches
call clearmatches()
" Store the current view, in order to restore it later
let saved_view = winsaveview()
" start at the last char in the file and wrap for the
" first search to find match at start of file
normal! G$
let flags = "w"
while search('\<private\>', flags) > 0
let flags = "W"
if s:CurrentSyntaxName() !~# "rubyAccess"
" it's not a real access modifier, keep going
continue
endif
let start_line = line('.')
" look for the matching "end"
let saved_position = getpos('.')
while search('\<end\>', 'W') > 0
if s:CurrentSyntaxName() !~# "rubyClass"
" it's not an end that closes a class, keep going
continue
endif
let end_line = line('.') - 1
call matchadd('rubyPrivateArea', '\%'.start_line.'l\_.*\%'.end_line.'l')
break
endwhile
" restore where we were before we started looking for the "end"
call setpos('.', saved_position)
endwhile
" We're done highlighting, restore the view to what it was
call winrestview(saved_view)
endfunction
function! s:CurrentSyntaxName()
return synIDattr(synID(line("."), col("."), 0), "name")
endfunction
augroup rubyPrivateArea
autocmd!
" Initial marking
autocmd BufEnter <buffer> call <SID>MarkPrivateArea()
" Mark upon writing
autocmd BufWrite <buffer> call <SID>MarkPrivateArea()
" Mark when not moving the cursor for 'timeoutlen' time
autocmd CursorHold <buffer> call <SID>MarkPrivateArea()
augroup END
At a high level: It defines a function, s:MarkPrivateArea that performs the actual searching and marking of the area. The s: indicates it's script-local, so it doesn't pollute the global namespace. It's later called in autocommands as <SID>MarkPrivateArea -- it's the same function.
The function starts from the end of the file, loops around, and then forbids looping, letting it cover the entirety of the file, only once. It looks for "private" with the right syntax group, finds the very next "end" that closes a class, and saves the start_line and end_line for that region. You can play around with adding or removing - 1 in order to decide if your "region" should include or exclude the "private" and whether it should include or exclude the final "end".
(Side note: for this to work, you need to have "expensive" syntax highlighting, but that should be on by default. Relevant docs: https://github.com/vim-ruby/vim-ruby/blob/f792ee3b2d005660da2e7303e43721ef222d7047/doc/ft-ruby-syntax.txt#L71)
In the end, the start and end lines are combined into a pattern that looks like this:
'\%<start-line>l\_.*\%<end-line>.'l'
Try :help \%l for more info, but that's basically a regex pattern that matches code in a particular line of the file. And \_. is the multiline form of .. So, it matches everything from the start line to the end line.
If you wanted to change that so that only the defs got highlighted, it would look like this:
call matchadd('rubyPrivateArea', '\%>'.start_line.'l\<def\>\%<'.end_line.'l')
That's what I'll be doing, at least, but you did say you wanted the entire area marked.
The autocommands at the end run the function on startup, upon writing the file, and upon holding the cursor for some time. You could remove some of these, if you like. For instance, you might be okay even without the CursorHold one, if you save often.
As for the rubyPrivateArea highlight group at the top of the file, there's a full explanation on how you can set its colors with :help highlight-args. You could also take a look at your favorite colorscheme for some examples.

Is it possible to have Ctrl-K cut/uncut functionality in Vim like there is in Nano and Pico?

In case you're not familiar with how it works, in Pico and Nano you can hit ctrl-k multiple times and it will add each line to the clipboard. Then you can ctrl-u to "uncut" this. It's a very useful command. Vim does something similar with the dd command, but it only works one line at a time. Thus, you have to use visual mode to properly accomplish the above.
I couldn't find a good answer online so I rolled my own solution. You can add this to your vimrc file:
imap <C-k> <Esc>:execute #a ? 'normal! "Bdd' : 'normal! "bdd'<cr>:let #a=1<cr>:echo ''<cr>i
imap <C-u> <Esc>"bPi
autocmd CursorMovedI * execute(':let #a=0')
The register #a is used to track whether or not the cut line should be appended. The register #b is used as the clipboard register. Whenever the cursor position changes, you stop being in "append" mode. Thus, you can hit ctrl-k over and over to keep appending lines, but as soon as you move the cursor you go back to normal. I'm pretty sure this is how Nano and Pico implement it under the hood.
Is anyone aware of a cleaner solution?
Intro to Registers
The Vim commands you are looking for are delete/cut, dd and put/paste, p. Each of these commands by default use the unnamed register, "". So dd will delete a line and put the freshly deleted line into the unnamed register. p will put the contents from the unnamed register into your current buffer.
Vim has more than just the unnamed register, it also has named registers. These registers are a-z.
To see what your registers are set to you can execute :registers.
To use a named register prefix your command with quote and a lowercase letter, e.g. "add
Upper case letters will append instead of replace the contents of a register, e.g "Add"
The Vim Way
"add the first line, then append the next line via "Add. For repeated deletions use .
Use a text object. e.g. dap for delete a paragraph. See :h text-objects
Delete text via some motion. e.g. d} is delete till end of paragraph
Delete to a regex patter. e.g. d/foo<cr>
Lastly would be using visual mode, V6jd. Nothing wrong with using visual mode
Additionally you want to stay out of insert mode unless you are inserting text. You only want to be in insert move for short burst at a time. Most of the time you should be in Normal mode, hence the name.
For a nice post on the Vi/Vim way see this StackOverflow post: Your problem with Vim is that you don't grok vi.
Alternatives
If none of the standard Vim tricks satisfy your needs you may want to look into plugins that support line exchanging like Unimpaired or LineJuggler.
However if you really do want something similar this nano/pico features you can use the following by putting it in your ~/.vimrc file:
nnoremap Q :<c-u>call <SID>DeleteAppend()<cr>
function! s:DeleteAppend()
let save = #a
let #a = ##
let reg = get(g:, 'append_tick', -1) == b:changedtick ? 'A' : 'a'
execute 'normal! "' . reg . 'dd'
let g:append_tick = b:changedtick
let ## = #a
let #a = save
endfunction
The Q normal command will now delete a line and append additional deleted lines until a another command is executed. Example usage: QQjjQQQp
For more help please see
:h d
:h p
:h "
:h registers
:h :reg
:h motion
:h text-objects
:h .
:h visual-mode
Yes, there are many cleaner solutions:
12dd{motion}p " cut 12 lines, move elsewhere, paste
d5j{motion}p " cut from here to 5 lines down, move elsewhere, paste
d/foo<CR>{motion}p " cut from here to next 'foo', move elsewhere, paste
:.,23d<CR>:12put<CR> " cut from here to line 23, paste after line 12
:.,+5d<CR>:0put<CR> " cut from here to fifth line below, paste at top of buffer
or the truly amazing:
:.,11m35<CR> " move the lines between this one and 11 to after line 35
You can even do:
Vjjjjjjjjjjd{motion}p
One of the big advantages of Vim over other editors is its expressive language: don't think in "nano", think in "Vim".

Is there a better way to create a line of some character in Vim? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How can I a put a line like “==========” quickly in vim
EDIT
Turns out this question is an exact duplicate of How can I a put a line like "==========" quickly in vim - but that one doesn't have such a great title...
When editing markdown style files, I like to have
Some heading
------------
Notice the line matches the length of the heading?
Currently I do:
yyp:s/./-/g
But I was wondering if there is any better (less keystrokes) way to do this?
As I've just discovered, you can do it in a few less key strokes:
yypVr- - 7, if you include the shift key.
I'm no Vim expert, but in any text editor creating and using macros helps me reduce keystrokes.
In normal mode, with the cursor placed on the line you want to underline, start recording a macro to buffer (a in this instance)
q a <enter>
Now, perform said commands. In this case I've finished by inserting a new line after the series of '-' characters. Before to leave insert mode before hitting q again to finish.
yyp:s/./-/gA<enter><esc>q
To underline any heading you might have, be in Normal mode with cursor on that line, and type:
#a<enter>
Keystrokes
Before: 18
After: 3
Savings of: 15!
AFAIK buffers can be assigned to 'a' through 'z'
Happy Vimming
I have added the following code to my ~/.vimrc for create just such headers. All I do is execute ch= on a line and it will create a line below filled with ='s. You could easily do ch- to create a header line of -'s as well.
"""" CreateHeaderLine
" Easily create heading mapping
" The following line is an example
" ================================
" mnemonic ch: create header
nnoremap <silent> ch :call CreateHeaderLine('ch' ,'.')<cr>
nnoremap <silent> cH :call CreateHeaderLine('cH', '-')<cr>
function! CreateHeaderLine(mapping, address)
let c = nr2char(getchar())
exe 't ' . a:address
exe "norm! v$r" . c
silent! call repeat#set(a:mapping . c)
endfunction

Vim: Delete Buffer When Quitting Split Window

I have this very useful function in my .vimrc:
function! MyGitDiff()
!git cat-file blob HEAD:% > temp/compare.tmp
diffthis
belowright vertical new
edit temp/compare.tmp
diffthis
endfunction
What it does is basically opening the file I am currently working on from repository in a vertical split window, then compare with it. This is very handy, as I can easily compare changes to the original file.
However, there is a problem. After finishing the compare, I remove the split window by typing :q. This however doesn't remove the buffer from the buffer list and I can still see the compare.tmp file in the buffer list. This is annoying because whenever I make new compare, I get this message:
Warning: File "temp/compare.tmp" has changed since editing started.
Is there anyway to delete the file from buffers as well as closing the vertical split window?
Perhaps you need the bwipe command?
:bw[ipeout][!] N1 N2 ...
Like |:bdelete|, but really delete the buffer. Everything
related to the buffer is lost. All marks in this buffer
become invalid, option settings are lost, etc. Don't use this
unless you know what you are doing.
One option would be to define the following:
function! DelBuf(filename)
let bname = bufname(filename)
if l:bname != ""
let bidx = buffer_number(l:bname)
exec = "bw " . l:bidx
endif
endfunction
and add a call to DelBuf("comapre.tmp") at the beginning of your function.
In theory it should be possible to bind DelBuf to the `bufhidden event like this:
autocmd! bufhidden "compare.tmp" call DelTmp("compare.tmp")
... but for some reason it didn't work for me.
You need to use autocmd winleave bd (buffer delete). Be warned that if you have the buffer open in more than one window they will all be removed.
I usually define the following things for diff-buffers:
setlocal bt=nofile bh=wipe nobl noswf ro
nnoremap <buffer> q :bw<cr>
The first line is what will make the difference in your case (:h 'bh' -> no need for a single execution autcocommand), the second line is just a shortcut.
BTW: use r! git instead of producing a temporary file. This way, you won't have to clear that file either.

How to select a rectangular area in visual block mode (Ctrl+v) in empty file (vim)?

I can easily select a rectangular area in a file provided this area contains text or spaces (or anything). I do it with visual block mode and motion keys.
But when I try to create such area in a new file (or any file without text) I can't.
Is there a way to 'expand' this area by automatically filling it with spaces for example? Or am I going in wrong direction?
The reason I want this:
I create a new file with vim and then would like to create a comment block similar to this:
##############################################
# #
# My comment goes here #
# #
##############################################
I can do it over existing text using Ctrl+v+motion keys, then r# to create an area filled with pounds. Then similarly to cut out inner rectangle with spaces.
How do I use this technique on a new file?
Thanks.
Apart from the (very good) answer from Brian Rasmussen, the only way I know of to do almost exactly what you're asking is to use virtualedit mode. This won't let you edit on non-existent lines, but it will let you edit beyond the end of existing lines. Therefore, to turn the current line into a load of # symbols, you could do this:
:set virtualedit=all
v50lr#
To make a 50x5 block, you could create 4 new blank lines and then do the same:
:set virtualedit=all
4o<ESC>
<C-V>4k50lr#
(where <C-V> means press Ctrl+V and <ESC> means press Esc).
I believe there are some plugins for various file types that make it much easier to create comment blocks like this, but I'm not sure which is best.
You could just do something like:
50i#<ESC>yyo#<ESC>48a<SPACE><ESC>a#<ENTER>#<SPACE><SPACE>My comment goes here<ESC>:exe<SPACE>'normal'<SPACE>(49-getpos('.')[2]).'a<SPACE>'<ENTER>a#<ENTER>#<ESC>48a<SPACE><ESC>a#<ESC>p
But maybe that's just me being silly! I'll leave it as an exercise for the reader to figure out what's going on there if you're interested (:help is your friend).
How about this as a slightly more serious alternative: bung the following in your vimrc or in a file in the plugins directory of the vim runtime folder (e.g. ~/.vim/plugins on Unix)
nmap <F4> :InsertCommentBlock<CR>
command! InsertCommentBlock call InsertCommentBlock()
function! InsertCommentBlock()
let linelength = 50
let linelist = []
call add(linelist, repeat('#', linelength))
call add(linelist, '#' . repeat(' ', linelength-2) . '#')
let comment = input('Please enter a comment: ')
call add(linelist, '# ' . comment . repeat(' ', linelength - (4+len(comment))) . '#')
call add(linelist, '#' . repeat(' ', linelength-2) . '#')
call add(linelist, repeat('#', linelength))
call append(line('.'), linelist)
endfunction
See:
:help function
:help 'virtualedit'
:help command
:help nmap
:help repeat()
:help append()
:help add()
:help getpos()
:help :exe
etc...
If you want to create a block like that in a new file, you could do something like 50i#EscY5P
So it's 50 times insert # followed by yank current line and put it 5 times. This will give you a 50x5 block of #.
Of course you still have to do, whatever you do to get from the block of #s to the comment in your post.
The accepted answer mentions using
:set virtualedit=all
However, then you enable virtualedit in all modes, which might not what you want. There is actually also an option to enable it in just block editing mode:
:set virtualedit=block
This is what I put in my vimrc
I don't think there's any to select text that isn't there, but the easy solution would be to "seed" it with something like 76a yy4p ... so draw 76 spaces, then copy the line 4 extra times.

Resources