Vim functions for cleaning up hidden characters - vim

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).

Related

Make sure all indents in a block of text are the same in vim

I am having the hardest time indenting things consistently in vim, and get no end of IndentationError in python. Even setting all these tab-related settings: https://stackoverflow.com/a/234578/651174 makes things no better.
How would I do the following in vim to normalize all indentation for a block of text? https://gyazo.com/a333f05e8b4d8034967029005f77ea27
I've tried doing the ctrl v visual mode, but can't seem to figure out how to use that for my purpose, or if there's something else I should be doing here. What would be the best way to do the above?
use visual mode to select a block of text and hit '=' to indent the code correctly. To effect the whole file use gg=G, which translates to go to the beginning of the file and indent correctly to the bottom of the file.
Put the following in your vimrc and use :ReformatFile for easy re-indenting.
function ReformatFile()
exec "normal! mqHmw"
exec "normal! gg=G"
exec "normal! 'wzt`q"
endfunction
command ReformatFile silent call ReformatFile()
mqHmw marks your current position and and the top of your current window see :h marks
"'wzt`q" moves your cursor to the mark w, moves the line to the top of the file and than to the line and column of mark q
Sry for my bad english, but try to type out the commands in vim and read the docs. The marks q/w are chosen arbitrary.

vim: need to workaround a bug in undofile()

The persistent undo feature in vim uses an internal function undofile() to determine the filename of the undofile for the current buffer. There is a bug in vim 8.0 on one of my machines where undofile() does not correctly escape the path separator.
:echo undofile(#%)
/home/bhawkins/.vim/undo/%home%bhawkins%some%path%to%code.c
Those bare % characters don't work on this machine (not sure why not). The correct result of this command should be:
:echo undofile(#%)
/home/bhawkins/.vim/undo/\%home\%bhawkins\%some\%path\%to\%code.c
So it fails to load the undo history, even though it saves the history correctly. For example, when I manually load the undo file, persistent undo magically starts working (just for that one buffer):
:rundo /home/bhawkins/.vim/undo/\%home\%bhawkins\%some\%path\%to\%code.c
I tried setting this up as an autocmd FileReadPost in my .vimrc by putting the result of undofile() into a let variable--but I can't figure out how to escape the % characters.
You can try this:
1 redir => oldundo
2 echo undofile(#%)
3 redir end
4 let oldundo=oldundo[1:]
5 let newundo=substitute(oldundo,"%","\\\\%","g")
The first three lines capture the undo path in oldundo variable.
Line 4 removes the first character (^#) from oldundo. The last line replaces % with \% and stores the result in newundo. Finally to use the new path you can type:
:rundo <c-r>=newundo<cr>
Here <c-r> means Ctrl + r. And <cr> represents the Enter key.
It turns out to be very tricky to reference a variable in an autocmd:
autocmd BufReadPost * :let undofilename=substitute(undofile(#%), "%", "\\\\%", "g")
autocmd BufReadPost * :silent execute "rundo ".g:undofilename
In my opinion this is yet another reason that vim should use a well-organized language like python for its command line and scripts, instead of torturing people with this campy homegrown mess known as "vimscript".

Vimscript - Previous buffer & propagate error message

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

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"

The behavior of ; to repeat the last t command bothers me. Can you help me make it better?

Okay, suppose we have a line of text:
[s]tackoverflow rocks
where the brackets show the location of the cursor, in normal mode. After pressing tr, you get:
stackov[e]rflow rocks
Now for the fun part. What happens if you push ; to repeat the command? Nothing! Vim finds the next 'r' (immediately to the right of the cursor) and positions itself just left of that (where it already was).
I would prefer that ; advance the cursor to this position:
stackoverflow[ ]rocks
This can be achieved by using l to move right one character before pressing ;, but the extra step is irritating. There is a similar issue with T, but not with f and F. Is there some way to make ; behave the way I want with t and T?
As of Vim version 7.3.235 this annoyance has been amended. Now the default is the behaviour you had expected in the first place: ; makes the cursor jump to right before the second "r".
This was the patch announcement:
Patch 7.3.235
Problem: ";" gets stuck on a "t" command, it's not useful.
Solution: Add the ';' flag in 'cpo'. (Christian Brabandt)
The old behaviour has been demoted to a compatibility option. You can bring it back with :set cpo+=;. See :h cpo-;.
Maybe it's not the answer you are looking for but I could not resist writing a VIM script for this. I put it in my .vimrc and it works for me:
map ; :call Semicolon()<CR>
function Semicolon()
let s:pos1 = getpos(".")
normal! ;
let s:pos2 = getpos(".")
if s:pos1 == s:pos2
normal! 2;
endif
endfunction
The basic idea is that ; will not move to the next match, but 2; will (if there is a match). The script supports ; after any of tTfF. The easiest way to implement the , command is to write a similar function for that.
EDIT
Changed the script after Luc's excellent suggestion
EDIT2
OK, these things are always more difficult than I originally think. The current mapping has the following problems:
Suppose you did a search like tr above. Now what should d; or c; do? As far as I'm concerned they should delete or change up until the first r not the second. This can be solved by only doing the mapping for normal and visual mode, not operator pending mode.
The current mapping does not work in visual mode. i.e., if you type v;;;; after the first ; the editor is no longer in visual mode (because of the :call). This can be solved by calling the function using #= instead of :call.
So now I end up with the following in my .vimrc (I also made one function for , and ;):
" Remap ; and , commands so they also work after t and T
" Only do the remapping for normal and visual mode, not operator pending
" Use #= instead of :call to prevent leaving visual mode
nmap ; #=FixCommaAndSemicolon(";")<CR>
nmap , #=FixCommaAndSemicolon(",")<CR>
vmap ; #=FixCommaAndSemicolon(";")<CR>
vmap , #=FixCommaAndSemicolon(",")<CR>
function FixCommaAndSemicolon(command)
let s:pos1 = getpos(".")
execute "normal! " . a:command
let s:pos2 = getpos(".")
if s:pos1 == s:pos2
execute "normal! 2" . a:command
endif
return ""
endfunction
2 comments one: Can you map ; to the lt_ command you are looking for? 2nd: Why not use 2tr or /r followed by n instead?
It sounds like your problem is more with the behaviour of t rather than ;.
In your example, lets say you start at 'e':
stackov[e]rflow rocks
I'm guessing you'd (reasonably) expect tr to jump forward to [ ]rocks rather than staying in place.
If so, you should leave ; as is and perhaps remap t to lt or something.

Resources