vim function for toggling colorschemes - vim

At the moment I'm using two different keys to toogle the colorscheme
map <F8> :colors wombat256 <cr>
map <F9> :colors dimtag <cr>
I want to achieve a toggle behavior like this
function! ToggleDimTags()
if (g:colors_name == "wombat256")
colors dimtag
else
colors wombat256
endif
endfunction
My problem is that ToogleDimTags() is resetting the cursor position to the first line on every call, which is undesirable. Any suggestions appreciated.

As discussed in the comments, the problem is that your map calling :execute
behaves a little differently, what you probably want is :call instead:
nnoremap <F5> :call ToggleDimTags()
To clarify what #ZyX was saying, :h :exec contains the following text:
:exe :execute
:exe[cute] {expr1} .. Executes the string that results from the evaluation
of {expr1} as an Ex command.
[...]
So what :execute really does is evaluating the expression looking for a string
that will be executed as an Ex command (also called colon commands). In other words:
exec ToggleDimTags() | " <-- ToggleDimTags() is evaluated and returns 0
exec 0
Which is:
:0
Now, :h :call:
:cal :call E107 E117
:[range]cal[l] {name}([arguments])
Call a function. The name of the function and its arguments
are as specified with |:function|. Up to 20 arguments can be
used. **The returned value is discarded**.
[...]
Update
I've been thinking about your function, and using the ternary operator and a bit
of :execute magic, you can simplify it up to a point where you discard the extra
function:
nnoremap <silent> <F9> :exec "color " .
\ ((g:colors_name == "wombat256") ? "dimtag" : "wombat256")<CR>
Here, this nnoremap will not produce output (<silent>) and is based on
:exec, which is followed by this expression:
"color " . ((g:colors_name == "wombat256") ? "dimtag" : "wombat256")
When g:colors_name is set to wombat256, the expression evaluates to:
"color dimtag"
Or, otherwise:
"color wombat256"
Then either one is evaluated by :exec. Of course you can join the lines
(without forgetting to remove the backslash), I did it like this simply to avoid
a too long line.

Related

How to make f and t wrap around the current line in vim

Is there a way to make the 'f' and 't' command wrap around the line? For example, if I have
Hello, my name is _intz,
where _ denotes my cursor position, I would like to be able to press fl for vim to place my cursor on the first l on the line.
Similarly, I would ideally like the , and ; commands to also wrap on the current line.
Thank you
No, this is not possible without implementing the feature yourself.
Note that fF are universally expected to mean "next on the line" and tT to mean "previous on the line", both of which being extremely useful in their own right. Instead of changing their meaning, and thus reducing the overall usefulness of Vim, you should consider making new commands.
Something like these quick and dirty mappings:
" move the cursor on first occurrence of character on the line
nnoremap <expr> <key> '0f' . nr2char(getchar())
" move the cursor before first occurrence of character on the line
nnoremap <expr> <key> '0t' . nr2char(getchar())
See :help <expr>, :help nr2char(), :help getchar().
With the help of this answer https://vi.stackexchange.com/questions/29167/determine-if-there-is-a-matching-character-on-the-current-line-past-the-cursor, the following maps <c-f> to allow that gives it the functionality of f with same line wrapping.
function!Neweff()
let character = nr2char(getchar())
let beforejump = getpos('.')
execute 'norm! f'.character.''
let afterjump = getpos('.')
if beforejump == afterjump
let firstcharacter = getline(".")[0]
execute 'norm! 0'
if character !=# firstcharacter
execute 'norm! f'.character.''
endif
endif
endfunction
nnoremap <c-f> :call Neweff()<CR>

`imap` conditional to white space left of cursor

Is it possible to have the following command be in effect
imap <Tab> <C-N>
unless the character left of the cursor is a white space, in which case, <Tab> should be <Tab>
(or as an interesting variant, unless the text left of the cursor matches ^\s*$, where $ stands for the end of the string left of the cursor) ?
Edit : solution
Thanks to the link provided by #Meninx
I found the following snippet in the vim Help
function! CleverTab()
if strpart( getline('.'), 0, col('.')-1 ) =~ '^\s*$'
return "\<Tab>"
else
return "\<C-N>"
endif
endfunction
inoremap <Tab> <C-R>=CleverTab()<CR>
which precisely implements what I wanted.
You do not need the <C-R>= in the inoremap. It is wiser to use an <expr> argument to inoremap, it will evaluate the last argument as an expression and then substitute the result. In your example it would look as follows:
function! CleverTab()
if strpart( getline('.'), 0, col('.')-1 ) =~ '^\s*$'
return "\<Tab>"
else
return "\<c-x>\<c-p>"
endif
endfunction
inoremap <expr> <tab> CleverTab()
Most inoremap maps are performed with <expr>, otherwise things become too complex too quickly.
Note that I also changed <C-N> to <c-x><c-p>. In most cases a context sensitive completion will find a better match looking backward than forward, since you are using it to repeat some text.
Also, we have a Vim specific part of the website: https://vi.stackexchange.com/

Vim: Make function with count only operate once

I have this function:
function Test()
echom "call " . v:count
endfunction
nnoremap a :call Test()<cr>
If I type, let's say 4a, it will print out
call 4
call 4
call 4
call 4
However, I want it only to be executed once, when I use a count. How can I achieve that?
You need <C-U> before calling the function.
function Test()
echom "call " . v:count
endfunction
nnoremap a :<C-U>call Test()<cr>
<C-U> removes all the characters before the cursor on command line. From help:
c_CTRL-U
CTRL-U Remove all characters between the cursor position and
the beginning of the line. Previous versions of vim
deleted all characters on the line. If that is the
preferred behavior, add the following to your .vimrc:
:cnoremap <C-U> <C-E><C-U>

Understand :iabbrev <buffer> iff if:<left>

I typed :autocmd FileType python :iabbrev <buffer> iff if:<left> as this tutorial told.
The output was
if :
Why is there a space between if and ":"?
I assume you're using the space bar after you type iff? If so, it's because of the <left>. This is positioning your cursor one to the left, i.e. between the f and the ":". Once the space bar is accepted your cursor is in between the two characters so it puts a space between them. You can try the command without the <left> and see if that does what you need. If not, you'll need to let us know exactly what output you're looking for us to be able to help you. Also see: :help abbrev if you haven't already.
Abbreviation's are triggered by non-keyword (e.g. ., <cr>, <space>, etc), <esc>, or <c-]>. Typing iff alone will is not enough to expand the abbreviation. You typed iff<space> which is enough to expand the abbreviation and puts the <space> inside your expanded abbreviation. You can use <c-]> to expand abbreviations without inserting any extra characters. e.g. iff<c-]>
Eatchar
I however find using <c-]> to be unappealing. Vim's documentation gives us an alternative, the Eatchar function. This function will consume a key matching some pattern and not output it.
function! Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunction
iabbr <buffer> iff if:<left><c-r>=Eatchar('\s')<cr>
Rails.vim like abbreviations
You can take this even further and make Rails.vim-esque abbreviations which only expand on <tab> or a supplied pattern. Think of these as lightweight snippets.
function! RailsExpand(root, good, ...)
let c = nr2char(getchar(0))
if c == "" || c =~ (a:0 ? a:1 : "\t")
return a:good
else
return a:root . c
endif
endfunction
iabbr <buffer> iff <c-r>=RailsExpand('iff', "if:\<left>")<cr>
Now iff<tab> will expand properly. However defining abbreviations like this is a mess.
function! Railsabbrev(root, good)
let good = substitute(a:good, '[\"|]', '\\&', "g")
let good = substitute(good, '<', '\\<lt>', "g")
let root = substitute(a:root, '[\"|]', '\\&', "g")
let root = substitute(root, '<', '\\<lt>', "g")
execute "iabbr <buffer> " . a:root . " <c-r>=RailsExpand(\"" . root . "\", \"" . good . "\")<cr>"
endfunction
command! -nargs=* Railsabbrev call Railsabbrev(<f-args>)
Now you can use :Railsabbrev to define your <tab> expanding abbreviation. Example:
Railsabbrev iff if:<left>
Snippets
Sometimes abbreviations are just too simple or too tricky to maintain for multiline expansions. If this is the case I suggest you look for a good snippet plugin. Good choices are UltiSnips or vim-snipmate. Look at their documentation on how to expand and create your own snippets.
More help
:h Abbreviations
:helpg Eatchar

How to use vim variables in an external filter command in visual mode?

I'm trying to make a code pretty printer filter (e.g. perltidy) accept arbitrary options depending on vim variables. My goal is to pass project specific options to an external command used as a filter (:!) in visual mode.
The following expresses my intention (the last line is problematic):
" set b:perltidy_options based on dirname of the currently edited file
function! SetProjectVars()
if match(expand("%:p:h"), "/project-foo/") >= 0
let b:perltidy_options = "--profile=$HOME/.perltidyrc-foo --quiet"
elseif match(expand("%:p:h"), "/project-bar/") >= 0
let b:perltidy_options = "--profile=$HOME/.perltidyrc-bar --quiet"
else
let b:perltidy_options = "--quiet"
endif
endfunction
" first set the project specific stuff
autocmd BufRead,BufNewFile * call SetProjectVars()
" then use it
vnoremap ,t :execute "!perltidy " . b:perltidy_options<Enter>
However, the last line (vnoremap) is an error in vim, because it expands to:
:'<,'>execute "!perltidy " . b:perltidy_options
and the execute command cannot accept a range.
But I'd like to have this:
:execute "'<,'>!perltidy " . b:perltidy_options
How can I do this?
p.s. My perltidy is configured to act like a unix filter and I use vim 7.3.
You can use <C-\>e and getcmdline() to preserve command-line contents:
vnoremap ,t :<C-\>e'execute '.string(getcmdline()).'."!perltidy " . b:perltidy_options'<CR><CR>
, but in this case I would suggest simpler <C-r>= which purges out the need for :execute:
vnoremap ,t :!perltidy <C-r>=b:perltidy_options<CR><CR>
If you ever want to get rid of a range in command (ex) mode,
CRL-u will do just that.
vnoremap ,t :execute "!perltidy " . b:perltidy_options<Enter>
becomes
vnoremap ,t :<C-u>execute "!perltidy " . b:perltidy_options<CR>
:h c_CTRL-u
Happy vimming,
-Luke

Resources