How do I define indents in vim based on curly braces? - vim

I use https://github.com/cakebaker/scss-syntax.vim for syntax highlighting SCSS (or SASS) files on vim, which works very well for syntax highlighting. However, the plugin does not come with an indent file and am having trouble writing one.
I would like to set the indent to look like this:
However, if i do gg=G, I get:
I suspect that it does not understand nested indent based on braces. I tried all the different combinations of
set cindent
set nocindent
set autoindent
set smartindent
and tried to use the code from Tab key == 4 spaces and auto-indent after curly braces in Vim , including
set tabstop=2
set shiftwidth=2
set expandtab
...but nested braces indent never seems to work.
I believe that I might want to write a custom indent file, and all I need is indentation based on braces with nested levels. How should I go about this? If someone has an indentation file for filetypes with similar syntax, that will be great as well.

This is a quick hack, based on the built-in perl indentation code (in indent/perl.vim). Hopefully you can use it to get what you want to do. See the more detailed comments in either the perl indentation code or another one of the files in the indent directory for more details.
setlocal indentexpr=GetMyIndent()
function! GetMyIndent()
let cline = getline(v:lnum)
" Find a non-blank line above the current line.
let lnum = prevnonblank(v:lnum - 1)
" Hit the start of the file, use zero indent.
if lnum == 0
return 0
endif
let line = getline(lnum)
let ind = indent(lnum)
" Indent blocks enclosed by {}, (), or []
" Find a real opening brace
let bracepos = match(line, '[(){}\[\]]', matchend(line, '^\s*[)}\]]'))
while bracepos != -1
let brace = strpart(line, bracepos, 1)
if brace == '(' || brace == '{' || brace == '['
let ind = ind + &sw
else
let ind = ind - &sw
endif
let bracepos = match(line, '[(){}\[\]]', bracepos + 1)
endwhile
let bracepos = matchend(cline, '^\s*[)}\]]')
if bracepos != -1
let ind = ind - &sw
endif
return ind
endfunction
Save that file as ~/.vim/indent/something.vim where something is your file type (replace ~/.vim with the path to vimfiles if you're on Windows.
You might also want to stick this at the start of the file (but only if there isn't some other indent declaration that might be loaded first):
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
finish
endif
let b:did_indent = 1

Related

Mapping/macro to 'smartly' auto-create pairs of apostrophes in vim (and ignore contractions)

I'm currently using closepairs for my auto-closing needs, and it works pretty well. However, there is one caveat -- apostrophes. Don't get me wrong, I need apostrophes closed all the time. I don't want to just disable them. But whenever I type in plain text, whenever there are any contractions (I'm, Don't, Can't)...these apostrophes get made.
Now I could just type to delete them as soon as they can, but doing it every time is a bit impractical.
Does anyone know how I can possibly modify the closepairs script to only autoclose single quotes/apostrophes if they are the start of a word? That is, they are preceded by a whitespace character?
Here is the current code:
inoremap <expr> " <SID>pairquotes('"')
inoremap <expr> ' <SID>pairquotes("'")
function! s:pairquotes(pair)
let l:col = col('.')
let l:line = getline('.')
let l:chr = l:line[l:col-1]
if a:pair == l:chr
return "\<right>"
else
return a:pair.a:pair."\<left>"
endf
I don't know closepairs, but the AutoClose - Inserts matching bracket, paren, brace or quote plugin handles this well. You'll find a list of plugin alternatives on the Vim Tips Wiki.
Are you sure you want to autocomplete only after whitespace? In that case, something like function('string') would not autocomplete after the parenthesis.
Regardless, you can check the previous character against some regex. For example, to avoid autocompletion after letters:
function! s:pairquotes(pair)
let l:line = getline('.')
let l:col = col('.')
let l:chr = l:line[l:col - 1]
let l:prev = l:line[l:col - 2]
if l:chr == a:pair
return "\<right>"
elseif l:prev !~ "[A-Za-z]"
return a:pair . a:pair . "\<left>"
else
return a:pair
endif
endfunction
Note that there are exceptions even with this conservative example, like typing r'regex' in Python, so it might also make sense to define filetype-specific behavior.

Show function name in status line

I edit a large C, C++, or Java file, say, about 15000 lines, with pretty long function definitions, say, about 400 lines. When the cursor is in middle of a function definition, it would be cool to see the function name in Vim status line.
When we set :set ls=2 in Vim, we can get the file path (relative to the current directory), line number, etc. It would be really cool if we could see the function name too. Any ideas how to get it?
Currently I use [[ to go to start of the function and Ctrl-O to get back to the line I'm editing.
To show current function name in C programs add following in your vimrc:
fun! ShowFuncName()
let lnum = line(".")
let col = col(".")
echohl ModeMsg
echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bW'))
echohl None
call search("\\%" . lnum . "l" . "\\%" . col . "c")
endfun
map f :call ShowFuncName() <CR>
Or if you need the "f" key, just map the function to whatever you like.
You can use ctags.vim for this, it will show the current function name in the title or status bar.
SOURCE: https://superuser.com/questions/279651/how-can-i-make-vim-show-the-current-class-and-method-im-editing
Based on #manav m-n's answer
The 'n' flag in search() won't move the cursor, so a shorter version of this with the same functionality would be:
fun! ShowFuncName()
echohl ModeMsg
echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bWn'))
echohl None
endfun
map f :call ShowFuncName() <CR>
Reference: run :help search()
Having investigated this and the accepted solution, I believe the simplest solution is:
Install Universal Ctags https://ctags.io/
Install Tagbar https://github.com/preservim/tagbar
call tagbar#currenttag() when setting your statusline, e.g. in .vimrc:
:set statusline=%<%f\ %h%m%r%=%{tagbar#currenttag('%s\ ','','f')}%-.(%l,%c%V%)\ %P
Note that:
Spaces must be slash-escaped in the strings you pass to currenttag()...and it's even different between running the command inside vim and putting it in your .vimrc?? Anyway, spaces can be weird, and they're probably something you want when outputting the function name.
It took me some digging but the default statusline is
:set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
There are several plugins for status line or on-demand with a mapping, e.g.:
http://www.vim.org/scripts/script.php?script_id=1094
http://www.vim.org/scripts/script.php?script_id=2805
http://www.vim.org/scripts/script.php?script_id=1553
My solution is as follows:
set stl=%f%h%m%r\ %{Options()}%=%l,%c-%v\ %{line('$')}
fu! PlusOpt(opt)
let option = a:opt
if option
return "+"
else
return "-"
endif
endf
fu! Options()
let opt="ic".PlusOpt(&ic)
let opt=opt." ".&ff
let opt=opt." ".&ft
if &ft==?"cpp" || &ft==?"perl"
let text = " {" . FindCurrentFunction() . "}"
let opt= opt.text
endif
return opt
fu! FindCurrentFunction()
let text =''
let save_cursor = getpos(".")
let opening_brace = searchpair('{','','}','bWr', '', '', 100)
if opening_brace > 0
let oldmagic = &magic
let &magic = 1
let operators='operator\s*\%((\s*)\|\[]\|[+*/%^&|~!=<>-]=\?\|[<>&|+-]\{2}\|>>=\|<<=\|->\*\|,\|->\|(\s*)\)\s*'
let class_func_string = '\(\([[:alpha:]_]\w*\)\s*::\s*\)*\s*\%(\~\2\|'.operators
let class_func_string = class_func_string . '\|[[:alpha:]_]\w*\)\ze\s*('
let searchstring = '\_^\S.\{-}\%('.operators
let searchstring = searchstring.'\|[[:alpha:]_]\w*\)\s*(.*\n\%(\_^\s.*\n\)*\_^{'
let l = search(searchstring, 'bW', line(".")-20 )
if l != 0
let line_text = getline(l)
let matched_text = matchstr(line_text, class_func_string)
let matched_text = substitute(matched_text, '\s', '', 'g')
let text = matched_text
endif
call setpos('.', save_cursor)
let &magic = oldmagic
endif
return text
endfunction
I'm actually attempting to match the C/C++/Java allowed names for functions. This generally works for me (including for overloaded operators) but assumes that the opening { is at column 0 on a line by itself.
I just noticed today that it fails if included in a namespace {}, even if otherwise formatted as expected.
I use https://github.com/mgedmin/chelper.vim for this. It doesn't needs a tags file, instead it parses the source code on the fly.
Based on #solidak solution (which was already based on another one :)
I wanted to always show the function name in the bottom of the terminal.
But I had some problems with very large function which I solved that way:
fun! ShowFuncName()
echohl ModeMsg
echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bWn'))[:winwidth('%')-3]
echohl None
endfun
augroup show_funcname
autocmd CursorMoved * :call ShowFuncName()
augroup end

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.

Two fold methods at the same time

I'd like to set two methods for folding
:set foldmethod=indent and retain all its features
hiding comments with
:set foldmethod=marker
:set foldmarker=/*,*/
I found out that this is not possible. Is it possible to achieve the desired folding and set this in .vimrc or to use some script or plugin for this?
It's not possible to have different foldmethod types in same buffer. How is Vim to know that there are some comments at same indent level as other text that you want to treat as being of a different (higher numbered) level?
I'm sure you can achieve what you want by setting foldmethod to 'expr'. This is most flexible way of doing folds in Vim but can get complicated (and/or slow) depending on what you want. I think it would work fairly easily for your use case, though.
First, somewhere in your vimrc or vimscripts you need to make sure that foldexpr is getting defined for the filetype in question.
set foldexpr=MyFoldLevel(v:lnum)
set foldmethod=expr
" and for last code example
let b:previous_level = 0
and you then have to flesh out your foldexpr function so that it assigns levels in a way that results in behavior you want. Something like the code below might come close to working in cases where each comment line has prefix symbol (i.e., not in your case), but I expect it needs some tweaks. h: fold-expr would be a good place to look for help:
function! MyFoldLevel(linenum)
" assign levels based on spaces indented and tabstop of 4
let level = indent(a:linenum) / 4
if getline(a:linenum) =~ [put line-based comment prefix pattern here]
let level = 20
endif
endfunction
Would need to be modified to assign higher level for lines between comment start and end markers the way you want:
function! MyFoldLevel(linenum)
let linetext = getline(a:linenum)
if linetext =~ [put line-based comment prefix pattern here]
let level = 20
elseif linetext =~ '^\s*/\*'
let level = 20
elseif linetext =~ '^\s*\*/'
let level = 21
else
if b:previous_level == 20
let level = 20
else
"assuming code is space-indented with tabstop of 4
let level = indent(a:linenum) / 4
endif
endif
let b:previous_level = level
return level
endfunction
I don't expect the foldmethod functions I've written would work exactly as written. But they do point the way to something that would work.
Note that use of level of '20' for comments is just arbitrary level that allows them to be folded while all (presumably lower-leveled) indented code could be visible. '21' for last line of comment section is just to differentiate it from the previous lines of comments that have level of 20, to know that next line should be treated as regular line of code.
Also, key ops like 'zc' and 'zo' will not work quite right on comments when they're set to level much higher than surrounding code. Would want to use direct command like :set foldlevel=21 to show all comment lines.
Not pretty, and I expect it could be simplified a little, but something like this is what I think is required for what you want.
Actually, thinking through this a little more, I think you would want the first line of any comment blocks to be at same level as if it were a non-comment line, only subsequent comment lines in same block would need to be of higher level to have them "fold" into the starting comment line. In the code I gave, if it works or comes close to working at all, I think vim would fold all the comment lines behind the preceding non-comment line, which isn't what you want, but I unfortunately don't have more time to devote to this little puzzle. . . I've done this sort of custom-folding quite a few times and generally always have a little bit of trial and error in getting exactly what I want.
I have the same requests as yours, here is my not perfect solution
my maker pair is #<=== and #===> (or #region and #endregion as in pycharm)
let b:inBlock=0
let b:lastLineNum=0
let b:lastLevel=0
let b:lastGoodLine=0
let b:lastGoodBlock=0
let b:startFoldingMark='^\s*.\?#<==*\|^\s*.\?#region'
let b:endFoldingMark='^\s*.\?#=*=>\|^\s*.\?#endregion'
function! MyFold(linenum)
let linetext = getline(a:linenum)
let level = indent(a:linenum) / &shiftwidth
"the first line have 0 fold level
if (a:linenum == 1)
if linetext =~ b:startFoldingMark
let b:inBlock = 1
let b:lastLineNum=a:linenum
let b:lastGoodLine=0
let b:lastGoodBlock=0
let b:lastLevel=level
return level
endif
let b:inBlock=0
let b:lastInBlock=0
let b:lastLineNum=a:linenum
let b:lastGoodLine=0
let b:lastGoodBlock=b:inBlock
let b:lastLevel=level + b:inBlock
return level + b:inBlock
endif
" not calculate from the mid of text
if ((b:lastLineNum+1) != a:linenum)
let level = indent(a:linenum) / &shiftwidth
let lastGoodNum = a:linenum-1
while (lastGoodNum>1 && getline(lastGoodNum) =~? '\v^\s*$' )
let lastGoodNum -= 1
endwhile
if (foldlevel(lastGoodNum)==-1)
let b:inBlock=b:lastGoodBlock
else
let lastlevel = indent(lastGoodNum) / &shiftwidth
let lastlinetext = getline(lastGoodNum)
let lastlinelevel = foldlevel(lastGoodNum)
if lastlinetext =~ b:startFoldingMark
let b:inBlock = lastlinelevel - lastlevel + 1
elseif lastlinetext =~ b:endFoldingMark
let b:inBlock = lastlinelevel - lastlevel - 1
else
let b:inBlock = lastlinelevel - lastlevel
endif
endif
endif
"blank lines have undefined fold level
if getline(a:linenum) =~? '\v^\s*$'
let b:lastLineNum=a:linenum
let b:lastLevel=-1
return -1
endif
"if next line is a start of new marker block, inBlock ++
if linetext =~ b:startFoldingMark
let b:lastLineNum=a:linenum
if (b:lastLevel != -1)
let b:lastGoodLine=a:linenum
let b:lastGoodBlock=b:inBlock
endif
let b:lastLevel=level + b:inBlock - 1
return level + b:inBlock - 1
"if next line is an end of new marker block, inBlock -
elseif linetext =~ b:endFoldingMark
let b:inBlock = b:inBlock - 1
let b:lastLineNum=a:linenum
let b:lastGoodLine=a:linenum
let b:lastGoodBlock=b:inBlock
let b:lastLevel=level + b:inBlock + 1
return level + b:inBlock + 1
endif
let b:lastLineNum=a:linenum
if (b:lastLevel != -1)
let b:lastGoodLine=a:linenum
let b:lastGoodBlock=b:inBlock
endif
let b:lastLevel=level + b:inBlock
return level+b:inBlock
endfunction
now, i can keep all the features when using indent fold method,
and i can fold each #<=, #=> marker block,
also, the lines' indent folding relations are still kept in each block.
In this function, i avoid using "a1", "s1" and "=" level, which will cause iteration for this function and may be slow for large files.
However, when you update lines, the calculation of fold level may be incorrect (because vim may not update all fold level from beginning, and thus have incorrect inBlock value)
you can use zx to update fold levels manually.
see more at https://github.com/Fmajor/configs
Syntax-based folding may be a better way to get what you want than the expr-based method I suggested in different answer to your question. Check :h fold-syn for more info. I think there may be some good solutions already out there for c-based folding. Don't know how good it is, but here is a c-syntax file with support for syntax-based folding:
http://www.vim.org/scripts/script.php?script_id=234
and another:
http://www.vim.org/scripts/script.php?script_id=925
The solutions above are entirely syntax-based, don't involve using indents to determine fold levels. But you could modify syntax-based folding to do the main folding by indented regions if you wanted. If you indent based on syntactical elements the result might be the same anyway.
Here's a tip that shows how to just fold c-style comments (not the actual code)
http://vim.wikia.com/wiki/Fold_C-style_comments

Resources