concatenate exe mode commands in command assignment - vim

I've a few files I want to apply folds to in a specific format incorporating two fixed elements and the contents of the " register. I was using a macro to do it but lately the files aren't in a consistent enough format.
I have 4 registers set by a function
#v='========= BEGIN'
#b='========== END'
#n=' =========={{{'
#m=' ==========}}}'
I find where the fold has to go yank the descriptor and then set the following and paste at the begiing and end
let #z=#v.#".#n
let #x=#b.#".#m
I tried every combination I could think of to concatenate the register assignment into a single map such as variations of
nnoremap <leader>X :'let #z=#v.#".#n | let #x=#b.#".#m'
Couldn't get it to work so added it to the function and mapped the function call to the X key.
Is there a way to chain command assignments in a key mapping? The functin works but the concept would be useful eslewhere.

Use <bar> or \|.
nnoremap <leader>X :'let #z=#v.#".#n <bar> let #x=#b.#".#m'
nnoremap <leader>X :'let #z=#v.#".#n \| let #x=#b.#".#m'
Take a look at :help map_bar

Related

How do I put the contents of the current register into a new buffer in vimscript?

I am trying to map my F7 key to do the following: take the current register and put it into a new file, which I am opening in a split:
map <F7> :sp clipboard.txt<CR> <bar> :put
The first piece works: I get a new buffer in split view called 'clipboard.txt', however, no text gets put. If I manually type :put in that buffer, the register puts as expected.
How can I script this put?
Two ways to go about this, you can either use <cr> after each command or you can use <bar> between multiple commands with a final <cr> at the end. Note that you need a <cr> at the end, otherwise Vim will just type the command in the command-line and leave it there, waiting for you to press enter.
Also, you should use nnoremap, first to make the mapping non-recursive (it would break if you remap the : key, for example) and second to make it work in normal mode, which is what's intended, I assume.
Putting it together:
nnoremap <F7> :split clipboard.txt<cr>:put<cr>
Or:
nnoremap <F7> :split clipboard.txt<bar>put<cr>
You might want to make that mapping a little more robust and useful, by:
Deleting the first line, before the pasted contents;
Making sure that deletion doesn't affect the default register used for pastes (by using the _ black-hole register)
Silencing the output of the :split and :put commands; and
Silencing the mapping itself.
Resulting in:
nnoremap <silent> <F7> :silent split clipboard.txt<bar>silent put<bar>1delete _<cr>

Merge changes using vimdiff

In my case, I have two files file1 and file2. Using vimdiff, I want to merge the changes as follows:
In first difference, place line from file1 above line from file2. It means difference such as Listing 2 in file2 and List 2 should be List 2 followed by Listing 2 in the merged file.
Reverse case in another change.
Snapshot is shown below.
How can we achieve this using vimdiff?
You can use the following basic commands to merge:
do - Get changes from other window into the current window.
dp - Put the changes from current window into the other window.
]c - Jump to the next change.
[c - Jump to the previous change.
zo - Open folded lines.
zc - Close folded lines.
zr - Unfold both files completely.
zm - Fold both files completely.
Ctrlww - change window.
:only | wq - quit other windows, write and quit.
Quirks to watch for
Both do and dp work if you are on a block of change (or just one line under a single line of change) in Normal mode, but not in Visual mode.
The undo command will only work in the buffer that was changed, so if you use dp and change your mind, you need to switch to the other buffer to undo.
:diffupdate will re-scan the files for changes (Vim can get confused, and show bogus stuff).
Visual mode and finer grained control
When selecting lines of text in Visual mode, you must use the normal commands:
:'<,'>diffget and
:'<,'>diffput.
For example:
Enter Visual mode and mark some text/lines.
Then type :diffput to push the selected lines to the other file or :diffget to get the selected lines from the other file.
To belabor the point: This means that if there is a block of changes consisting of multiple lines, then selecting a subset of lines and issueing :diffput will only apply those changes in the other buffer.
(:diffget and :diffput also accept ranges, see :h copy-diffs for more.)
Compare two buffers inside Vim
If you load up two files in splits (:vs or :sp), you can do :diffthis on each window and achieve a diff of files that were already loaded in buffers.
:diffoff can be used to turn off the diff mode.
This Vimcasts post and video show this in practice.
How to apply all changes between buffers
Make sure that all participating buffers are in diff mode (see :h start-vimdiff)
a. Get changes from a buffer to the current one:
:%diffget <buffer-number>
b. Put all changes from current buffer into another:
:%diffput <buffer-number>
(:% is a range to select the entire file; see :h :%. :ls will show currently opened buffers.)
You can switch back and forth between the two windows with Ctrlww. You can copy from one window do a Ctrlww, and then paste into the other. As you resolve differences, the highlights will change, and disappear.
Take a look at this video.
You can just switch between the windows and copy and paste to resolve the differences, as #David W. suggests in his answer, but Vim also has dedicated :diffput and :diffget commands to simplify this. With these (or the corresponding normal mode do and dp commands), you don't have to switch between windows, and the range defaults to the current change.
If you need to add instead of overwrite with the other buffer's differences (which is a rather unusual case in a classic two-way diff), you still have to yank the original lines and put them after the :diffget.
After you're done in one place, you can use the ]c, [c commands to jump to the next difference.
I am using the following mappings to deal with three-way merges (when conflictstyle=diff3)
nnoremap g1 :<C-U>call MergeKeepLeft()<CR>
nnoremap g2 :<C-U>call MergeKeepBoth()<CR>
nnoremap g3 :<C-U>call MergeKeepRight()<CR>
function! MergeKeepLeft()
let lastsearch = #/
let #/ = '<<<<<<<'
execute "normal! ?\<cr>dd"
let #/ = '|||||||'
execute "normal! /\<cr>V"
let #/ = '>>>>>>>'
execute "normal! /\<cr>d"
let #/ = lastsearch
endfunction
function! MergeKeepBoth()
let lastsearch = #/
let #/ = '<<<<<<<'
execute "normal! ?\<cr>dd"
let #/ = '|||||||'
execute "normal! /\<cr>V"
let #/ = '======='
execute "normal! /\<cr>d"
let #/ = '>>>>>>>'
execute "normal! /\<cr>dd"
let #/ = lastsearch
endfunction
function! MergeKeepRight()
let lastsearch = #/
let #/ = '<<<<<<<'
execute "normal! ?\<cr>V"
let #/ = '======='
execute "normal! /\<cr>d"
let #/ = '>>>>>>>'
execute "normal! /\<cr>dd"
let #/ = lastsearch
endfunction

Function arguments not evaluating

I'm trying to set up a couple of maps to quickly go through merge conflicts. Here's my code:
func! DiffAccept(w)
diffget a:w
diffupdate
normal ]c
endfunc
noremap dh :exec DiffAccept("//2")<CR>
noremap dl :exec DiffAccept("//3")<CR>
Every time I try to use this I get "No matching buffer for a:w". I'm clearly using this variable wrong, but it acts as expected when I change the line to "echo a:w".
Vim's evaluation rules are different than most programming languages. You need to use :execute in order to evaluate the (function argument) variable; otherwise, it's taken literally (as a buffer name):
execute 'diffget' a:w
PS: Prefer using :normal! (with !); this avoids interference from mappings.

How to make an horizontal ruler with vimscript?

i'm trying to do a command that fills up the current line with # , then goes to the begining and rewrites with the date and time, then o, this is my attempt:
:nnoremap <F6><Home>
set count = 0
normal i
while count <= columns
normal #
set count += 1
endwhile
<Home>R =strftime("%c")<CR>
normal o
the result of pressing F6 should be somehting like:
Date and time######################################################################
but yeah this is my first attempt at extending vim so please help me understand how to make this allright.
The right-hand side of a mapping is basically the same as if you typed the commands. This allows you to gradually move from repeatedly typing something to recording a macro (#q), to finally making a permanent mapping.
Here's a somewhat literal translation of your attempt:
:nnoremap <F6> 80I#<Esc>0R<C-r>=strftime("%c")<CR><Esc>o
It uses a fixed width of 80. If you want something more dynamic (like the value of 'textwidth' when it's > 0), you have to move from direct execution to programmatic interpolation via :execute, like this:
:nnoremap <F6> :execute 'normal' (&textwidth > 0 ? &textwidth : 80) . "I#\<lt>Esc>"<CR>0R<C-r>=strftime("%c")<CR><Esc>o
As you can see, this is already way more cryptic, with a mixture of Ex commands and :normal mode, quoting, etc.
The final step is a fully configurable and reusable plugin. Only for those, with elaborate error handling and stuff, you'd typically use separate :function definitions, which are then just called from your mapping:
function! s:InsertSeparator()
...
endfunction
nnoremap <silent> <F6> :call <SID>InsertSeparator()<CR>
You can use printf in vimscript.
:nnoremap <F6> I<C-r>=substitute(printf("%-80s", strftime("%c")), '\(\s\)\#<=\s', '#', 'g')<CR><Esc>o

Vim inline remap to check first character

I am trying to do a comment remap in Vim with an inline if to check if it's already commented or not. This is what I have already and of course it's not working ha ha:
imap <c-c> <Esc>^:if getline(".")[col(".")-1] == '/' i<Delete><Delete> else i// endif
What I want to do is check the first character if it's a / or not. If it's a / then delete the first two characters on that line, if it's not a / then add two // in front of the line.
What I had originally was this:
imap <c-c> <Esc>^i//
And that worked perfectly, but what I want is to be able to comment/uncomment at a whim.
I completely agree with #Peter Rincker's answer warning against doing this in insert mode, and pointing you to fully-featured plugins.
However, I couldn't resist writing this function to do precisely what you ask for. I find it easier to deal with this kind of mapping with functions. As an added bonus, it returns you to insert mode in the same position on the line as you started (which has been shifted by inserting or deleting the characters).
function! ToggleComment()
let pos=getpos(".")
let win=winsaveview()
if getline(".") =~ '\s*\/\/'
normal! ^2x
let pos[2]-=1
else
normal! ^i//
let pos[2]+=3
endif
call winrestview(win)
call setpos(".",pos)
startinsert
endfunction
inoremap <c-c> <Esc>:call ToggleComment()<CR>
Notice the modifications to pos to ensure the cursor is returned to the correct column. The command startinsert is useful in this type of function to return to insert mode. It is always safer to use noremap for mappings, unless there is a very good reason not to.
This seems to work well, but it is not very Vim-like, and you might find other plugins more flexible in the long run.
There are many commenting plugins for vim:
commentary.vim
tComment
EnhCommentify
NERD Commenter
and many more at www.vim.org
I would highly suggest you take a look at some these plugins first before you decide to roll your own. It will save you great effort.
As a side note you typically would want to comment/uncomment in normal mode not insert mode. This is not only the vim way, but will also provide a nicer undo history.
If you are dead set on creating your own mappings I suggest you create a function to do all the hard work and have your mapping call that function via :call. If you think you can get by with simple logic that doesn't need a function then you can use an expression mapping (see :h map-<expr>). You may want organize into a plugin as it could be large. If that is the case look at :h write-plugin to give you a feel
for writing plugins the proper way.
Example of a simple expression mapping for toggling comments:
nnoremap <expr> <leader>c getline(".") =~ '\m^\s*\/\/' ? '^"_2x' : 'I//<esc>`['
there's also this vimtip! http://vim.wikia.com/wiki/Comment/UnComment_visually_selected_text
i use the bottom one with the
...
noremap <silent> ,c :<C-B>sil <C-E>s/^/<C-R>=escape(b:comment_leader,'\/')<CR>/<CR>:noh<CR>
noremap <silent> ,u :<C-B>sil <C-E>s/^\V<C-R>=escape(b:comment_leader,'\/')<CR>//e<CR>:noh<CR>
,c comments out a region
,u uncomments a region

Resources