Vim: Cucumber indentation for "And" lines - vim

In Cucumber's Gherkin language, it's allowed to indent And lines, like so:
Scenario:
Given there is a user named Chris
And I am logged in as Chris
When I go to the home page
Then I should see "hi Chris"
And I should not see "log in"
I like this indentation style much better than the equally-indented style, but with Tim's Cucumber scripts for Vim, I have to manually indent the And lines and manually dedent the following lines, and sometimes Vim will automatically indent lines and it ends up all wrong.
What's the best way to work with indented And lines in Vim? Or is it easiest to just give up on it?

I think you could customize indent/cucumber.vim (online here) to increase the indent on lines starting with ^\s*And.

Here's a diff for indent/cucumber.vim based on Andy's answer:
--- .vim/indent/cucumber.vim.bak 2011-03-24 18:44:27.000000000 +0100
+++ .vim/indent/cucumber.vim 2011-03-24 19:09:41.000000000 +0100
## -47,6 +47,10 ##
return indent(prevnonblank(v:lnum-1)) + &sw
elseif cline =~# '^\s*[^|# \t]' && line =~# '^\s*|'
return indent(prevnonblank(v:lnum-1)) - &sw
+ elseif cline =~# '^\s*\%(And\|But\)' && line !~# '^\s*\%(And\|But\)'
+ return indent(prevnonblank(v:lnum-1)) + &sw
+ elseif cline !~# '^\s*\%(And\|But\)' && line =~# '^\s*\%(And\|But\)'
+ return indent(prevnonblank(v:lnum-1)) - &sw
elseif cline =~# '^\s*$' && line =~# '^\s*|'
let in = indent(prevnonblank(v:lnum-1))
return in == indent(v:lnum) ? in : in - &sw

Related

How to remove digraph highlighting in vim

Using vim or nvim, when I open a new vim terminal digraphs display correctly. See below:
However, whenever I change colorscheme (any, it is not specific to a particular colorscheme) - then the digraphs appear highlighted. The highlighting remains even when switching back to the original colorscheme. This happens with any digraph, not just the one shown in this question.
See below:
Cannot find a way to remove that highlighting, or prevent it happening in the first place. Have tried commands like :highlight nonascii none but had no luck. Any help / suggestions much appreciated.
Most/Some of the colorschemes aren't really made for hotswap.
It seems to be a rather common problem with solarized f.e.
There is a plugin which handles a change cleanly: https://github.com/xolox/vim-colorscheme-switcher (I haven't tested it).
And I copied a bunch of functions for that somewhere which works arround the problems most of the time. I don't know where I got it from, but I want to be clear, it is not my work!
function! s:Find_links() " {{{1
" Find and remember links between highlighting groups.
redir => listing
try
silent highlight
finally
redir END
endtry
for line in split(listing, "\n")
let tokens = split(line)
" We're looking for lines like "String xxx links to Constant" in the
" output of the :highlight command.
if len(tokens) == 5 && tokens[1] == 'xxx' && tokens[2] == 'links' && tokens[3] == 'to'
let fromgroup = tokens[0]
let togroup = tokens[4]
let s:known_links[fromgroup] = togroup
endif
endfor
endfunction
function! s:Restore_links() " {{{1
" Restore broken links between highlighting groups.
redir => listing
try
silent highlight
finally
redir END
endtry
let num_restored = 0
for line in split(listing, "\n")
let tokens = split(line)
" We're looking for lines like "String xxx cleared" in the
" output of the :highlight command.
if len(tokens) == 3 && tokens[1] == 'xxx' && tokens[2] == 'cleared'
let fromgroup = tokens[0]
let togroup = get(s:known_links, fromgroup, '')
if !empty(togroup)
execute 'hi link' fromgroup togroup
let num_restored += 1
endif
endif
endfor
endfunction
function! s:AccurateColorscheme(colo_name)
call <SID>Find_links()
exec "colorscheme " a:colo_name
call <SID>Restore_links()
endfunction
command! -nargs=1 -complete=color MyColorscheme call <SID>AccurateColorscheme(<q-args>)

Folding repeated lines in vim

I have a file in which, below is the content.
A
A
A
A
A
A
A
A
A
.
.
.
A
Piece of code...
A
A
A
.
.
A
I want something like this using folding in vim
A
Piece of code
A
2 folds are created by folding repeated lines.
This should happen automatically as I open the file. Is it possible by doing it in vimrc?
How about this :help fold-expr:
setlocal foldenable foldmethod=expr
let &l:foldtext = 'printf("+-%s %d times: %s", v:folddashes, (v:foldend - v:foldstart + 1), getline(v:foldstart))'
let &l:foldexpr = 'getline(v:lnum) ==# getline(v:lnum + 1) && v:lnum < line("$") ? 1 : (getline(v:lnum - 1) ==# getline(v:lnum) ? "<1" : 0)'
It should be possible by using setlocal foldmethod=expr where you can write your own function:
setlocal foldmethod=expr
setlocal foldexpr=CustomFold(v:lnum)
function! CustomFold(lnum)
if getline(a:lnum) == getline(a:lnum-1) || getline(a:lnum) == getline(a:lnum+1)
return '1'
endif
return '0'
endfunction
However this is untested and you wouldn't want to do it for all files. But it should point you in the right direction. It also would not 100% match your criteria, but once you have a specific problem, you can always ask again

vim: complete depending on previous character

I want to create a mapping that will changed the ins-completion depending on the character just before the cursor. If the character is { then I want tag completion, if its : i want normal completion (that depends on the complete option) and if the characters is a backslash plus some word (\w+) I want dictionary completion. I have the following in my ftplugin/tex/latex_settings.vim file:
setlocal dictionary=$DOTVIM/ftplugin/tex/tex_dictionary
setlocal complete=.,k
setlocal tags=./bibtags;
function! MyLatexComplete()
let character = strpart(getline('.'), col('.') - 1, col('.'))
if character == '{'
return "\<C-X>\<C-]>"
elseif character == ':'
return "\<C-X>\<C-N>"
else
return "\<C-X>\<C-K>"
endif
endfunction
inoremap <C-n> <c-r>=MyLatexComplete()<CR>
This doesn't work and I don't know how to fix it.
Edit: This seems to work but I'm want a conditional that checks for \w+ (backslash plus any word) and a final one that gives a message "No match found".
function! MyLatexComplete()
let line = getline('.')
let pos = col('.') - 1
" Citations (comma for multiple ones)
if line[pos - 1] == '{' || line[pos - 1] == ','
return "\<C-X>\<C-]>"
" Sections, equations, etc
elseif line[pos - 1] == ':'
return "\<C-X>\<C-N>"
else
" Commands (such as \delta)
return "\<C-X>\<C-K>"
endif
endfunction
In your original function you have mistakes:
strpart() takes string, offset and length arguments, while you supplied two offsets.
col('.') is one character past the end-of-line. I.e. len(getline('.'))==col('.')+1 meaning that strpart(getline('.'), col('.')-1) is always empty.
You have fixed these issues in the second variant. But if you want conditional check for \command you need not just last character. Thus I would suggest matching slice
let line=getline('.')[:col('.')-2]
if line[-1:] is# '{' || line[-1:] is# ','
return "\<C-X>\<C-]>"
elseif line[-1:] is# ':'
return "\<C-X>\<C-N>"
elseif line =~# '\v\\\w+$'
return "\<C-X>\<C-K>"
else
echohl ErrorMsg
echomsg 'Do not know how to complete: use after {, comma or \command'
echohl None
return ''
endif
. Note some things:
Never use == for string comparison without # or ? attached. This does not matter in this case, but you should make yourself used. ==# and ==? both ignore value of 'ignorecase' setting (first acts as if 'noignorecase' was set, second as if 'ignorecase' was set). I use even stricter is#: a is# b is like type(a)==type(b) && a ==# b.
Same for =~: use =~#.
Due to backwards compatibility string[-1] (string[any_negative_integer]) is always empty. Thus I have to use line[-1:].
Never use plain :echoerr. It is unstable: in terms that you cannot say for sure whether or not this will break execution flaw (:echoerr breaks execution if put inside :try block and does not do so otherwise). echohl ErrorMsg|echomsg …|echohl None never breaks execution, throw … and try|echoerr …|endtry break always.
To spot preceding LaTeX commands you can use the following regular expression on your line variable:
line =~ '\\\w\+$'
(as you can see, the regex is similar to the Perl expression you guessed at, but requires some the characters to be escaped).
To echo a "No match found" message, you could return an appropriate :echoerr command:
return "\<C-o>:echoerr 'No match found'\<CR>"
But this has the side-effect of hijacking insert-mode for a moment... maybe it's cleaner just to return no matches as an empty string?
So your final function would look something like this:
function! MyLatexComplete()
let line = getline('.')
let pos = col('.') - 1
" Citations (comma for multiple ones)
if line[pos - 1] == '{' || line[pos - 1] == ','
return "\<C-X>\<C-]>"
" Sections, equations, etc
elseif line[pos - 1] == ':'
return "\<C-X>\<C-N>"
elseif line =~ '\\\w\+$'
" Commands (such as \delta)
return "\<C-X>\<C-K>"
else
" Echo an error:
return "\<C-o>:echoe 'No match found'\<CR>"
endif
endfunction

How to convert leading spaces to tabs?

Many people use spaces rather than tabs. I use both of them. Tabs at the beginning of line and spaces from the first non-whitespace character. No problem for starting new document and in case I have to modify one better adapt to using format. Still sometimes I need to fix the spaces issue anyway.
According to Search and replace I can just do :%s/spaces_for_tab/tab/g. It is simple and it will work for many cases. Anyway I want to refactor only spaces at the beginning of line.
This is more of a regex issue. To anchor at the beginning of the line, use the caret, e.g.
s/^ /\t/
Or do it using vim's builtin functionality:
:set tabstop=4 "four spaces will make up for one tab
:set noexpandtab "tell vim to keep tabs instead of inserting spaces
:retab "let vim handle your case
By the way, I too prefer tabs for indentation and spaces for alignment. Unfortunately, vim doesn't handle this well (and I don't know what other editors do), so I mostly use :set expandtab (maybe see :set softtabstop).
I've written a simple func for it. Anyway it will work only for 4-space tab.
fu! Fixspaces()
while search('^\t* \{4}') != 0
execute ':%s/^\t*\zs \{4}/\t/g'
endwhile
endfu
You can suggest better solution, if exists, and I will use it with pleasure.
The issue is that this func replaces spaces in strings as well.
David's response is very elegant but it doesn't address leading whitespace that has a mixture of tabs and spaces. For example to convert a line like:
<SPACE><SPACE><TAB>something...
you have to know the position of the tab to determine the number of spaces needed to replace the <TAB> and reach the next tabstop. My solution below, although not as compact as David's, addresses this. It also allows me to select which way to use leading whitespace without depending upon &expandtab. I would appreciate any suggestions to improve my code...
function! TabsToSpaces(...)
let ts = &tabstop
let pos = getpos('.')
while search('^ *\zs\t', "w") != 0
let l:curpos = getcharpos('.')
" The number of spaces needed depends upon the position of the <TAB>
let numsp = 1 + ts - ( curpos[2] % ts )
if numsp == 9
let numsp = 1
endif
silent execute ':s/^ *\zs\t/'.repeat(' ', numsp).'/'
endwhile
if a:0 == 0
echo 'Changed leading tabs to spaces'
endif
call setpos('.', pos)
endfunction
function! SpacesToTabs(...)
let ts = &tabstop
let pos = getpos('.')
" First normalize all tabs to spaces
call TabsToSpaces("quiet")
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
if a:0 == 0
echo 'Changed leading spaces to tabs'
endif
call setpos('.', pos)
endfunction
" Some keystrokes to implement the spaces/tabs functions
nmap <Leader>st :execute SpacesToTabs()<CR>
nmap <Leader>ts :execute TabsToSpaces()<CR>
I took Martin's answer and improved on it a bit if anyone's interested:
function Fixspaces()
let ts = &tabstop
let pos = getpos('.')
if &expandtab
while search('^ *\t') != 0
silent execute ':%s/^ *\zs\t/'.repeat(' ', ts).'/g'
endwhile
echo 'Changed tabs to spaces'
else
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
echo 'Changed spaces to tabs'
endif
call setpos('.', pos)
endfunction
This function does the appropriate thing depending on the values of the expandtab and tabstop settings and also remembers where the cursor is.

Is there any easy way to toggle "do/end" and "{}" in ruby in Vim?

Is there any easy way to toggle "do/end" and "{}" in ruby in Vim?
(TextMate does this with ^{.)
You'd have to either use searchpair(), or to play with % (as long as matchit is installed, and as you are on begin/end), then mark the two positions, test whether it's text or brackets, and finally update the two lines.
nnoremap <buffer> <c-x>{ :call <sid>ToggleBeginOrBracket()<cr>
let s:k_be = [ 'begin', 'end' ]
function! s:ToggleBeginOrBracket()
let c = lh#position#char_at_mark('.')
if c =~ '[{}]'
" don't use matchit for {,}
exe 'normal! %s'.s:k_be[1-(c=='}')]."\<esc>``s".s:k_be[(c=='}')]."\<esc>"
else
let w = expand('<cword>')
if w == 'begin'
" use mathit
normal %
exe "normal! ciw}\<esc>``ciw{\<esc>"
elseif w == 'end'
" use mathit
normal %
exe "normal! ciw{\<esc>``ciw}\<esc>"
else
throw 'Cannot toggle block: cursor is not on {, }, begin, nor end'
endif
endif
endfunction
Where lh#position#char_at_mark() is defined here.
PS: this is definitively a SO question as it combines ruby context, and advanced vim scripting.
Check out this new plugin: https://github.com/jgdavey/vim-blockle.
30 chars pad
There is a splitjoin.vim plugin that does this nicely (gJ/gS mappings for splitting/joining).

Resources