Vimscript - Previous buffer & propagate error message - vim

I'm working on a function that opens a help document in the current window, rather than a new split.
This is the function now:
function! OpenHelpInCurrentWindow(topic) " {{{2
" Open a helpfile in the current window, rather than a new split.
try
" Open the help document.
execute 'help '.a:topic
" Get buffer id of the help document.
let l:help_buffer = bufnr("%")
" Close the help buffers' window. This *should* return us to the window we ran
" this command from.
wincmd c
" Switch to the help doc's buffer in the current window.
:execute "buffer ".help_buffer
catch
echo 'Sorry, no help for ' . a:topic . '.'
endtry
endfunction
I map this to :H with:
command! -nargs=? -complete=help H call OpenHelpInCurrentWindow(<q-args>)
There are currently two issues:
If you enter a bad name for a help file, the help file doesn't open. When using the built-in help command (:help) this displays an error message in red, but doesn't require you to hit to continue.
If I display the error message with echoerr, it displays the message in red, but also displays the line number and requires to continue (not the way :help works).
If I display the error message with echo, it displays in the foreground color (not red like :help, but doesn't display the line number or require to continue).
Any ideas how I can make this more closely resemble the default behavior?
The second issue is that when you open a help document using this function, the previous buffer is not set correctly. Trying to return to where I was using :bprevious dumps me into the wrong document.
Any ideas why this is occuring, what tracks the 'buffer list' (I don't think this is tagstack), or how I might fix it?
Thanks!

Yes, :echoerr from within a function prints out multi-line information. The totally correct way would be to move that part out of the function, and put the :echoerr directly into the :command definition, but it's cumbersome. (My ingo-library plugin has a helpful ingo#err#Get() function for that.)
Most plugins instead just use :echohl and :echomsg to emulate the error reporting:
echohl ErrorMsg
echomsg 'Sorry, no help for ' . a:topic . '.'
echohl None
The only downside is that it doesn't abort a command chain, e.g. :H foo | quit.
With regards to :bprevious, that navigates by buffer number. As the help buffer is a buffer, too, the order gets messed up. You need to use other navigation means, e.g. the alternate file (<C-^>, :edit #), or arguments and :argnext.

Forget the window shuffling. We can open help in the current buffer by set buftype=help
We have the following problems:
restore the 'buftype' value of the previous buffer, #, in the success case
restore the 'buftype' value of the current buffer, %, in the error case
let the :h error message through correctly
The :h command like so many of Vim's command will set v:errmsg when an error messages occurs. This means we can detect the error message. However as #Ingo Karkat mentioned this will give a "stack trace" when used inside of a function. We can overcome this by suppressing the error via :silent!. Now we can check v:errmsg and restore 'buftype' correctly.
Since you still want to keep the error message we will use :execute in the :command definition and have the function return the failed help command.
Below is the resulting code:
function! s:help(subject)
let buftype = &buftype
let &buftype = 'help'
let v:errmsg = ''
let cmd = "help " . a:subject
silent! execute cmd
if v:errmsg != ''
let &buftype = buftype
return cmd
else
call setbufvar('#', '&buftype', buftype)
endif
endfunction
command! -nargs=? -bar -complete=help H execute <SID>help(<q-args>)
Thoughts
You may want to look into something like cmdallias.vim to make this command easier to type
Maybe instead of all this trouble just promote the help window to its own tab via <c-w>T (my preference)
the technique will need to be adjusted to take into consideration if there is a different a window with buftype=help already open
More help
:h 'buftype'
:h :h
:h :silent
:h v:errmsg
:h :exe

Related

Is it possible to fix the 'help-curwin' command / function from vim help tips?

I do not understand vim script very well, just enough to tinker with my vimrc. So I am looking for someone's help to understand what can be done here.
I use this HelpCurWin command from the vim help page 'tips.txt', tag 'help-curwin'. The command is designed to open a given help page in the current window instead of opening a new split. This part works fine. Here is the code:
" Command to open help page in current window.
command -bar -nargs=? -complete=help HelpCurwin
\ execute s:HelpCurwin(<q-args>)
let s:did_open_help = v:false
function s:HelpCurwin(subject) abort
let mods = 'silent noautocmd keepalt'
if !s:did_open_help
execute mods .. ' help'
execute mods .. ' helpclose'
let s:did_open_help = v:true
endif
if !getcompletion(a:subject, 'help')->empty()
execute mods .. ' edit ' .. &helpfile
endif
return 'help ' .. a:subject
endfunction
Here is a slightly diferent version from the nvim documentation (I tried both):
" Command to open help page in current window.
command -bar -nargs=? -complete=help HelpCurwin
\ execute s:HelpCurwin(<q-args>)
let s:did_open_help = v:false
function s:HelpCurwin(subject) abort
let mods = 'silent noautocmd keepalt'
if !s:did_open_help
execute mods .. ' help'
execute mods .. ' helpclose'
let s:did_open_help = v:true
endif
" Fix from nvim documentation.
if !empty(getcompletion(a:subject, 'help'))
execute mods .. ' edit ' .. &helpfile
endif
return 'help ' .. a:subject
endfunction
As I was saying the main goal of opening the help page in the current window is achieved, but, there are 3 side effects I noticed:
When I open a specific help page, the default 'help.txt' page will automatically be added to the buffer list,
The 'help.txt' page will not be added to the list as an "unlisted" buffer which clutters the tabline, and the buffer list,
Even if I issue :HelpCurwin with no page specified, the 'help.txt' page will be listed as a regular buffer and not get syntax highlighting (even though the filetype is correctly set).
Those are not critical issues, but it is bugging me, and I would be grateful if anyone could help me fix it, or, if impossible, explain me why.
EDIT: I forgot to specify that I am personally using this function mostly from vertically split windows, so I am looking to improve it primarily for that use case.
Here's a solution I came up with that is a bit less hacky (IMO) than the recommended solution and doesn't suffer from the problem of polluting the buffer list.
" Open help in the current window with :Help
command! -bar -nargs=? -complete=help Help call s:HelpHere(<q-args>)
function! s:HelpHere(subject) abort
" remember the state of the windows
let l:window_id = win_getid()
let l:window_width = winwidth(0)
let l:num_windows = winnr('$')
" open the help window
execute 'help ' .. a:subject
" close the original window if a new help window was opened, and
" if it should have opened immediately above the original window
if l:num_windows < winnr('$') && (l:num_windows == 1 || l:window_width >= 80)
call win_execute(l:window_id, 'close')
endif
endfunction
The new :Help command is implemented by the function s:HelpHere. This function just passes its argument to the standard :help command, then closes the original window if :help created a new window.
Most of the time, this simulates opening the help window in the window that :Help was invoked from.
However, if the standard :help command is invoked from a narrow window, then it will create a new window on top of potentially several vertically split windows, so closing the original window doesn't simulate what we want. In this case, my :Help command doesn't close the original window and just preserves the default behavior of :help.

How do I close a buffer while keeping the window open? [duplicate]

Vim's multilayered views (Windows, Buffers and Tabs) left me a little confused. Let's say I split the display (:sp) and then select a different buffer to display in each window. Now I want to close one of the buffers, yet I don't want the window to close (After the closing it can display the next buffer on the list or an empty buffer, it doesn't matter). How can I do this?
I messed with this a bit and finally came up with:
:bp | sp | bn | bd
Here's the copy/paste version for key mapping:
:bp<bar>sp<bar>bn<bar>bd<CR>
I've tested it a fair bit and it works consistently in various conditions. When used on the last buffer it will leave you with a new blank buffer.
Throw this in your .vimrc:
map <leader>q :bp<bar>sp<bar>bn<bar>bd<CR>
Restart Vim, or just :source ~/.vimrc for changes to take effect. Next time you want to close a buffer just type: \q (if \ is your leader key)
I searched for this today and came up with
:b#|bd#
which changes the current window to the previously open buffer and deletes/hides the buffer you just switched away from.
This requires at least two known buffers.
If another window but the current shows the same buffer this will still destroy splitting. You can change all windows to the previously open buffer with
:windo b#
I added more detail about the former command discussing a mapping for it (and some pitfalls) in an answer to a similar question.
There's a script on the Vim wiki to do this. I don't think there is a builtin that does what you want.
The latest version of vim-bufkill is on github.
nmap <leader>d :bprevious<CR>:bdelete #<CR>
Works as it should until one buffer is open in several windows. Good enough unless you want to use the bigger scripts out there.
Edit: this is what i use right now:
function! BufferDelete()
if &modified
echohl ErrorMsg
echomsg "No write since last change. Not closing buffer."
echohl NONE
else
let s:total_nr_buffers = len(filter(range(1, bufnr('$')), 'buflisted(v:val)'))
if s:total_nr_buffers == 1
bdelete
echo "Buffer deleted. Created new buffer."
else
bprevious
bdelete #
echo "Buffer deleted."
endif
endif
endfunction
I think this is what you're looking for
http://www.vim.org/htmldoc/windows.html#window-moving
Try this:
Look ar your buffer id using
:buffers
you will see list of buffers there like
1 a.cpp
2 b.py
3 c.php
if you want to remove b.py from buffer
:2bw
if you want to remove/close all from buffers
:1,3bw
For those who use NERDTree.
I fix this using this plugin https://github.com/jistr/vim-nerdtree-tabs and now I can close the only buff/file/tab without closing the window.
After having the plugin above installed put the following code on my .vimrc:
let g:nerdtree_tabs_autoclose=0
The description for the variable above is: Close current tab if there is only one window in it and it's NERDTree (default 1)
More info here: https://github.com/jistr/vim-nerdtree-tabs#configuration
A simple version I use personally is
:bp|bd#
It goes to the previous buffer and deletes the other buffer (which is actually the original where we jumped from).
This does what you would expect in 99% of cases without any complicated scripts.
As a keyboard shortcut I use the following
nnoremap <silent> <Leader>c :bp<BAR>bd#<CR>
I don't think there is a one shot way to do this, but you could do :enew or :ls to list your buffers and swap to a different one using :b [number].
Once you've got a different buffer in the window :bd # will delete the previous buffer in the window, and since the current buffer still exists the window won't be closed.
I used to use :
:bp<bar>sp<bar>bn<bar>bd<CR>
But I found certain occasions where it closed my window.
Recently I noticed that I always use this when I am working on a project and need to quickly open my .tmux.conf .zshrc before going back to work.
For this usage, I find better to :
switch back to the buffer I was previously working on with C-6
type :bd# to delete the previous buffer (I have mapped it like this : nnoremap <leader>d :bd#<CR>)
It allows me to control the buffer I'm going back to and feels more natural.
Here is a very readable vimscript function, which handles all cases well,
behave similar to built-in:bd (if only one window, just invoke it!),
issue a warning and do nothing if buffer modifed.
if no other buffer, create one, via :enew.
if alternate buffer exist and in buffer-list, switch to it, else go next, via:bn.
more reasonable behavior for multiple-window layout
not closing any window,
always stay on the original window.
for each window that displays current buffer, do as listed above, then delete old current buffer.
nnoremap <Leader>b :call DeleteCurBufferNotCloseWindow()<CR>
func! DeleteCurBufferNotCloseWindow() abort
if &modified
echohl ErrorMsg
echom "E89: no write since last change"
echohl None
elseif winnr('$') == 1
bd
else " multiple window
let oldbuf = bufnr('%')
let oldwin = winnr()
while 1 " all windows that display oldbuf will remain open
if buflisted(bufnr('#'))
b#
else
bn
let curbuf = bufnr('%')
if curbuf == oldbuf
enew " oldbuf is the only buffer, create one
endif
endif
let win = bufwinnr(oldbuf)
if win == -1
break
else " there are other window that display oldbuf
exec win 'wincmd w'
endif
endwhile
" delete oldbuf and restore window to oldwin
exec oldbuf 'bd'
exec oldwin 'wincmd w'
endif
endfunc
Simply do :new|bd# or Paste this into your vimrc
let mapleader = " "
" CLOSE current Buffer without closing window
nnoremap <silent><leader>d :new<BAR>bd#<CR>
" CLOSE current window
noremap <leader>x <c-w>c
Then hit (space + d) or (space + x)
EDIT: even better with
nnoremap <silent><leader>d :new<BAR>bd#<BAR>bp<CR>
Would
:enew
do what you want? it will edit a new, unnamed buffer in the current window leaving the existing file open in any other windows.
My favorite solution for this is the bufkill.vim plugin (GitHub). It provides alternate versions of the various :b-prefix commands that work the same as their counterparts, but leave the window open. They display whatever the last visible buffer contained, or an empty/new buffer if there was no prior buffer.
From the documentation:
When you want to unload/delete/wipe a buffer, use:
:bun/:bd/:bw to close the window as well (vim command), or
:BUN/:BD/:BW to leave the window(s) intact (this script).
To 'close' a view, use :hid[e]. Works if you have managed to split the viewport or opened multiple files. You can't hide the last buffer on display.
1 Further tip that helped me: use :e ./path/to/file.work to open a file in viewport without splitting the window.
P.S. At two days into vim I still have trouble finding the precise help commands. Hopefully this will help someone else keep working until they really get time to understand vim.
If you're like me and you came here trying to do the opposite, close the window without closing the buffer, you can do that via:
:close
I like this answer most, although I prefer
<CTRL-^>:bd#
because it is faster to type and I don't need a keymapping.
The command <CTRL-^> (same as on English keyboard) switches to the alternate file and :bd# deletes the other buffer.
use ":bd" as a command.

Vim functions for cleaning up hidden characters

Currently in my .vimrc file I have a function which clears all trailing white spaces on save, while retaining my mouse position.
fun! <SID>StripTrailingWhitespaces()
let l = line(".")
let c = col(".")
%s/\s\+$//e
call cursor(l, c)
endfun
autocmd BufWritePre *.sql,*.php :call <SID>StripTrailingWhitespaces()
This works great. But I would like to add a few more things to it like:
* Remove carriage returns
* Fix indent SP followed by a TAB
I tried adding
%s/^M//e
to my StripTailingWhitespaces() function, but when I save now vim tells me
Press ENTER or type command to continue
So I think I did something wrong or am forgetting something. Any help figuring this out? Thanks
UPDATE: Still working on this problem. I've tried adding a <CR> at the end of my searches in the StripTrailingWhitespaces function as well as at the end of the BufWritePre command. No luck. In fact, adding it gives me lots of "Trailing Spaces" errors. Any more suggestions?
If not one for fixing the need to press enter problem, what about a search to fix indent SP followed by a TAB?
I have tested it with
fun! S()
let l = line(".")
let c = col(".")
%s/\s\+$//e
%s/^M//e
call cursor(l, c)
endfun
and it worked perfectly with Vim 7.3 (Note: the ^M is entered with CTRL-V CTRL-M)
So, it looks like you don't do anything wrong, and haven't forgotten anything.
Now, that doesn't help you going further, does it?
If you have this message, try :messages, maybe this will give you a hint.
Also, :help messages reads
Press ENTER or type command to continue
This message is given when there is something on the screen for you to read,
and the screen is about to be redrawn:
- After executing an external command (e.g., ":!ls" and "=").
- Something is displayed on the status line that is longer than the width of
the window, or runs into the 'showcmd' or 'ruler' output.
So, this section might be worth reading (it's longer than the one I pasted).

How do I turn on search highlighting from a vim script?

If I do either of the following two:
call search("searchString")
exec "/ searchString"
From a script, then vim does the search but does not highlight the results, even though hlsearch. Doing the same searches from outside a script highlights the results.
Just found out the answer myself:
call search(l:searchString)
call matchadd('Search', l:searchString)
The
feedkeys()
function is the key (pun intended):
call feedkeys("/pattern\<CR>")
or cleaner:
" highlights – or doesn’t – according to 'hlsearch' option
function SearcH(pattern)
let #/ = a:pattern
call feedkeys("/\<CR>")
endfunction
I know this is late. However when I searched for the answer to this problem this page came up. So I feel compelled to help fix it.
call search(l:searchString)
call matchadd('Search', l:searchString)
Did not work for me. (when run from inside a function) It did higlight the words I wanted to search for but n/N wouldn't cycle between them. Also when I performed a new search the "l:serachStirng" pattern still remained highlighted. This answer on this link worked much better
Vim search and highlighting control from a script
Which gave me:
let #/ = l:searchString
then run
normal n
outside the funciton (so the highlighting is done immediately without the user needing to press n)
To turn on, press ESC type :set hls
To turn off, press ESC type :set nohls
Found answer here:
http://vim.1045645.n5.nabble.com/highlighting-search-results-from-within-a-function-tt5709191.html#a5709193
```
One solution would be
function! XXXX()
execute '/this'
return #/
endfunction
and to use the following instead of ":call XXXX()".
:let #/ = XXXX()
```
I believe this works from inside a function
(to just enable highlighting and nothing more):
call feedkeys(":\<C-u>set hlsearch \<enter>")
You need to put this in your .vimrc file
" Switch syntax highlighting on, when the terminal has colors
" Also switch on highlighting the last used search pattern.
if &t_Co > 2 || has("gui_running")
syntax on
set hlsearch
endif
The .vimrc file is usually located in your home directory, or you can find it using "locate .vimrc"

How can I close a buffer without closing the window?

Vim's multilayered views (Windows, Buffers and Tabs) left me a little confused. Let's say I split the display (:sp) and then select a different buffer to display in each window. Now I want to close one of the buffers, yet I don't want the window to close (After the closing it can display the next buffer on the list or an empty buffer, it doesn't matter). How can I do this?
I messed with this a bit and finally came up with:
:bp | sp | bn | bd
Here's the copy/paste version for key mapping:
:bp<bar>sp<bar>bn<bar>bd<CR>
I've tested it a fair bit and it works consistently in various conditions. When used on the last buffer it will leave you with a new blank buffer.
Throw this in your .vimrc:
map <leader>q :bp<bar>sp<bar>bn<bar>bd<CR>
Restart Vim, or just :source ~/.vimrc for changes to take effect. Next time you want to close a buffer just type: \q (if \ is your leader key)
I searched for this today and came up with
:b#|bd#
which changes the current window to the previously open buffer and deletes/hides the buffer you just switched away from.
This requires at least two known buffers.
If another window but the current shows the same buffer this will still destroy splitting. You can change all windows to the previously open buffer with
:windo b#
I added more detail about the former command discussing a mapping for it (and some pitfalls) in an answer to a similar question.
There's a script on the Vim wiki to do this. I don't think there is a builtin that does what you want.
The latest version of vim-bufkill is on github.
nmap <leader>d :bprevious<CR>:bdelete #<CR>
Works as it should until one buffer is open in several windows. Good enough unless you want to use the bigger scripts out there.
Edit: this is what i use right now:
function! BufferDelete()
if &modified
echohl ErrorMsg
echomsg "No write since last change. Not closing buffer."
echohl NONE
else
let s:total_nr_buffers = len(filter(range(1, bufnr('$')), 'buflisted(v:val)'))
if s:total_nr_buffers == 1
bdelete
echo "Buffer deleted. Created new buffer."
else
bprevious
bdelete #
echo "Buffer deleted."
endif
endif
endfunction
I think this is what you're looking for
http://www.vim.org/htmldoc/windows.html#window-moving
Try this:
Look ar your buffer id using
:buffers
you will see list of buffers there like
1 a.cpp
2 b.py
3 c.php
if you want to remove b.py from buffer
:2bw
if you want to remove/close all from buffers
:1,3bw
For those who use NERDTree.
I fix this using this plugin https://github.com/jistr/vim-nerdtree-tabs and now I can close the only buff/file/tab without closing the window.
After having the plugin above installed put the following code on my .vimrc:
let g:nerdtree_tabs_autoclose=0
The description for the variable above is: Close current tab if there is only one window in it and it's NERDTree (default 1)
More info here: https://github.com/jistr/vim-nerdtree-tabs#configuration
A simple version I use personally is
:bp|bd#
It goes to the previous buffer and deletes the other buffer (which is actually the original where we jumped from).
This does what you would expect in 99% of cases without any complicated scripts.
As a keyboard shortcut I use the following
nnoremap <silent> <Leader>c :bp<BAR>bd#<CR>
I don't think there is a one shot way to do this, but you could do :enew or :ls to list your buffers and swap to a different one using :b [number].
Once you've got a different buffer in the window :bd # will delete the previous buffer in the window, and since the current buffer still exists the window won't be closed.
I used to use :
:bp<bar>sp<bar>bn<bar>bd<CR>
But I found certain occasions where it closed my window.
Recently I noticed that I always use this when I am working on a project and need to quickly open my .tmux.conf .zshrc before going back to work.
For this usage, I find better to :
switch back to the buffer I was previously working on with C-6
type :bd# to delete the previous buffer (I have mapped it like this : nnoremap <leader>d :bd#<CR>)
It allows me to control the buffer I'm going back to and feels more natural.
Here is a very readable vimscript function, which handles all cases well,
behave similar to built-in:bd (if only one window, just invoke it!),
issue a warning and do nothing if buffer modifed.
if no other buffer, create one, via :enew.
if alternate buffer exist and in buffer-list, switch to it, else go next, via:bn.
more reasonable behavior for multiple-window layout
not closing any window,
always stay on the original window.
for each window that displays current buffer, do as listed above, then delete old current buffer.
nnoremap <Leader>b :call DeleteCurBufferNotCloseWindow()<CR>
func! DeleteCurBufferNotCloseWindow() abort
if &modified
echohl ErrorMsg
echom "E89: no write since last change"
echohl None
elseif winnr('$') == 1
bd
else " multiple window
let oldbuf = bufnr('%')
let oldwin = winnr()
while 1 " all windows that display oldbuf will remain open
if buflisted(bufnr('#'))
b#
else
bn
let curbuf = bufnr('%')
if curbuf == oldbuf
enew " oldbuf is the only buffer, create one
endif
endif
let win = bufwinnr(oldbuf)
if win == -1
break
else " there are other window that display oldbuf
exec win 'wincmd w'
endif
endwhile
" delete oldbuf and restore window to oldwin
exec oldbuf 'bd'
exec oldwin 'wincmd w'
endif
endfunc
Simply do :new|bd# or Paste this into your vimrc
let mapleader = " "
" CLOSE current Buffer without closing window
nnoremap <silent><leader>d :new<BAR>bd#<CR>
" CLOSE current window
noremap <leader>x <c-w>c
Then hit (space + d) or (space + x)
EDIT: even better with
nnoremap <silent><leader>d :new<BAR>bd#<BAR>bp<CR>
Would
:enew
do what you want? it will edit a new, unnamed buffer in the current window leaving the existing file open in any other windows.
My favorite solution for this is the bufkill.vim plugin (GitHub). It provides alternate versions of the various :b-prefix commands that work the same as their counterparts, but leave the window open. They display whatever the last visible buffer contained, or an empty/new buffer if there was no prior buffer.
From the documentation:
When you want to unload/delete/wipe a buffer, use:
:bun/:bd/:bw to close the window as well (vim command), or
:BUN/:BD/:BW to leave the window(s) intact (this script).
To 'close' a view, use :hid[e]. Works if you have managed to split the viewport or opened multiple files. You can't hide the last buffer on display.
1 Further tip that helped me: use :e ./path/to/file.work to open a file in viewport without splitting the window.
P.S. At two days into vim I still have trouble finding the precise help commands. Hopefully this will help someone else keep working until they really get time to understand vim.
If you're like me and you came here trying to do the opposite, close the window without closing the buffer, you can do that via:
:close
I like this answer most, although I prefer
<CTRL-^>:bd#
because it is faster to type and I don't need a keymapping.
The command <CTRL-^> (same as on English keyboard) switches to the alternate file and :bd# deletes the other buffer.
use ":bd" as a command.

Resources