Vim function to switch between splits - vim

I am trying to find a function which will switch back to the original window in a split view for vim. I know about shortcut and I also know about the function :call cursor. But is there a function which can let me switch back to the previous split window so I could stick it in my vim function?

In a vimscript you can use:
" Save the window where you are currently
let l:currentWindow=winnr()
" Do stuff where you change of window
" Go back to the original window
exe l:currentWindow . "wincmd w"
For more information the doc is always an excellent reading:
:h wincmd
:h winnr()
Edit Another way to do it is to use wincmd p:
wincmd is the vimscript equivalent to Ctrlw in normal mode.
In normal mode when you change of window you can use Ctrlw + p to come back to the previous window. So in vimscript you simply use:
wincmd p
To go back to the previous window.
Of course if the rest of your function use more than 2 splits you will not go back to your initial window but if you have only two splits it can be lighter than using a variable to keep the number of your window.

Related

How to have multiple views of the same buffer in vim?

I have been trying to implement a function in vimscript to switch between two views of the same buffer. The functionality I want to have is like this:
I press a key and the screen position and cursor are moved to my previously saved location. If there is not yet another saved location, it creates one at the current view and cursor location. I have a vimscript function that does exactly this functionality i described:
fun! SwitchFileMarker(reset)
if a:reset == 1
if exists("b:switch_file_window")
unlet b:switch_file_window
endif
endif
if exists("b:switch_file_window")
let cursor_location = getpos("'0")
let top_location = getpos("'9")
:execute "normal! m0Hm9`0"
call setpos('.', top_location)
:execute "normal! zt"
call setpos('.', cursor_location)
:execute "normal! zv"
else
" save the location
:execute "normal! m0Hm9`0"
endif
let b:switch_file_window = 1
endfun
nmap <leader>b :call SwitchFileMarker(0)<CR>
nmap <leader>B :call SwitchFileMarker(1)<CR>
The only problem with this function is I want it to save the folds of the current view and load the folds of the saved view when the function is called. I can achieve this by using :mkview and :loadview, but the problem with that is if the number of lines in the file changes, the folds are lost. The :mkview function seems to remember the folds at a specific line number, and If ive added several lines above that fold location while editing the file, when i use :loadview, the fold is lost. using the marks, as done in the function i show works to save my cursor position (but not folds) because the marks keep track of the changing line number. The funcionality that I am trying to get is essentially like having two views of a buffer but in the same window, rather than two windows. If i have a two windows editing the same buffer, I can add lines in one window and the folds are not lost in the other, so this is exactly the functionality I want, just in one window instead of two. Any suggestions how this can be done?
Use marks!
To set a mark, you can use m<letter> or :mark <letter>.
To go to a marked line, use '<letter>. To go to the exact position, prefer `<letter>.
If the letter is lowercase, it is buffer-local. If it is uppercase, it is global.
Edit: OP wants to save folds, which this can’t do. But it’s helpful to know anyway, so I’m leaving the answer.

Copy lines in vim to execute in above window opened by ter command?

I am using the latest vim version.
vim --version | head -1
VIM - Vi IMproved 8.1 (2018 May 18, compiled Aug 12 2019 17:28:55)
Edit a python file with vim.
vim embed.py
x = 3
print(x)
y =4
print(x+y)
Now open a new window with ter command in vim.
The normal way to execute embed.py which is in edit status.
:! python3 %
New window open and execute embed.py.
I have a new idea,how can copy all the lines in embed.py into the above window opened by ter command in vim?Show the expected way as below.
ggyG can't work.
Move cursor in vim window,and press ggyG.
Move cursor in the python3 window.
ctrl + v can't work, <C-\><C-N> can't work too.
It is time to try with gui way,paste nothing also.
Do as Tarun Lalwani say:
step1: copy lines into system clipboard
:%y+
or with other command.
step2: move cursor into the upper window which run python3.
step3: ctrl+v+shift
How can bind all steps with a hot key?
Status 1:
Write the following in my .vimrc.
function! CopyPasteBuffer()
normal gg"+yG
wincmd p
call feedkeys('^W"+')
endfunction
nnoremap <leader>p :call CopyPasteBuffer()<CR>
\p will put ^W"+ on python3's interactive window.
Status 2:
Write the following in my .vimrc.
function! CopyPasteBuffer()
normal gg"+yG
wincmd p
endfunction
nnoremap <leader>p :call CopyPasteBuffer()<CR>
\p will move cursor into upper window,now pressing ctrl+v+sfift can take effect.
Almost done!It remains a issue here.
The last step (step 3) which paste all program's lines into python interactive window haven't been automated into the vimscript,rkta's CopyPasteBuffer() only bind two steps with hot key \p successfully.
Please have a try in bash ,instead of zsh.
Almost same result both for normal gg"+yG and normal gg"*yG,ctrl+v+shift or ctrl+w+ctrl+v or ctrl+v can't paste content in register * if it is normal gg"*yG in CopyPasteBuffer()(verified in my bash).
There is a built-in function named term_sendkeys to send keys to a terminal buffer.
Here is a oneliner to send all lines in the current buffer to the first terminal window using term_sendkeys:
:cal term_sendkeys(term_list()[0], join(map(getbufline(bufnr('.'), 1, '$'), 'v:val . "\n"'), ''))
You can simply define a map to execute the oneliner in your .vimrc like this:
nnoremap <leader>p :<c-u>cal term_sendkeys(term_list()[0], join(map(getbufline(bufnr('.'), 1, '$'), 'v:val . "\n"'), ''))<Cr>
However oneliners are bit hard to understand at glance, so it is better to define this as a function and define a map to call it:
function! s:SendLinesToTerm()
let term_buf = term_list()[0]
let lines = getbufline(bufnr('.'), 1, '$')
let str = join(map(lines, 'v:val . "\n"'), '')
cal term_sendkeys(term_buf, str)
endfunction
nnoremap <leader>p :call <SID>SendLinesToTerm()<Cr>
To copy the current buffer, switch to the terminal running in the only split and paste the buffer contents use this function:
function! CopyPasteBuffer()
normal ggyG
wincmd p
call feedkeys("\<C-W>\"*")
endfunction
(As we are in terminal mode, we need to use Ctrl W " to paste, see :h terminal-typing for other special keys.)
This will paste everything and leave you in the terminal buffer - use Ctrl WW to switch back.
To bind it to a key use
nnoremap <leader>p :call CopyPasteBuffer()<CR>
If you didn't rebind your leader key you can execute the function with \p.
To use the function with the * register just change the function to
function! CopyPasteBuffer()
normal gg"*yG
wincmd p
call feedkeys("\<C-W>\"*")
endfunction
You have to understand that when you yank the lines in vim it is not basically going to system's clipboard. The terminal shown in the upper window can only interact with the system's clipboard
You can see the below thread on how to use system clipboard
https://vi.stackexchange.com/questions/84/how-can-i-copy-text-to-the-system-clipboard-from-vim
I use mac which has pbcopy to copy to the clipboard. So I can execute something like :silent !pbcopy < %. This will copy the file to clipboard. And then a normal CTRL+V or CTRL+SHIFT+V or CMD+V would work based on your OS
For unix you would use something like xclip
How to use the terminal window
According to this SO answer and #Amadan’s comment, in a terminal window, the command ctrl-wN (capital N) allows to exit the “insertion mode” (so that you can copy things from the terminal window); also, ctrl-w" followed by the appropriate register name (* for X primary, + for X clipboard) allows to paste the contents of the said register (that’s what you are interested in). You may also paste the primary register with ctrl-insert, and the clipboard register with the window menu you show on one of your screenshots.
How to use registers
On registers: long story short, Vim stores yanked text in various named registers. If you are running Vim from an X graphical environment, the Vim register * is connected to the X clipboard “primary” (usually the last text selected with the mouse from within a graphical application), and the Vim register + is connected to the X clipboard “clipboard” (usually the last text copied with the shortcut ctrl-v from within a graphical application). By default, Vim puts text yanked with the command yy in the register *, but you can put it in + as well, and you can change the default to + (set clipboard=unnamedplus).
Use whichever register you prefer. Only make sure you use the same when copying and pasting (i.e., by default yy will typically copy to X primary while the window menu will paste from the X clipboard).
Official source: Read :help terminal to learn how to use Vim’s terminal windows, and :help registers for Vim’s registers.
(Credible source: Google PageRank.)
However in your case, aren’t you looking rather at the Python keyword for importing a file into the REPL? Although I don’t know Python very well, it should probably looks like import embed or something.

How to make a shortcut for moving between Vim windows?

Let’s say I have single Vim tab displaying 9 buffers (equally separated, like a 3×3 table).
Currently, to get from the top left window to the bottom right one, I have to press 3, Ctrl+W, J, and then 3, Ctrl+W, L. This is cumbersome, and I would like to just be able to press Ctrl+9 to go to the 9th window, and Ctrl+3 to go to the 3rd window, etc.
Is there any easy way I can map something like this in Vim?
There's a much simpler solution than using the mouse or hard-set movement mappings; they will break if the window numberings are different from what you have in mind for a 3x3 matrix, or if you decide to work with less than 9 windows. Here's how:
Include the following in your .vimrc:
let i = 1
while i <= 9
execute 'nnoremap <Leader>' . i . ' :' . i . 'wincmd w<CR>'
let i = i + 1
endwhile
Now you can just press <Leader><number> and be taken to the window number you want. I wouldn't recommend going beyond 9, because IMO, the utility of having multiple viewports follows a Rayleigh distribution and quickly becomes useless with too many viewports in one window.
It will be helpful if you have the window number displayed in your statusline to aid you in quickly figuring out which window you're on and which window you want to go to. To do that, use this little function and add it accordingly in your statusline.
function! WindowNumber()
let str=tabpagewinnr(tabpagenr())
return str
endfunction
See it in action in your statusline:
set laststatus=2
set statusline=win:%{WindowNumber()}
Note that the above line will replace your statusline. It was just meant for illustration purposes, to show how to call the function. You should place it where ever you think is appropriate in your statusline. Here's what mine looks like:
Update
romainl asked for my status line in the comments, so here it is:
"statusline
hi StatusLine term=bold cterm=bold ctermfg=White ctermbg=235
hi StatusHostname term=bold cterm=bold ctermfg=107 ctermbg=235 guifg=#799d6a
hi StatusGitBranch term=bold cterm=bold ctermfg=215 ctermbg=235 guifg=#ffb964
function! MyGitBranchStyle()
let branch = GitBranch()
if branch == ''
let branchStyle = ''
else
let branchStyle = 'git:' . branch
end
return branchStyle
endfunction
function! WindowNumber()
let str=tabpagewinnr(tabpagenr())
return str
endfunction
set laststatus=2
set statusline=%#StatusLine#%F%h%m%r\ %h%w%y\ col:%c\ lin:%l\,%L\ buf:%n\ win:%{WindowNumber()}\ reg:%{v:register}\ %#StatusGitBranch#%{MyGitBranchStyle()}\ \%=%#StatusLine#%{strftime(\"%d/%m/%Y-%H:%M\")}\ %#StatusHostname#%{hostname()}
The last line should be a single line (be careful if your setup automatically breaks it into multiple lines). I know there are ways to keep it organized with incremental string joins in each step, but I'm too lazy to change it. :) The GitBranch() function (with other git capabilities) is provided by the git.vim plugin. There's a bug in it as noted here and I use the fork with the bug fix. However, I'm leaving both links and the blog here to give credit to all.
Also, note that I use a dark background, so you might have to change the colours around a bit if you are using a light scheme (and also to suit your tastes).
Better, more general answer:
Use countCtrl+wCtrl+w to jump to the count window below/right of the current one.
For example, if you're in the top left of a 3x3 grid and want to jump to the bottom left you'd use 7Ctrl+wCtrl+w.
Specific 3x3 grid answer:
If you're always using a 3x3 layout you could try these mappings for the numpad, which always jump to the top left and then move the appropriate amount from there, with the key's position on the keypad jumping to the window's with 'equivalent' position on the screen:
noremap <k7> 1<c-w><c-w>
noremap <k8> 2<c-w><c-w>
noremap <k9> 3<c-w><c-w>
noremap <k4> 4<c-w><c-w>
noremap <k5> 5<c-w><c-w>
noremap <k6> 6<c-w><c-w>
noremap <k1> 7<c-w><c-w>
noremap <k2> 8<c-w><c-w>
noremap <k3> <c-w>b
Edited: turns out c-w c-w goes to the top left at the start automatically. The explicit 1 is required in the first mapping, as c-w c-w without a count toggles between the current and the previously selected window.
(The Ctrl-W t mapping always goes to the top-left most window, the Ctrl-W b mapping always goes to the bottom-rightmost).
Alternatively you could map each number to jump to the Nth window, so k6 would be 6 c-w c-w, rather than trying to lay out the keys as on screen.
I prefer use standard vim keys(jkhl).
noremap <C-J> <C-W>w
noremap <C-K> <C-W>W
noremap <C-L> <C-W>l
noremap <C-H> <C-W>h
There is a trick if you notice that the first two maps can jump clock wise or the reverse, rather than just jumping up or down.
And you can also switch to any window directly with <Number><C-J>, for example, 2<C-J> will go to the 2nd window.
Not exactly what you're looking for, but if you're using a terminal that supports it, you can set the following options:
:set mouse+=a
:set ttymouse=xterm2
and click on a buffer to switch to it. Yes, with the mouse.
A bunch of other mouse behavior works too - you can click to move the insertion point, drag to select text or resize splits, and use the scroll wheel.
Ermmm... I'm pretty sure that this will not keep window layout as it is, but I use
:buf sys
to go to system.h,
:buf Sing
to go to MyLargeNamedClassSingleton.cpp
buf will do autocomplete (possibly menucompletion if so configured) so you can do
:buf part<Tab>
to list what files could match the part you typed. Beats the crap out of navigating buffers all around.
But I understand, this doesn't answer your specific question of course :)
I like to move around with the arrow keys.
I map ctr+direction to move to the next window partition in that direction.
map <C-UP> <C-W><C-UP>
map <C-DOWN> <C-W><C-DOWN>
map <C-LEFT> <C-W><C-LEFT>
map <C-RIGHT> <C-W><C-RIGHT>
You cant jump directly from one window to another but I find that it makes it very easy to move between windows

How can I reuse the same Vim window/buffer for command output, like the :help window?

Though I'm no Vim expert, I've been scratching an itch by working on a rough Vim equivalent of TextMate's ⌘R functionality to run Ruby code from a buffer and display the output.
The script currently just opens a new window (split) with :new and puts the output there. If you run it multiple times, it opens multiple windows. Ideally, I'd like it to reuse the same window within each tab page, much like :help does.
I've looked but haven't found a way to achieve this. Any pointers?
You can create a scratch buffer with a name, so that on subsequent calls you can check to see if that buffer is already open (and if so reuse it) or you need a new one. Something like this:
function! Output()
let winnr = bufwinnr('^_output$')
if ( winnr >= 0 )
execute winnr . 'wincmd w'
execute 'normal ggdG'
else
new _output
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap
endif
silent! r! ls
endfunction
I guess you could do it manually.
For Example:
:e test1.txt (or use any existing buffer)
:vs (or :new or :sp)
:b <tab> (keep pressing tab until test1.txt comes up. or use the buff no)
You may want to use the quickfix window so you can also jump to errors. If you get a ruby compiler vim plugin, you can run :make to run your code. You should see output and errors in the quickfix (:copen).

Vim close buffer but not split window

If I have 2 buffers split horizontally/vertically and want to close one of them, but i don't want to close a window. I want to keep places of split windows are the same as before closing buffer.
If I press :bd , the window in which was closed buffer also became closed.
Like #RusAlex I don't like plug-ins. I also like to know what code I enter actually does.
nmap ,d :b#<bar>bd#<CR>
In short this adds a key mapping to vim's normal mode waiting for key sequence ,d. When executed this switches to a previously open buffer and attempts to delete the buffer you switched away from.
Deleting an off-screen buffer keeps the screen split as it is.
The command consists of three space-separated parts:
nmap - add/change key mapping for mode normal
,d - key sequence to react to; first , (comma), then d
:b#<bar>bd#<CR> - key sequence to execute
The command to be executed consists of five parts:
: - switch vim to mode command-line
b# - switch window to previously open buffer
<bar> - expect a follow-up command; represents | (pipe character); used for chaining commands
bd# - delete previously open buffer, i.e. the buffer just switched away from
<CR> - execute command(s); represents carriage return, basically the keys Return or Enter
The command is in the format it is used in a configuration file like ~/.vimrc. If you want to add the mapping from within vim you prepend : (colon) - the mapping then will be lost when exiting vim:
:nmap ,d :b#<bar>bd#<CR>
When you open vim it is usually in normal mode as opposed to modes insert (indicated on the bottom of the screen by -- INSERT -- after pressing i), visual and so on. The n in nmap specifies the key mapping to be added to normal mode only. Find more on mappings here
Important notes:
b# will switch to the current buffer if it is the only known buffer.
b# may switch to a hidden/closed buffer, e.g. the one you just closed by pressing ,d.
bd# will close the current buffer if it is the only known buffer unsplitting the screen leaving you with an empty buffer.
bd# will fail if the buffer switched away from is a hidden/closed buffer.
bd# will still unsplit if after switching another window shows the buffer to close.
Additional notes:
:windo b# will switch all windows to the previously open buffer. Not sure how to combine with bd.
<CR> can be left out in which case you have to manually press Return or Enter to execute.
:nmap , displays all normal mode mappings starting with ,.
:ls lists open buffers.
It's impossible to have an empty window in vim, but you can just create a new empty file in the current window using :enew.
I'll have to check on my work computer, but I think the script I'm using for this is BufClose.
You want to delete the buffer but keep the split? You need a new buffer then - :new will do that for you, creating a buffer for a new/empty file, but you'll still need to kill the old buffer/window. In vim a window is a viewport on a buffer, so if you want an empty window you need an empty buffer.
Here's a variation on the answer provided #zenbro that keeps all your (split) windows and tabs open even if the buffer you close is the last one.
The function switches all windows pointing to the current buffer (that you are closing) to the next buffer (or a new buffer if the current buffer is the last one).
function! CloseBuffer()
let curBuf = bufnr('%')
let curTab = tabpagenr()
exe 'bnext'
" If in last buffer, create empty buffer
if curBuf == bufnr('%')
exe 'enew'
endif
" Loop through tabs
for i in range(tabpagenr('$'))
" Go to tab (is there a way with inactive tabs?)
exe 'tabnext ' . (i + 1)
" Store active window nr to restore later
let curWin = winnr()
" Loop through windows pointing to buffer
let winnr = bufwinnr(curBuf)
while (winnr >= 0)
" Go to window and switch to next buffer
exe winnr . 'wincmd w | bnext'
" Restore active window
exe curWin . 'wincmd w'
let winnr = bufwinnr(curBuf)
endwhile
endfor
" Close buffer, restore active tab
exe 'bd' . curBuf
exe 'tabnext ' . curTab
endfunction
To map it:
map <silent> <F4> :call CloseBuffer()<cr>
#RusAlex version + activate current buffer in the end need if delete buffer twice.
nmap ,d :b#<bar>bd#<bar>b<CR>
Here is some workaround:
function! CloseSplitOrDeleteBuffer()
let curNr = winnr()
let curBuf = bufnr('%')
wincmd w " try to move on next split
if winnr() == curNr " there is no split"
exe 'bdelete'
elseif curBuf != bufnr('%') " there is split with another buffer
wincmd W " move back"
exe 'bdelete'
else " there is split with same buffer"
wincmd W
wincmd c
endif
endfunction
nnoremap <silent> Q :call CloseSplitOrDeleteBuffer()<CR>

Resources