How to treat leading spaces as tabs in Vim when navigating? - vim

Basically I have the following settings for PSR-2 code compliance:
set tabstop=4
set shiftwidth=4
set softtabstop=4
set expandtab
set smarttab
PSR-2 requires indentions to be 4 spaces. That's fine, but I'm used to real tabs instead of spaces, so if I'm at the beginning of a line, and I move right, instead of moving to the first indented character, it moves over one space at a time.
Is there any way to make vim treat these leading spaces in the same way, that is, to "jump" to the first non-space character when navigating in normal mode and insert mode?
I know I can use ^ to place the cursor to the first non-whitespace character, but I'm not used to that, and it's not as convenient as simply navigating.

I think you would be better served getting used to utilizing vims more powerful movement commands, such as ^.
That being said, here is one way you could achieve what you want.
nnoremap <right> :silent call SkipSpace()<cr>
function! SkipSpace()
let curcol = col('.')
execute "normal ^"
let hatcol = col('.')
if curcol >= hatcol
" move one space right
let newcol = curcol + 1
elseif curcol + 4 > hatcol
" move to the start of the next word if it is less than 4 spaces away
let newcol = hatcol
else
" move 4 spaces right
let newcol = curcol + 4
endif
execute "normal " . newcol . "|"
endfunction
P.S. For a bit of fun check out :help |

Put below into your vimrc
function! s:super_left()
return getline('.')[:col('.')] =~ '^\s\+$' ? 'w' : 'l'
endfunction
augroup SuperLeft
au!
autocmd FileType php nnoremap <expr> l <sid>super_left()
augroup END

Related

Vim displays coloring marks on backspace

Every time I'm in insert mode, when I press backspace, something like on image below happens. On save everything is saved properly, but until then - it looks broken.
It seems like shell colour characters, but I'm not really sure.
After some testing, and removing pieces of .vimrc, seems like it's issue of delimitMate.
What could be the reason?
s:ExtraMappings() defines a map for Backspace in plugin/delimitMate.vim:
" If pair is empty, delete both delimiters:
inoremap <silent> <Plug>delimitMateBS <C-R>=delimitMate#BS()<CR>
if !hasmapto('<Plug>delimitMateBS','i')
if empty(maparg('<BS>', 'i'))
silent! imap <unique> <buffer> <BS> <Plug>delimitMateBS
endif
if empty(maparg('<C-H>', 'i'))
silent! imap <unique> <buffer> <C-h> <Plug>delimitMateBS
endif
endif
Note that <Plug>delimitMateBS is mapped to <C-R>=delimitMate#BS()<CR>. <C-R>= enters an expression, and that expression will be returned by delimitMate#BS(). See :help c_CTRL-R_=:
'=' the expression register: you are prompted to
enter an expression (see expression)
delimitMate#BS() returns key inputs to handle pairs in autoload/delimitMate.vim:
function! delimitMate#BS() " {{{
if s:is_forbidden("")
let extra = ''
elseif &bs !~ 'start\|2'
let extra = ''
elseif delimitMate#WithinEmptyPair()
let extra = "\<Del>"
elseif s:is_space_expansion()
let extra = "\<Del>"
elseif s:is_cr_expansion()
let extra = repeat("\<Del>",
\ len(matchstr(getline(line('.') + 1), '^\s*\S')))
else
let extra = ''
endif
return "\<BS>" . extra
endfunction " }}} delimitMate#BS()
By looking into the code, you can know the return value can be:
"\<BS>"
"\<BS>\<Del>"
"\<BS>\<Del>\<Del>"
…
So as I think, it's an issue of handling Backspace or Delete. See :help :fixdel for additional information.
:fix[del] Set the value of 't_kD':
't_kb' is 't_kD' becomes
CTRL-? CTRL-H
not CTRL-? CTRL-?
(CTRL-? is 0177 octal, 0x7f hex) {not in Vi}
If your delete key terminal code is wrong, but the
code for backspace is alright, you can put this in
your .vimrc:
:fixdel
This works no matter what the actual code for
backspace is.
If the backspace key terminal code is wrong you can
use this:
:if &term == "termname"
: set t_kb=^V<BS>
: fixdel
:endif
Where "^V" is CTRL-V and "<BS>" is the backspace key
(don't type four characters!). Replace "termname"
with your terminal name.
If your <Delete> key sends a strange key sequence (not
CTRL-? or CTRL-H) you cannot use ":fixdel". Then use:
:if &term == "termname"
: set t_kD=^V<Delete>
:endif
Where "^V" is CTRL-V and "<Delete>" is the delete key
(don't type eight characters!). Replace "termname"
with your terminal name.

Vim doesn't indent when inserting carriage return from a function?

So I'm trying to write a function that makes inserting semi colons a bit more pleasant:
inoremap <leader>; <esc>:call InsertSemiColin()<cr>
Basically it checks to see if I'm standing at the end of the current line, if so I auto-format the code, insert the semi-colon at the end and break down to the next line (carriage return)
fun! InsertSemiColin()
if (!IsEOL()) | exec "normal! a;" | return | endif
exec "normal! \<esc>:OmniSharpCodeFormat\<cr>A;\<cr>"
endf:
fun! IsEOL()
" col number == length of current line?
return col('.') == strlen(getline(line('.'))) " or just getline('.')
endf
Expectation:
Result:
To try it out on your own, you can remove the code-formatting and just do:
exec "normal! a;\<cr>"
My indentation settings:
set smartindent
set tabstop=4
set shiftwidth=4
filetype plugin indent on
The weird thing is that if I don't insert the carriage return from a function, it works as expected!
inoremap <leader>; ;<cr>
Why is this happening? and how can I get the result I'm expecting?
Very frustrating, any help would be appreciated!
I would avoid leaving and re-entering insert mode for this via :help :map-expr:
inoremap <expr> <leader>; ';' . (IsEOL() ? '<esc>:OmniSharpCodeFormat<cr>A<cr>' : '')
For this to work, you need to change the comparison in the IsEOL() function:
fun! IsEOL()
" col number == length of current line?
return col('.') > strlen(getline(line('.'))) " or just getline('.')
endf
This also fixes the indent problem.

How to convert leading spaces to tabs?

Many people use spaces rather than tabs. I use both of them. Tabs at the beginning of line and spaces from the first non-whitespace character. No problem for starting new document and in case I have to modify one better adapt to using format. Still sometimes I need to fix the spaces issue anyway.
According to Search and replace I can just do :%s/spaces_for_tab/tab/g. It is simple and it will work for many cases. Anyway I want to refactor only spaces at the beginning of line.
This is more of a regex issue. To anchor at the beginning of the line, use the caret, e.g.
s/^ /\t/
Or do it using vim's builtin functionality:
:set tabstop=4 "four spaces will make up for one tab
:set noexpandtab "tell vim to keep tabs instead of inserting spaces
:retab "let vim handle your case
By the way, I too prefer tabs for indentation and spaces for alignment. Unfortunately, vim doesn't handle this well (and I don't know what other editors do), so I mostly use :set expandtab (maybe see :set softtabstop).
I've written a simple func for it. Anyway it will work only for 4-space tab.
fu! Fixspaces()
while search('^\t* \{4}') != 0
execute ':%s/^\t*\zs \{4}/\t/g'
endwhile
endfu
You can suggest better solution, if exists, and I will use it with pleasure.
The issue is that this func replaces spaces in strings as well.
David's response is very elegant but it doesn't address leading whitespace that has a mixture of tabs and spaces. For example to convert a line like:
<SPACE><SPACE><TAB>something...
you have to know the position of the tab to determine the number of spaces needed to replace the <TAB> and reach the next tabstop. My solution below, although not as compact as David's, addresses this. It also allows me to select which way to use leading whitespace without depending upon &expandtab. I would appreciate any suggestions to improve my code...
function! TabsToSpaces(...)
let ts = &tabstop
let pos = getpos('.')
while search('^ *\zs\t', "w") != 0
let l:curpos = getcharpos('.')
" The number of spaces needed depends upon the position of the <TAB>
let numsp = 1 + ts - ( curpos[2] % ts )
if numsp == 9
let numsp = 1
endif
silent execute ':s/^ *\zs\t/'.repeat(' ', numsp).'/'
endwhile
if a:0 == 0
echo 'Changed leading tabs to spaces'
endif
call setpos('.', pos)
endfunction
function! SpacesToTabs(...)
let ts = &tabstop
let pos = getpos('.')
" First normalize all tabs to spaces
call TabsToSpaces("quiet")
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
if a:0 == 0
echo 'Changed leading spaces to tabs'
endif
call setpos('.', pos)
endfunction
" Some keystrokes to implement the spaces/tabs functions
nmap <Leader>st :execute SpacesToTabs()<CR>
nmap <Leader>ts :execute TabsToSpaces()<CR>
I took Martin's answer and improved on it a bit if anyone's interested:
function Fixspaces()
let ts = &tabstop
let pos = getpos('.')
if &expandtab
while search('^ *\t') != 0
silent execute ':%s/^ *\zs\t/'.repeat(' ', ts).'/g'
endwhile
echo 'Changed tabs to spaces'
else
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
echo 'Changed spaces to tabs'
endif
call setpos('.', pos)
endfunction
This function does the appropriate thing depending on the values of the expandtab and tabstop settings and also remembers where the cursor is.

How do I change the join character in Vim?

In Vim, one can join two lines by typing capital J.
However, these are usually joined by a space.
I seem to remember there was a way to change the character used for the joining by setting some variable, but I can't seem to find it again.
I'd appreciate it if anyone could remind me, or confirm that it can't be done.
When I want to join just a few lines I use a 3 keys combo (normal mode):
Jr,
being , the joining character.
In case I want to join more lines or even join lines in groups, I use the previous combo with a macro.
For example, to transform 3 lines in a 3 columns CSV table, I record this macro (assigned to letter j of course):
qjJr,Jr,jq
So, using #j joins 3 lines using , and goes to the next line.
10#j converts 10 lines.
There isn't a setting that allows you to do this directly, see:
:help J
in particular, the text below the list of commands.
A couple of ways you could do this:
:nnoremap J gJi.<ESC>
" or
let joinchar = ';'
nnoremap J :s/\n/\=joinchar/<CR>
The latter option allows you to change it on the fly by changing the joinchar option.
Try something like this in your .vimrc:
nnoremap Y Jxi*<Esc>
It'll remap Y to join the lines with a *.
From http://vim.wikia.com/wiki/Remap_join_to_merge_comment_lines
put this in your .vimrc:
function! JoinWithLeader(count, leaderText)
let l:linecount = a:count
" default number of lines to join is 2
if l:linecount < 2
let l:linecount = 2
endif
echo l:linecount . " lines joined"
" clear errmsg so we can determine if the search fails
let v:errmsg = ''
" save off the search register to restore it later because we will clobber
" it with a substitute command
let l:savsearch = #/
while l:linecount > 1
" do a J for each line (no mappings)
normal! J
" remove the comment leader from the current cursor position
silent! execute 'substitute/\%#\s*\%('.a:leaderText.'\)\s*/ /'
" check v:errmsg for status of the substitute command
if v:errmsg=~'Pattern not found'
" just means the line wasn't a comment - do nothing
elseif v:errmsg!=''
echo "Problem with leader pattern for JoinWithLeader()!"
else
" a successful substitute will move the cursor to line beginning,
" so move it back
normal! ``
endif
let l:linecount = l:linecount - 1
endwhile
" restore the #/ register
let #/ = l:savsearch
endfunction
nnoremap <space> :<C-U>call JoinWithLeader(v:count, '"')<CR>
This also allows you to remap J to something else.
It will quicker if you replace the end of line with a comma (or join character)
:%s/$/,
and then joining multiple lines either by providing a range, or by selecting lines in visual mode and using the join command
10J
It's mapping. You can read the tutorial in vim wikia :
Mapping keys in vim
Try the command below in command mode, and try to press . This should work :)
:map <space> J

How can I trim blank lines at the end of file in Vim?

Sometimes I accidentally leave blank lines at the end of the file I am editing.
How can I trim them on saving in Vim?
Update
Thanks guys, all solutions seem to work.
Unfortunately, they all reset current cursor position, so I wrote the following function.
function TrimEndLines()
let save_cursor = getpos(".")
silent! %s#\($\n\s*\)\+\%$##
call setpos('.', save_cursor)
endfunction
autocmd BufWritePre *.py call TrimEndLines()
This substitute command should do it:
:%s#\($\n\s*\)\+\%$##
Note that this removes all trailing lines that contain only whitespace. To remove only truly "empty" lines, remove the \s* from the above command.
EDIT
Explanation:
\( ..... Start a match group
$\n ... Match a new line (end-of-line character followed by a carriage return).
\s* ... Allow any amount of whitespace on this new line
\) ..... End the match group
\+ ..... Allow any number of occurrences of this group (one or more).
\%$ ... Match the end of the file
Thus the regex matches any number of adjacent lines containing only whitespace, terminated only by the end of the file. The substitute command then replaces the match with a null string.
1. An elegant solution can be based on the :vglobal command
(or, which is the same thing, on the :global with ! modifier):
:v/\_s*\S/d
This command executes :delete on every line that does not have
non-whitespace characters in it, as well as after it in the remaining
text to the end of buffer (see :help /\s, :help /\S, and :help /\_
to understand the pattern). Hence, the command removes the tailing
blank lines.
To delete the empty lines in a strict sense—as opposed to blank ones
containing only whitespace—change the pattern in that :vglobal
command as follows.
:v/\n*./d
2. On huge sparse files containing large blocks of consecutive
whitespace characters (starting from about hundreds of kilobytes of
whitespace) the above commands might have unacceptable performance.
If that is the case, the same elegant idea can be used to transform
that :vglobal commands into much faster (but perhaps less
elegantly-looking) :delete commands with pattern-defined ranges.
For blank lines:
:0;/^\%(\_s*\S\)\#!/,$d
For empty lines:
:0;/^\%(\n*.\)\#!/,$d
The essence of both commands is the same; namely, removing the lines
belonging to the specified ranges, which are defined according to the
following three steps:
Move the cursor to the first line of the buffer before interpreting
the rest of the range (0;—see :help :;). The difference between
0 and 1 line numbers is that the former allows a match at the
first line, when there is a search pattern used to define the ending
line of the range.
Search for a line where the pattern describing a non-tailing blank
line (\_s*\S or \n*.) does not match (negation is due to the
\#! atom—see :help /\#!). Set the starting line of the range to
that line.
Set the ending line of the range to the last line of the buffer
(,$—see :help :$).
3. To run any of the above commands on saving, trigger it using
an autocommand to be fired on the BufWrite event (or its synonym,
BufWritePre).
You can put this into your vimrc
au BufWritePre *.txt $put _ | $;?\(^\s*$\)\#!?+1,$d
(replace *.txt with whatever globbing pattern you want)
Detail:
BufWritePre is the event before writing a buffer to a file.
$put _ appends a blank line at file end (from the always-empty register)
| chains Ex commands
$;?\(^\s*$\)\#!? goes to end of file ($) then (;) looks up backwards (?…?) for the first line which is not entirely blank (\(^\s*$\)\#!), also see :help /\#! for negative assertions in vim searches.
×××+1,$ forms a range from line ×××+1 till the last line
d deletes the line range.
Inspired by solution from #Prince Goulash, add the following to your ~/.vimrc to remove trailing blank lines for every save for Ruby and Python files:
autocmd FileType ruby,python autocmd BufWritePre <buffer> :%s/\($\n\s*\)\+\%$//e
I found the previous answers using substitute caused trouble when operating on very large files and polluted my registers. Here's a function I came up with which performs better for me, and avoids polluting registers:
" Strip trailing empty newlines
function TrimTrailingLines()
let lastLine = line('$')
let lastNonblankLine = prevnonblank(lastLine)
if lastLine > 0 && lastNonblankLine != lastLine
silent! execute lastNonblankLine + 1 . ',$delete _'
endif
endfunction
autocmd BufWritePre <buffer> call TrimTrailingLines()
I have a separated function called Preserve which I can call from other functions,
It makes easy to use Preserve to do other stuff:
" remove consecutive blank lines
" see Preserve function definition
" another way to remove blank lines :g/^$/,/./-j
" Reference: https://stackoverflow.com/a/7496112/2571881
if !exists('*DelBlankLines')
fun! DelBlankLines() range
if !&binary && &filetype != 'diff'
call Preserve(':%s/\s\+$//e')
call Preserve(':%s/^\n\{2,}/\r/ge')
call Preserve(':%s/\v($\n\s*)+%$/\r/e')
endif
endfun
endif
In my case, I keep at least one blank line, but not more than one, at the end of the file.
" Utility function that save last search and cursor position
" http://technotales.wordpress.com/2010/03/31/preserve-a-vim-function-that-keeps-your-state/
" video from vimcasts.org: http://vimcasts.org/episodes/tidying-whitespace
" using 'execute' command doesn't overwrite the last search pattern, so I
" don't need to store and restore it.
" preserve function
if !exists('*Preserve')
function! Preserve(command)
try
let l:win_view = winsaveview()
"silent! keepjumps keeppatterns execute a:command
silent! execute 'keeppatterns keepjumps ' . a:command
finally
call winrestview(l:win_view)
endtry
endfunction
endif
Here's why I have a separated Preserve function:
command! -nargs=0 Reindent :call Preserve('exec "normal! gg=G"')
" join lines keeping cursor position
nnoremap J :call Preserve(':join')<CR>
nnoremap <Leader>J :call Preserve(':join!')<CR>
" Reloads vimrc after saving but keep cursor position
if !exists('*ReloadVimrcFunction')
function! ReloadVimrcFunction()
call Preserve(':source $MYVIMRC')
" hi Normal guibg=NONE ctermbg=NONE
windo redraw
echom "Reloaded init.vim"
endfunction
endif
noremap <silent> <Leader>v :drop $MYVIMRC<cr>
command! -nargs=0 ReloadVimrc :call ReloadVimrcFunction()
" Strip trailing whitespaces
command! Cls :call Preserve(':%s/\v\s+$//e')
And if by any chance you want to create an autocommand you must create a group
to avoid overloading autocommands:
augroup removetrailingspaces
au!
au! BufwritePre *.md,*.py,*.sh,*.zsh,*.txt :call Preserve(':%s/\v\s+$//e')
augroup END
This one is to change file headers (if you have any suggestions) feel free to interact:
" trying avoid searching history in this function
if !exists('*ChangeHeader')
fun! ChangeHeader() abort
if line('$')>=7
call Preserve(':1,7s/\v(Last (Change|Modified)|date):\s+\zs.*/\=strftime("%b %d, %Y - %H:%M")/ei')
endif
endfun
endif
" dos2unix ^M
if !exists('*Dos2unixFunction')
fun! Dos2unixFunction() abort
"call Preserve('%s/ $//ge')
call Preserve(":%s/\x0D$//e")
set ff=unix
set bomb
set encoding=utf-8
set fileencoding=utf-8
endfun
endif
com! Dos2Unix :call Dos2unixFunction()

Resources