Vim Tips Wiki
(Fix double-brace ending tip template + clean)
(Change to TipImported template + clean + integrate most comments to tip)
Line 1: Line 1:
 
{{review}}
 
{{review}}
  +
{{TipImported
{{Tip
 
 
|id=1061
 
|id=1061
  +
|previous=1059
|title=SmartHome & SmartEnd over wrapped lines
 
  +
|next=1063
 
|created=November 27, 2005
 
|created=November 27, 2005
 
|complexity=intermediate
 
|complexity=intermediate
Line 8: Line 9:
 
|version=6.0
 
|version=6.0
 
|rating=11/4
 
|rating=11/4
|text=
 
 
}}
 
}}
 
 
The Smart Home function allows the cursor to toggle between the start position and the first non-whitespace character position of the line. This is useful for skipping past indentation, whether it be tabs or spaces. Likewise, the Smart End function toggles past trailing whitespaces at the end of a line.
 
The Smart Home function allows the cursor to toggle between the start position and the first non-whitespace character position of the line. This is useful for skipping past indentation, whether it be tabs or spaces. Likewise, the Smart End function toggles past trailing whitespaces at the end of a line.
   
Line 17: Line 16:
 
The reason I am bringing this up again is because previous implementations of the Smart Home functionality have not worked well over lines that are wrapped. The implementation below works pleasingly well even when lines are wrapped, stepping through the wrapped lines by <Home> or <End> one by one. It also solves the "<Home> for one character on a line" insert mode problem, and is Replace mode friendly.
 
The reason I am bringing this up again is because previous implementations of the Smart Home functionality have not worked well over lines that are wrapped. The implementation below works pleasingly well even when lines are wrapped, stepping through the wrapped lines by <Home> or <End> one by one. It also solves the "<Home> for one character on a line" insert mode problem, and is Replace mode friendly.
   
Of course, cursor positioning can be achieved via vanilla Vim way: using 0, ^, g0, g$, g_, etc. but it's easier and faster when two keys take care of all of the functions. Especially over wrapped lines.
+
Of course, cursor positioning can be achieved via vanilla Vim way: using <tt>0</tt>, <tt>^</tt>, <tt>g0</tt>, <tt>g$</tt>, <tt>g_</tt>, etc, but it's easier and faster when two keys take care of all of the functions. Especially over wrapped lines.
   
 
<pre>
 
<pre>
 
"place in vimrc
 
"place in vimrc
nmap &lt;silent&gt;&lt;Home&gt; :call SmartHome()&lt;CR&gt;
+
nmap &lt;silent&gt;&lt;Home&gt; :call SmartHome("n")&lt;CR&gt;
nmap &lt;silent&gt;&lt;End&gt; :call SmartEnd()&lt;CR&gt;
+
nmap &lt;silent&gt;&lt;End&gt; :call SmartEnd("n")&lt;CR&gt;
imap &lt;silent&gt;&lt;Home&gt; &lt;C-r&gt;=SmartHome()&lt;CR&gt;
 
imap &lt;silent&gt;&lt;End&gt; &lt;C-r&gt;=SmartEnd()&lt;CR&gt;
 
vmap &lt;silent&gt;&lt;Home&gt; &lt;Esc&gt;:call SmartHome()&lt;CR&gt;msgv`s
 
vmap &lt;silent&gt;&lt;End&gt; &lt;Esc&gt;:call SmartEnd()&lt;CR&gt;msgv`s
 
 
"smart home function
 
function SmartHome()
 
let curcol = col(".")
 
"gravitate towards beginning for wrapped lines
 
if curcol &gt; indent(".") + 2
 
call cursor(0, col(".") - 1)
 
endif
 
if curcol == 1 || curcol &gt; indent(".") + 1
 
if &amp;wrap
 
normal g^
 
else
 
normal ^
 
endif
 
else
 
if &amp;wrap
 
normal g0
 
else
 
normal 0
 
endif
 
endif
 
return ""
 
endfunction
 
 
"smart end function
 
function SmartEnd()
 
let editmode = (mode() == "i" || mode() == "R")
 
let curcol = col(".")
 
let lastcol = editmode ? col("$") : col("$") - 1
 
"
 
"gravitate towards ending for wrapped lines
 
if curcol &lt; lastcol - 1
 
call cursor(0, col(".") + 1)
 
endif
 
if curcol &lt; lastcol
 
if &amp;wrap
 
normal g$
 
else
 
normal $
 
endif
 
else
 
normal g_
 
endif
 
"
 
"correct edit mode cursor position, put after current character
 
if editmode
 
call cursor(0, col(".") + 1)
 
endif
 
return ""
 
endfunction
 
</pre>
 
 
== Comments ==
 
This is the most useful tip for a runaway from MS notepad/Visual Studio. But sometimes it confused me in the insert-select mode. When I selected something with shift and arrow keys then usually pressed &lt;home&gt; or &lt;end&gt; to move quickier, I got the "msgv`s" inserted in my text.
 
So I spent a little time to fix the wrong action. My decision:
 
 
<pre>
 
" substitute proposed visual mappings for following:
 
vmap &lt;silent&gt;&lt;Home&gt; &lt;Esc&gt;:call RestoreSelection(SmartHome())&lt;CR&gt;
 
vmap &lt;silent&gt;&lt;End&gt; &lt;Esc&gt;:call RestoreSelection(SmartEnd())&lt;CR&gt;
 
"
 
" Check if it's needed to restore visual selection
 
function RestoreSelection(foo)
 
if (mode() != 's' &amp;&amp; mode() != 'S')
 
normal msgv`s
 
endif
 
endfunction
 
</pre>
 
 
The composition of the two functions is used only to execute them consequently in the only normal session. I don't know if it's the best way, but it works well.
 
 
----
 
You know what? :) I found that bug soon after I posted this tip and fixed it, but I forgot to post the fix. The cause of the bug is due to Vim's confusion and inability to properly keep track of its modes.
 
 
The Insert-Select mode SmartHome/End is one instant where Vim is using almost all of its modes at one go! i.e. Insert, Select, Normal (within the function called as an expression register)
 
 
The fix is simple. Prefix &lt;Esc&gt; to "msgv`s" to make sure Vim is in Normal mode by that time. You would, however, lose your Insert-Select mode, after a SmartHome/End, to the Select mode.
 
 
vmap &lt;silent&gt;&lt;Home&gt; &lt;Esc&gt;:call SmartHome()&lt;CR&gt;&lt;Esc&gt;msgv`s
 
vmap &lt;silent&gt;&lt;End&gt; &lt;Esc&gt;:call SmartEnd()&lt;CR&gt;&lt;Esc&gt;msgv`s
 
 
----
 
I was a little bugged that the Insert-Visual/Select mode would be lost ;)
 
Apparently, mode() does not work well here, and I decided to scrap it.
 
You're right, Anatoli - "normal msgv`s" is the way to go.
 
 
'''Please disregard the tip above for the one below'''
 
 
<pre>
 
"fixes the bug in Insert-Visual/Select mode
 
nmap &lt;silent&gt;&lt;Home&gt; :cal SmartHome("n")&lt;CR&gt;
 
nmap &lt;silent&gt;&lt;End&gt; :cal SmartEnd("n")&lt;CR&gt;
 
 
imap &lt;silent&gt;&lt;Home&gt; &lt;C-r&gt;=SmartHome("i")&lt;CR&gt;
 
imap &lt;silent&gt;&lt;Home&gt; &lt;C-r&gt;=SmartHome("i")&lt;CR&gt;
 
imap &lt;silent&gt;&lt;End&gt; &lt;C-r&gt;=SmartEnd("i")&lt;CR&gt;
 
imap &lt;silent&gt;&lt;End&gt; &lt;C-r&gt;=SmartEnd("i")&lt;CR&gt;
vmap &lt;silent&gt;&lt;Home&gt; &lt;Esc&gt;:cal SmartHome("v")&lt;CR&gt;
+
vmap &lt;silent&gt;&lt;Home&gt; &lt;Esc&gt;:call SmartHome("v")&lt;CR&gt;
vmap &lt;silent&gt;&lt;End&gt; &lt;Esc&gt;:cal SmartEnd("v")&lt;CR&gt;
+
vmap &lt;silent&gt;&lt;End&gt; &lt;Esc&gt;:call SmartEnd("v")&lt;CR&gt;
  +
"
 
"smart home function
 
 
function SmartHome(mode)
 
function SmartHome(mode)
 
let curcol = col(".")
 
let curcol = col(".")
Line 144: Line 46:
 
endif
 
endif
 
endif
 
endif
"
 
 
if a:mode == "v"
 
if a:mode == "v"
 
normal msgv`s
 
normal msgv`s
 
endif
 
endif
"
 
 
return ""
 
return ""
 
endfunction
 
endfunction
   
"smart end function
 
 
function SmartEnd(mode)
 
function SmartEnd(mode)
 
let curcol = col(".")
 
let curcol = col(".")
 
let lastcol = a:mode == "i" ? col("$") : col("$") - 1
 
let lastcol = a:mode == "i" ? col("$") : col("$") - 1
"
 
 
"gravitate towards ending for wrapped lines
 
"gravitate towards ending for wrapped lines
 
if curcol &lt; lastcol - 1
 
if curcol &lt; lastcol - 1
 
call cursor(0, curcol + 1)
 
call cursor(0, curcol + 1)
 
endif
 
endif
"
 
 
if curcol &lt; lastcol
 
if curcol &lt; lastcol
 
if &amp;wrap
 
if &amp;wrap
Line 171: Line 68:
 
normal g_
 
normal g_
 
endif
 
endif
"
 
 
"correct edit mode cursor position, put after current character
 
"correct edit mode cursor position, put after current character
 
if a:mode == "i"
 
if a:mode == "i"
 
call cursor(0, col(".") + 1)
 
call cursor(0, col(".") + 1)
 
endif
 
endif
"
 
 
if a:mode == "v"
 
if a:mode == "v"
 
normal msgv`s
 
normal msgv`s
 
endif
 
endif
"
 
 
return ""
 
return ""
 
endfunction
 
endfunction
 
</pre>
 
</pre>
   
 
==Comments==
----
 
 
This tip and [[VimTip315]] and gave me some great ideas for my own &lt;Home&gt; and &lt;End&gt; keys.
 
This tip and [[VimTip315]] and gave me some great ideas for my own &lt;Home&gt; and &lt;End&gt; keys.
   
I also studied some great ideas from URL mentioned in [[VimTip315]], http://hermitte.free.fr/vim/ressources/vimfiles/plugin/homeLikeVC++_vim.html"
+
I also studied some great ideas from a URL mentioned in [[VimTip315]]: http://hermitte.free.fr/vim/ressources/vimfiles/plugin/homeLikeVC++_vim.html
   
Try putting these in your ~/.vimrc file.
+
Try putting these in your vimrc.
   
 
<pre>
 
<pre>
Line 218: Line 112:
 
endif
 
endif
 
endfun
 
endfun
  +
"
 
 
" map &lt;End&gt; to move to end of line
 
" map &lt;End&gt; to move to end of line
 
" if at end of line, move to end of window
 
" if at end of line, move to end of window

Revision as of 08:00, 20 December 2007

Tip 1061 Printable Monobook Previous Next

created November 27, 2005 · complexity intermediate · author Gerald Lai · version 6.0


The Smart Home function allows the cursor to toggle between the start position and the first non-whitespace character position of the line. This is useful for skipping past indentation, whether it be tabs or spaces. Likewise, the Smart End function toggles past trailing whitespaces at the end of a line.

This topic has been discussed before, see Smart home.

The reason I am bringing this up again is because previous implementations of the Smart Home functionality have not worked well over lines that are wrapped. The implementation below works pleasingly well even when lines are wrapped, stepping through the wrapped lines by <Home> or <End> one by one. It also solves the "<Home> for one character on a line" insert mode problem, and is Replace mode friendly.

Of course, cursor positioning can be achieved via vanilla Vim way: using 0, ^, g0, g$, g_, etc, but it's easier and faster when two keys take care of all of the functions. Especially over wrapped lines.

"place in vimrc
nmap <silent><Home> :call SmartHome("n")<CR>
nmap <silent><End> :call SmartEnd("n")<CR>
imap <silent><Home> <C-r>=SmartHome("i")<CR>
imap <silent><End> <C-r>=SmartEnd("i")<CR>
vmap <silent><Home> <Esc>:call SmartHome("v")<CR>
vmap <silent><End> <Esc>:call SmartEnd("v")<CR>

function SmartHome(mode)
  let curcol = col(".")
  "gravitate towards beginning for wrapped lines
  if curcol > indent(".") + 2
    call cursor(0, curcol - 1)
  endif
  if curcol == 1 || curcol > indent(".") + 1
    if &wrap
      normal g^
    else
      normal ^
    endif
  else
    if &wrap
      normal g0
    else
      normal 0
    endif
  endif
  if a:mode == "v"
    normal msgv`s
  endif
  return ""
endfunction

function SmartEnd(mode)
  let curcol = col(".")
  let lastcol = a:mode == "i" ? col("$") : col("$") - 1
  "gravitate towards ending for wrapped lines
  if curcol < lastcol - 1
    call cursor(0, curcol + 1)
  endif
  if curcol < lastcol
    if &wrap
      normal g$
    else
      normal $
    endif
  else
    normal g_
  endif
  "correct edit mode cursor position, put after current character
  if a:mode == "i"
    call cursor(0, col(".") + 1)
  endif
  if a:mode == "v"
    normal msgv`s
  endif
  return ""
endfunction

Comments

This tip and VimTip315 and gave me some great ideas for my own <Home> and <End> keys.

I also studied some great ideas from a URL mentioned in VimTip315: http://hermitte.free.fr/vim/ressources/vimfiles/plugin/homeLikeVC++_vim.html

Try putting these in your vimrc.

" map <Home> to move to first word in line
" if at first word, move to beginning of line
" if at beginning of line, move to beginning of window
" if at beginning of window, move to beginning of file
nnoremap <silent> <Home> :call SmartHome("n")<CR>
inoremap <silent> <Home> <C-O>:call SmartHome("i")<CR>
vnoremap <silent> <Home> <Esc>:call SmartHome("v")<CR>
function! SmartHome(mode)
  if col('.') == 1
    if line('.') == 1
      return
    elseif winline() == &scrolloff+1
      normal! m`gg
    else
      normal! m`H0
    endif
  elseif strpart(getline('.'), -1, col('.')) =~ '^\s\+$'
    normal! 0
  else
    normal! ^
  endif
  if a:mode == "v"
    normal! msgv`s
  endif
endfun

" map <End> to move to end of line
" if at end of line, move to end of window
" if at end of window, move to end of file
nnoremap <silent> <End> :call SmartEnd("n")<CR>
inoremap <silent> <End> <C-O>:call SmartEnd("i")<CR>
vnoremap <silent> <End> <Esc>:call SmartEnd("v")<CR>
function! SmartEnd(mode)
  if col('.') < col('$')-1
    normal! $
  elseif winline() < winheight(0) - &scrolloff
    normal! m`L$
  else
    normal! m`G$
  endif
  if a:mode == "v"
    normal! msgv`s
  endif
endfun