Vim: language independent fall back to syntax folding with foldmethod=expr - vim

My goal is to extend the foldmethod=syntax with an additional custom defined rule of my own.
My approach in doing so is to use foldmethod=expr and define this rule in my foldexpr (e.g. add a fold for '///' comments). After that the resources I found usually fall back to something similar to indentation folding. So is there a good way to fall back to syntax folding after the custom rules, without reproducing the complete grammar for whatever language I am using?
My attempt so far is this, which is not a very satisfying approximation of syntax folding:
function! GetFold(lnum)
let this_line=getline(a:lnum)
let pprev_i=indent(a:lnum - 2)/&shiftwidth
let prev_i=indent(a:lnum - 1)/&shiftwidth
let this_i=indent(a:lnum)/&shiftwidth
" comments
if this_line =~? '^\s*\/\/\/.*'
return this_i + 1
endif
" like indent folding, but includes the closing bracket line to the fold
if empty(this_line)
if prev_i < pprev_i
return prev_i
endif
return -1
endif
if this_i < prev_i
return prev_i
endif
return this_i
endfunction

The solution is to just use set fold=syntax and add a syntax region for the comments to your .vimrc. There you can use the fold keyword to mark the region as foldable (see :h syn-fold for more information):
syn region myFold start="///" end="///" transparent fold
(Note, take also a look at :h syn-transparent it is quite useful here)

No, there isn't a way to make Vim "fall back"; only one 'foldmethod' can be active at a time, and there's no API to evaluate another fold method "as if".
You could temporarily switch to syntax folding, store the generated folds, and then use that information in your fold information, extended with what your algorithm determines. (Or you could keep the same buffer in a window split, have syntax folding enabled there, and query it from the; this would save the complete re-creation of the folds, but you'd probably need to synchronize the cursor positions.)
This is cumbersome and possibly slow; I wouldn't recommend it.

Related

can we specify multiple foldmarkers in vim?

I am trying to fold multiple lines in vim with :set foldmethod=marker I have done :set foldmarker={,} is there any way such that we can fold function that has starting braces='[' and end braces=']' and also fold a function with starting braces='{' and end braces='}' like in vscode we can fold any function with any starting and ending braces.
From :help folding:
There are six methods to select folds:
manual manually define folds
indent more indent means a higher fold level
expr specify an expression to define folds
syntax folds defined by syntax highlighting
diff folds for unchanged text
marker folds defined by markers in the text
marker won't help for the reason given under :help 'foldmarker':
The marker is a literal string (a regular expression would be too slow).
See :help fold-marker.
diff is irrelevant.
See :help fold-diff.
syntax may help, depending on the language and how the syntax script for that language was written.
See :help fold-syntax.
expr is the most powerful method because it lets you decide exactly what to fold and how to fold it.
It is also the hardest to set up, for obvious reasons. See :help fold-expr.
indent is a pretty dumb method, which makes it very memory efficient and versatile. You could try that one because it doesn't care about braces at all.
See :help fold-indent.
And then there is the manual method, which lets you define folds with motions.
This lets you do things like zfi{ or zf12j.
See :help fold-manual.

How can i fold both code and comments in Vim?

Vim can fold Ruby code, but not comments.
After adding this in .vimrc to change foldmethod to comments, i can no longer fold code.
autocmd FileType ruby,eruby
\ set foldmethod=expr |
\ set foldexpr=getline(v:lnum)=~'^\\s*#'
How can i configure Vim to fold both comments and code?
In my recent Vim 7.3.823 snapshot, the $VIMRUNTIME/syntax/ruby.vim (version 2009 Dec 2) has both folding for Ruby constructs and comment blocks.
Just put
:let g:ruby_fold = 1
into ~/.vimrc. (And make sure you don't have a variable named ruby_no_comment_fold.)
You could use foldmethod=marker and add {{{ / }}} markers (or other markers of your choosing) to indicate where folds begin and end.
You could also modify the file which defines ruby syntax highlighting to adjust what it considers as eligible for folding with foldmethod=syntax.
A third option would be to develop a more complex routine for use with foldmethod=expr. For example, I use the vim functions defined here to define how ruby code should be folded. It automatically defines folds for modules, classes and methods along with any comment lines that immediately precede those; and it supports the standard fold markers for folding other sections. It gets used with foldexpr=ruby#MethodFold(v:lnum).
Further information on how fold expressions should behave can be found by doing :help fold-expr. There's also a nice vimcast about that.
Setting foldmethod to indent will fold lines based on indent level,
regardless of whether the line is a comment or code.
:set foldmethod=indent
:help fold-indent
I think you are looking for
set foldignore=#
If you want to fold block comments (like /* .... */ in multiple line), watch my other post in vi.stackechange

How to disable AutoComplPop completions in comments?

I've recently started using AutoComplPop, but I find the popup annoying when I'm writing comments (as I usually don't need autocomplition when writing a comment).
Is there a configuration option or a quick hack that can effectively disable AutoComplPop when the insertion point is in a comment?
To answer the question of performance when checking the syntax on every cursor move, I had to implement this myself, and made this into the OnSyntaxChange plugin.
With this plugin, setting this up can be done in just three lines (e.g. in .vimrc):
call OnSyntaxChange#Install('Comment', '^Comment$', 0, 'i')
autocmd User SyntaxCommentEnterI silent! AcpLock
autocmd User SyntaxCommentLeaveI silent! AcpUnlock
For me, the performance impact is noticeable (depending on the filetype and syntax), but tolerable. Try for yourself!
You need to check via a hook into CursorMovedI that you're currently in a comment, and can then use AutoComplPop's :AcpLock to disable it temporarily. (And undo with :AcpUnlock once you move out of comments.)
Detecting comments for various filetypes is best done by querying the syntax highlighting; this way, you benefit from the syntax definitions of existing filetypes.
Here's a snippet for this:
function! IsOnSyntaxItem( syntaxItemPattern )
" Taking the example of comments:
" Other syntax groups (e.g. Todo) may be embedded in comments. We must thus
" check whole stack of syntax items at the cursor position for comments.
" Comments are detected via the translated, effective syntax name. (E.g. in
" Vimscript, 'vimLineComment' is linked to 'Comment'.)
for l:id in synstack(line('.'), col('.'))
let l:actualSyntaxItemName = synIDattr(l:id, 'name')
let l:effectiveSyntaxItemName = synIDattr(synIDtrans(l:id), 'name')
if l:actualSyntaxItemName =~# a:syntaxItemPattern || l:effectiveSyntaxItemName =~# a:syntaxItemPattern
return 1
endif
endfor
return 0
endfunction
With this, you should be able to stitch together a solution.

Preventing repeated use of hjkl movement keys in vim

Since I frequently don't use the excellent motions and text objects that vim provides, (and since "Holding down 'j' is a vim anti-pattern,") I'd like vim to assist me in training to use these instead of using hjkl more than a few times in a row.
When I started using vim, I was annoyed that I didn't use hjkl to move but would instead use the arrow keys. As a reminder not to do this, I remapped the arrow keys to keep myself from using them - I knew using the home row to navigate would be a better long term plan, so I cut out the positive reinforcement I would get by having working arrow keys.
map <left> <nop>
map <right> <nop>
# I quickly removed nop's for up and down because using
# the mouse wheel to scroll is sometimes useful
I no longer need these mappings in my .vimrc because this worked very well, and I quickly switched, without having to make a conscious effort to do so. In a similar fashion, I'd now like to cut off my repeated use of basic movement keys hjkl as well. I was envisioning something like this:
let g:last_mov_key = 'none'
let g:times_mov_key_repeated = 0
function! MovementKey(key)
if (g:last_mov_key == a:key)
let g:times_mov_key_repeated = g:times_mov_key_repeated + 1
else
let g:last_mov_key = a:key
let g:times_mov_key_repeated = 0
endif
if g:times_mov_key_repeated > 3
echo "Negative Reinforcement!"
endif
endfunction
noremap j :call MovementKey('j')<CR>gj
noremap k :call MovementKey('k')<CR>gk
noremap h :call MovementKey('h')<CR>h
noremap l :call MovementKey('l')<CR>l
But this breaks in visual mode, and I imagine in tons of other cases where using the vim command line in the middle of something changes the state when it shouldn't. How can I constrain myself to have to use more complicated motions?
Edit: Question edited after first two answers for clarity, paragraphs reordered. I want some assistance from vim moving beyond romainl's "level 0". So far answers advise me not to waste my time, but what if we assume that I'm a fan of the learning technique where I change my habits by altering my environment to change incentives? In this model I want to accomplish a task, say, scrolling down a page, and I will more or less randomly attempt key combinations until I achieve that task. Dopamine signalling etc. in my brain will reinforce the action which eventually achieves this result. I could focus on remembering not to use hjkl, or I could focus on the task I was originally trying to do anyway, edit text, and without really thinking about it find myself using more efficient editing techniques.
You listen to other people waaay too much. Just use for movement whatever keys you like best, and leave other keys alone. Remapping hjkl keys is somewhat troublesome, and best not done, because they're hardcoded in vim due to historical reasons.
One thing I've found that works is :noremap jj <nop>. Vim will delay the j motion because it thinks you might be extending it, which slows you down enough to try something else. You also can't hold the j key down anymore because it just triggers the nop instead. You can still use the button in emergencies, but it's frustrating enough to make you avoid it in the day-to-day editing.
EDIT
After reading your edit and your comment here is the radical solution I propose: remap hjkl to do super annoying things:
nnoremap h $
nnoremap l 0
nnoremap j gg
nnoremap k G
" and so on for other modes
hjkl are mapped to the extreme opposite of their original behaviour, essentially making them completely unusable. You should do the same for their synonyms (:h h, :h j, :h k, :h l) too for completeness but there are cases when character-by-character/line-by-line movement is useful. In such cases you will be glad to have + or - or <Space>.
I don't really like that kind of pavlovian method, though. I find it too brutal and the risk that you actually get used to these weird mappings seems quite high.
END EDIT
Short version:
Neither the arrow keys nor hjkl are worth using so your remappings are ultimately useless. After a while you will get used to Vim's motions and text-objects. Use what feels more natural to you in the meantime. Don't rush it. It will come. Naturally.
Long version:
Are you a touch typist? Or do you plan to learn touch typing?
I'm not a touch typist and I don't plan to ever become one: I use hjkl only to actually input hjkl in INSERT mode.
When I need to move 1 or 2 lines above or below without a particular "target" I use the arrow keys, when I need to move a couple letters to the left or to the right I use the arrow keys. There is no shame in doing that.
There is a problem, however, when you type jjjjjjjjjj or hit the down arrow key 10 times to move down 10 lines: jjjjjjjjjjj is obviously just as bad as ↓↓↓↓↓↓↓↓↓↓
This stupid mantra of "don't use the arrow keys" is just the tree that hides the forest: it's repeated so often that it makes you focus on useless patterns/antipatterns instead of actually learning more powerful ways.
I don't want to paraphrase the beautiful Your problem with Vim is that you don't grok vi. but you could see it as "levels of enlightenment":
Level 0 would be
jjjjjjjjjjj or ↓↓↓↓↓↓↓↓↓↓ then all the necessary horizontal movements to reach your target.
Reading the line above, isn't it rather obvious that the hjkl Vs ←↓↑→ debate is absolutely stupid?
Whether you use one method or the other you end up mashing your keyboard moving vertically AND horizontally to reach the value you want to edit. What a waste. And what a waste of time and energy to force yourself to use one method over another since both are equally bad.
Level 1 would be
10j22l to go down 10 lines and reach your target 22 characters to the right. While it's a lot less typing, you now have to count lines and characters which is not particularly better.
Level 2 would be
10jwww to go down 10 lines and reach your target 3 words to the right
or 10jf# to go down 10 lines and jump to the first letter of your target (an hex color value).
Hmm, that's a lot better.
Level 3 would be
/#<CR> to jump directly to your target (an hex color value, as before). If it's above your current position you would do ?#<CR>.
If you are a touch typist, or are training to become one, the debate is settled: hjkl (or jkl; as some like to remap them) are quick and natural and you don't need arrow keys at all and if you are not, the benefits of using hjkl over ←↓↑→ are at best minimal.
Focus on eEbBwWfFtT/?} and Co. instead. One bit at a time. Don't "force" yourself.
I would like to propose a solution that is more in line with the OP's way of thinking.
I too decided that blocking certain motions if not preceded by a count was a good idea, as discussed here. I too tried a simply direct remapping, but this did not work in many contexts, as described by the OP.
But I did finally come up with an approach that worked using key maps that returned an expression:
function! DisableIfNonCounted(move) range
if v:count
return a:move
else
" You can make this do something annoying like:
" echoerr "Count required!"
" sleep 2
return ""
endif
endfunction
function! SetDisablingOfBasicMotionsIfNonCounted(on)
let keys_to_disable = get(g:, "keys_to_disable_if_not_preceded_by_count", ["j", "k", "l", "h", "gj", "gk"])
if a:on
for key in keys_to_disable
execute "noremap <expr> <silent> " . key . " DisableIfNonCounted('" . key . "')"
endfor
let g:keys_to_disable_if_not_preceded_by_count = keys_to_disable
let g:is_non_counted_basic_motions_disabled = 1
else
for key in keys_to_disable
try
execute "unmap " . key
catch /E31:/
endtry
endfor
let g:is_non_counted_basic_motions_disabled = 0
endif
endfunction
function! ToggleDisablingOfBasicMotionsIfNonCounted()
let is_disabled = get(g:, "is_non_counted_basic_motions_disabled", 0)
if is_disabled
call SetDisablingOfBasicMotionsIfNonCounted(0)
else
call SetDisablingOfBasicMotionsIfNonCounted(1)
endif
endfunction
command! ToggleDisablingOfNonCountedBasicMotions :call ToggleDisablingOfBasicMotionsIfNonCounted()
command! DisableNonCountedBasicMotions :call SetDisablingOfBasicMotionsIfNonCounted(1)
command! EnableNonCountedBasicMotions :call SetDisablingOfBasicMotionsIfNonCounted(0)
DisableNonCountedBasicMotions
Note that the code is included here for convenience, but I would check with the gist as well to see if there have been updates/fixes.
You can relax the constraint for horizontal motions, or
add/remove other motions by setting the list of the keys/commands
that are effected, g:keys_to_disable_if_not_preceded_by_count, in your ~/.vimrc.
For example, the following only enforces count-prefixed motions for "j" and "k":
let g:keys_to_disable_if_not_preceded_by_count = ["j", "k"]
Also, as noted in the gist, you might find it really useful to add something like this in your ~/.vimrc as well as the code above:
set number
if has('autocmd')
augroup vimrc_linenumbering
autocmd!
autocmd WinLeave *
\ if &number |
\ set norelativenumber |
\ endif
autocmd BufWinEnter *
\ if &number |
\ set relativenumber |
\ endif
autocmd VimEnter *
\ if &number |
\ set relativenumber |
\ endif
augroup END
Or install a plugin line vim-numbers.
Even though this question has (long) been answered to the OP's satisfaction with an alternate approach, I am adding my solution here in case anyone is searching for something different.
A very platform-specific solution I just found, Mac only: using something vim-like (I've only gotten this to work with PyCharm in vim mode), turn on MacOS key holding*, which makes holding down e bring up èéêëēėę to choose from. This makes holding down keys never work! This is really annoying unless you are trying to learn to stop holding down keys!
If I could figure out how to make this work in my terminal emulator (iTerm, Terminal.app, etc) this could probably be made to work in normal vim?
*This turns this feature on in case it's disabled for you:
defaults write -g ApplePressAndHoldEnabled -bool true

Alternate indentation display in Vim

I came across this question:
https://softwareengineering.stackexchange.com/questions/87077/how-can-a-code-editor-effectively-hint-at-code-nesting-level-without-using-inde
and thought that Vim might be able to do something similar as well with a plugin.
I believe the indentation level could be indicated with the a sign (icon in GUI, text with highlighting in term). The piece I'm not sure about is displaying the lines without indentation. Does anyone know, is it possible and/or how you would do it?
This question is not whether or not displaying indentation levels in this manner is desirable; but how to accomplish it in Vim.
You could use the conceal feature that is new in 7.3.
Here is a function that does roughly what the article is describing (for spaces; accounting for tabs instead would be a fairly trivial addition):
function! IndentationHeatMap()
set conceallevel=1
for i in range(1,9)
let indentation = repeat(" ", &sts * i)
exe 'syntax match NonText "^' . indentation . '" conceal cchar=' . i
endfor
endfunction
A solution closer to what you are requesting might use conceal to hide all leading whitespace with
syntax match NonText "^\s\+" conceal
and then use signs to provide the indicators based on custom calculations.
Note: NonText in these syntax commands is an arbitrary highlight group.
Take a look at these plugins: Indent Guides and IndentHL
Both have screenshots.

Resources