Vim - add one line above the fold to the folding - vim

When I am coding in vim, I use set foldmethod=syntax which folds my code.
It looks something like this then:
How can I add the line above the { to the fold? So that it would look something like this:
So the idea is that it (always) takes the line above the fold into the fold.
How can I make this happen?

This needs a custom fold expression (h fold-expr) indenpendently from:
foldtext <- you want to fold one more line
foldignore <- you still want to fold { and } whatever the indent
In your vimrc:
" Callback: Fold level <- next line indent
function! FoldMethod(lnum)
let l:indent = max([indent(a:lnum+1), indent(a:lnum)])
return l:indent / &shiftwidth
endfunction
set foldmethod=expr
set foldexpr=FoldMethod(v:lnum)

Related

Vim getline() equivalent for current display/screen line?

Are there any vim functions, similar to getline(), that can access the current display line of a wrapped line of text in a buffer? Alternatively, are there any functions similar to col(), getpos() or getcurpos() that I could use to return the start/end column of the current display/screen line, from within a script?
I realize I could move the cursor around with g0 and g$, and then get positions after such moves. However, I'm specifically looking for a built-in or custom function that does NOT move the cursor. This is being used in a complex operator-pending mapping, so it's best for the cursor to stay put.
After some tinkering, this seemed to be the best available method for finding the bounds of the current screen line, in the absence of other answers. Alas, it does move the cursor, but restores its original position. The function returns a list of the beginning-of-line and end-of-line column indices, like [1, 25].
function! DispLineCols()
"display line columns: [bol, eol] columns for current screen line
let r = []
let p = getcurpos()
normal! g0
call add(r, col('.'))
normal! g$
call add(r, col('.'))
call setpos('.', p)
return r
endfunction
I wrote the following function to return a list of lists (in the same [bol, eol] format indicated above) for all screen lines that the current wrapped line comprises. For example, for a wrapped line that comprises three screen lines, this might return: [[1, 79], [80, 159], [160, 180]]
function! DispLinesCols()
"display lines columns: [[bol1, eol1], [bol2, eol2], ...] columns for
" all screen line wraps of current line
let r = []
let p = getcurpos()
normal! 0
call add(r, DispLineCols())
normal! g$l
while col('.') != r[-1][1]
call add(r, DispLineCols())
normal! g$l
endwhile
call setpos('.', p)
return r
endfunction
Not a perfect (or even elegant) solution, but gets the bounds of the display/screen line well enough. Still looking for better solutions...
EDIT: For completeness, here are two functions that will turn the column [bol, eol] output from the functions above into the actual text of the lines.
function! DispLineText()
"display line text: the text on the current screen line
let [beg, end] = DispLineCols()
return getline('.')[beg-1 : end-1]
endfunction
function! DispLinesText()
"display lines text: list of screen lines comprising the current line
let r = []
let l = getline('.')
for [beg, end] in DispLinesCols()
call add(r, l[beg-1 : end-1])
endfor
return r
endfunction

Jump to the end of a long list of repeated pattern

I have a big file with a lot of lines that share the same pattern, something like this:
dbn.py:206 ... (some other text) <-- I am here
dbn.py:206 ... (some other text)
...
(something I don't know) <-- I want to jump here
Is there a quick way in Vim to jump to the place where the succession of dbp.py:206 ends?
/^\(dbn.py\)\#!
Matches first line which does not start with the text inside the escaped parentheses.
If you want quick access to this you could add a vmap which yanks the visually selected text and inserts it in the right spot (but first escaping it with escape(var, '/').
Try this vmap: vmap <leader>n "hy<Esc>/^\(<C-R>=escape(#h,'/')<CR>\)\#!<CR>
Press n when visually selecting the text you wish to skip and you should be placed on the next first line which does not begin with the selection.
I just write a function to select identical lines:
nnoremap vii :call SelectIdenticalLines()<CR>
fun! SelectIdenticalLines()
let s = getline('.')
let n = line('.')
let i = n
let j = n
while getline(i)==s && i>0
let i-=1
endwhile
while getline(j)==s && j<=line('$')
let j+=1
endwhile
call cursor(i+1, 0)
norm V
call cursor(j-1, 0)
endfun
type vii to select identical lines (feel free to change the key-binding)
type zf to fold them.
type za to toggle folding
It's handy when you want to squeeze several empty line.
It acts like C-x C-o in emacs.
One option is to go to the bottom of the file and search backwards for the last line you want, then go down one:
G ?^dbn\.py:206?+1

How to define a new Vim operator with a parameter?

I have been looking to map a new operator in Vim that takes an extra parameter.
For example, we know that ciw will “cut inside word” and will put you into Insert mode. What I am looking for is having a custom action to replace c (for example, s) that takes movements like iw, but requires an extra parameter.
A trivial example would be:
Given a line in a text file
Execute siw* in Normal mode (assuming the cursor is on the first column) for it to surround the first word with * like so:
*Given* a line in a text file
I know, this is what the most excellent surround.vim plugin does. But I am just giving an example here, and looking for an answer as to how to get the mappings so that the above work.
I tried playing with onoremap and opfunc, but can’t seem to get them to play the way I want.
So, what I am looking for is a combination of motions plus operator pending mappings.
Here is an example implementation of the command described
in the question, for illustrative purposes.
nnoremap <silent> s :set opfunc=Surround<cr>g#
vnoremap <silent> s :<c-u>call Surround(visualmode(), 1)<cr>
function! Surround(vt, ...)
let s = InputChar()
if s =~ "\<esc>" || s =~ "\<c-c>"
return
endif
let [sl, sc] = getpos(a:0 ? "'<" : "'[")[1:2]
let [el, ec] = getpos(a:0 ? "'>" : "']")[1:2]
if a:vt == 'line' || a:vt == 'V'
call append(el, s)
call append(sl-1, s)
elseif a:vt == 'block' || a:vt == "\<c-v>"
exe sl..','..el 's/\%'..sc..'c\|\%'..ec..'c.\zs/\=s/g|norm!``'
else
exe el 's/\%'..ec..'c.\zs/\=s/|norm!``'
exe sl 's/\%'..sc..'c/\=s/|norm!``'
endif
endfunction
To get user input, the function InputChar() is used, assuming that
the required argument is a single character.
function! InputChar()
let c = getchar()
return type(c) == type(0) ? nr2char(c) : c
endfunction
If it is necessary to accept a string argument, change the call to
InputChar() in Surround() to the call to input(), instead.
The title of the question might cause misunderstanding. What you want to do is to define a new operator like y, d and c, neither motions nor text objects, isn't it?
:help :map-operator describes how to define a new operator. To take a parameter like the surround plugin, use getchar() in your 'operatorfunc'.
Though :help :map-operator describes the basics, it's a bit troublesome to deal with arguments passed to 'operatorfunc'. You can use vim-operator-user to simplify the handling of arguments. With this plugin, surround-like operator can be written as follows:
function! OperatorSurround(motion_wise)
let _c = getchar()
let c = type(_c) == type(0) ? nr2char(_c) : _c
if c ==# "\<Esc>" || c == "\<C-c>"
return
endif
let bp = getpos("'[")
let ep = getpos("']")
if a:motion_wise ==# 'char'
call setpos('.', ep)
execute "normal! \"=c\<Return>p"
call setpos('.', bp)
execute "normal! \"=c\<Return>P"
elseif a:motion_wise ==# 'line'
let indent = matchstr(getline('.'), '^\s*')
call append(ep[1], indent . c)
call append(bp[1] - 1, indent . c)
elseif a:motion_wise ==# 'block'
execute bp[1].','.ep[1].'substitute/\%'.ep[2].'c.\zs/\=c/'
execute bp[1].','.ep[1].'substitute/\%'.bp[2].'c\zs/\=c/'
call setpos('.', bp)
else
endif
endfunction
call operator#user#define('surround', 'OperatorSurround')
map s <Plug>(operator-surround)
If you really want to define your own text objects, please consider vim-textobj-user.
Consider one of the plugins for writing custom text objects. For example:
https://github.com/kana/vim-textobj-user

Tune cindent "switch" indentation

Nemerle is a C-like language and mostly works very well with cindent. However, its construct analogous to switch is called match:
match (x) // switch (x)
{ // {
| "Hello World" => ... // case "Hello World": ...
| _ => ... // default: ...
} // }
Is it possible to get the cinoptions for switch statements to apply to this construct, instead? Maybe there is a regular expression I can set somewhere. If not, can I get the vertical bars to align with the braces another way?
Update
Here is what I came up with:
" Vim indent file
" Language: Nemerle
" Maintainer: Alexey Badalov
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
" Nemerle is C-like, but without switch statements or labels.
setlocal cindent cinoptions=L0
" Enable '|', disable ':'.
setlocal indentkeys=0{,0},0),0#,0\|,!^F,o,O,e
setlocal indentexpr=GetNemerleIndent()
let b:undo_indent = "setl cin< cino< indentkeys< indentexpr<"
function! GetNemerleIndent()
" Nemerle is C-like; use built-in C indentation as a basis.
let indent = cindent(v:lnum)
" Set alignment for lines starting with '|' in line with the opening
" brace. Use default indentation outside of blocks.
if getline(v:lnum) =~ '^\s*|'
call cursor(v:lnum, 1)
silent! normal [{
if line('.') == v:lnum
return indent
endif
return indent(line('.'))
endif
return indent
endfunction
See :h indent-expression to get a foothold in the Vim documentation. Basically I think you will want to write your own "indent file" for your filetype, which will return an indentexpr with appropriate spaces for your match structure, and otherwise (assuming that's only change) return the usual cindent() value. It involves a little more than just setting a regular expression, the indent file will have Vim commands and structures to evaluate lines and return correct value. As documentation says, best way to learn how they work is to look at some of the indent files for other languages. . . . (C doesn't have an indent file for you to look at because it's all integrated into Vim's own c source code, but most other languages have indent files using Vimscript.)

vim - set auto indent to fill the leading space with space or tabstop

It seems if we enable 'ai', vim will fill the the leading space with tabstop.
I can make it fill with just space with 'et'. I don't like a C file mixed with space and tabstop.
My vimrc:
set ts=4 et
set ai
set hlsearch
syntax on
filetype plugin indent on
autocmd FileType make setlocal noexpandtab
However, in some condition I do need to input tabstop when I hit the 'TAB' on keyboard, for example, in makefile and some others.
The 'autocmd FileType' command is not good: I can't add every file type in vimrc.
What I want is simple:
autoindent to fill leading area with
space;
when hit 'TAB' on keyboard, tabstop
input, not space (so no 'et')
How to do it?
inoremap <expr> <tab> ((getline('.')[:col('.')-2]=~'\S')?("\<C-v>\t"):(repeat(' ', &ts-((virtcol('.')-1)%&ts))))
It does the same as #Lynch answer if I read it correctly.
You can also use <C-v><Tab>: this will insert <Tab> without invoking any mappings and ignores expandtab unless you remapped <C-v> or <C-v><Tab> for some reason.
If you want to just insert tab do
inoremap <Tab> <C-v><Tab>
It will ignore expandtab setting.
I did it using a function. I tested it, but maybe in some particular case you will have to fix some bugs. Try adding this to your vimrc:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
if pos != 1 && substitute(before, "[ \t]", "", "g") != ""
let insert = "\t"
else
let insert = " "
endif
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
Basicaly it does remap your key in visual mode to the function Inserttab(). Also note that if you change ts for something other than 4 it will still output 4 spaces instead of two because the value is hard coded.
Also im not very familiar with vim scripts, but I think all the variables used will be global which is a bad thing.
I forgot to mention that to "see" white spaces you can use set list. You disable this with set nolist. Also in normal mode you can use ga to see information about the character your cursor is on.
Edit
I realise that you may want to insert tab at the beginin of the line. My script insert space at the begining and tab anywhere else.
If you really want a tab every time you hit tab key you could simply use this:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
let insert = "\t"
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
But I dont see the point, with this version you will never be able to indent manually from insert mode.
One way to do it is
:set sw=4 (or whatever you want)
:set ts=46 (or some large number)
Then autoindent will not insert tabs unless you reach 46 spaces, in which case you can put in a higher number.
Only drag about this is if someone else is using tabs, then you have to reset ts to agree with the file you are editing. On the other hand, it will make the tabs immediately obvious, which can be desirable as well.

Resources