Preserve cursor position when using == - vim

I am trying to make Vim indent lines like Emacs (that is, "make the current line the correct indent" instead of "insert tab character"). Vim can do this with = (or ==) for one line). I have imap <Tab> <Esc>==i in my .vimrc but this makes the cursor move to the first non-space character on the line. I would like the cursor position to be preserved, so I can just hit tab and go back to typing without having to adjust the cursor again. Is this possible?
Example
What I have now (| represents the cursor):
function f() {
doso|mething();
}
Tab
function f() {
|dosomething();
}
What I would like:
function f() {
doso|mething();
}
Tab
function f() {
doso|mething();
}
Also
function f() {
| dosomething();
}
Tab
function f() {
|dosomething();
}

I don't believe there's an "easy" way to do this (that is, with strictly built-in functionality) but a simple function does it just fine. In your .vimrc file:
function! DoIndent()
" Check if we are at the *very* end of the line
if col(".") == col("$") - 1
let l:needsAppend = 1
else
let l:needsAppend = 0
endif
" Move to location where insert mode was last exited
normal `^
" Save the distance from the first nonblank column
let l:colsFromBlank = col(".")
normal ^
let l:colsFromBlank = l:colsFromBlank - col(".")
" If that distance is less than 0 (cursor is left of nonblank col) make it 0
if l:colsFromBlank < 0
let l:colsFromBlank = 0
endif
" Align line
normal ==
" Move proper number of times to the right if needed
if l:colsFromBlank > 0
execute "normal " . l:colsFromBlank . "l"
endif
" Start either insert or line append
if l:needsAppend == 0
startinsert
else
startinsert!
endif
endfunction
" Map <Tab> to call this function
inoremap <Tab> <ESC>:call DoIndent()<CR>

There's always <C-T> and <C-D> (that is CtrlT and CtrlD) in insert mode to indent and outdent on the fly.
These don't change the cursor position – and they're built-in functionality.

Try using a mark of last insert mode, and use the append command to set the cursor back just where it was before, like:
:imap <Tab> <Esc>==`^a

Related

How can I block moving up/down with multiple j,k keystrokes?

I learned moving with hjkl by blocking arrow keys.
I'd like to do something similiar for moving up/down with jjjjjj/kkkkk.
For example whenever I press j 4 times in a row with small delays it would jump back to original position, so I'd have to think how to move smarter to the place I want.
I'm not a fan of technical solutions to this problem (I'd rather critically reflect on my own typing occasionally), but this can be done by storing subsequent keypresses in an array, and complaining if the size becomes too large:
let g:pos = []
let g:keys = []
function! RecordKey( key )
if v:count || get(g:keys, 0, '') != a:key
" Used [count], or different key; start over.
let g:keys = [a:key]
let g:pos = getpos('.')
echo
return 1
endif
call add(g:keys, a:key)
if len(g:keys) > 4
" Too many identical movements (without count).
let g:keys = [a:key]
call setpos('.', g:pos)
echohl ErrorMsg
echomsg 'Try again'
echohl None
return 0
endif
echo
return 1
endfunction
" Reset counter after a delay in movement.
autocmd CursorHold * let g:keys = []
nnoremap <silent> j :<C-u>if RecordKey('j')<Bar>execute 'normal!' (v:count ? v:count : '') . 'j'<Bar>endif<CR>
nnoremap <silent> k :<C-u>if RecordKey('k')<Bar>execute 'normal!' (v:count ? v:count : '') . 'k'<Bar>endif<CR>
" Add more movements as you wish.
(Trying this out, I'm already annoyed by this :-)
I would suggest the following mappings:
nnoremap jjjj j
nnoremap kkkk k
This will make fast movement up and down very cumbersome. Unfortunately it will also prohibit a normal 'j' from executing very fast, as Vim will wait to see whether you want to add anything else after the first keypress to complete the binding. This can be circumvented by pressing another key afterwards (e.g. switching to insert mode with i/I/a/A or the like).

In vim, how to split a word and flip the halves? FooBar => BarFoo

I sometimes write a multi-word identifier in one order, then decide the other order makes more sense. Sometimes there is a separator character, sometimes there is case boundary, and sometimes the separation is positional. For example:
$foobar becomes $barfoo
$FooBar becomes $BarFoo
$foo_bar becomes $bar_foo
How would I accomplish this in vim? I want to put my cursor on the word, hit a key combo that cuts the first half, then appends it to the end of the current word. Something like cw, but also yanking into the cut buffer and then appending to the current word (eg ea).
Nothing general and obvious comes to mind. This is more a novelty question than one of daily practical use, but preference is given to shortest answer with fewest plugins. (Hmm, like code golf for vim.)
You can use this function, it swaps any word of the form FooBar, foo_bar, or fooBar:
function! SwapWord()
" Swap the word under the cursor, ex:
" 'foo_bar' --> 'bar_foo',
" 'FooBar' --> 'BarFoo',
" 'fooBar' --> 'barFoo' (keeps case style)
let save_cursor = getcurpos()
let word = expand("<cword>")
let match_ = match(word, '_')
if match_ != -1
let repl = strpart(word, match_ + 1) . '_' . strpart(word, 0, match_)
else
let matchU = match(word, '\u', 1)
if matchU != -1
let was_lower = (match(word, '^\l') != -1)
if was_lower
let word = substitute(word, '^.', '\U\0', '')
endif
let repl = strpart(word, matchU) . strpart(word, 0, matchU)
if was_lower
let repl = substitute(repl, '^.', '\L\0', '')
endif
else
return
endif
endif
silent exe "normal ciw\<c-r>=repl\<cr>"
call setpos('.', save_cursor)
endf
Mapping example:
noremap <silent> gs :call SwapWord()<cr>
Are you talking about a single instance, globally across a file, or generically?
I would tend to just do a global search and replace, e.g.:
:1,$:s/$foobar/$barfoo/g
(for all lines, change $foobar to $barfoo, every instance on each line)
EDIT (single occurrence with cursor on the 'f'):
3xep
3xep (had some ~ in there before the re-edit of the question)
4xea_[ESC]px
Best I got for now. :)
nnoremap <Leader>s dwbP
Using Leader, s should now work.
dw : cut until the end of the word from cursor position
b : move cursor at the beginning of the word
P : paste the previously cut part at the front
It won't work for you last example though, you have to add another mapping to deal with _ .
(If you don't know what Leader is, see :help mapleader)

Jump to the end of a long list of repeated pattern

I have a big file with a lot of lines that share the same pattern, something like this:
dbn.py:206 ... (some other text) <-- I am here
dbn.py:206 ... (some other text)
...
(something I don't know) <-- I want to jump here
Is there a quick way in Vim to jump to the place where the succession of dbp.py:206 ends?
/^\(dbn.py\)\#!
Matches first line which does not start with the text inside the escaped parentheses.
If you want quick access to this you could add a vmap which yanks the visually selected text and inserts it in the right spot (but first escaping it with escape(var, '/').
Try this vmap: vmap <leader>n "hy<Esc>/^\(<C-R>=escape(#h,'/')<CR>\)\#!<CR>
Press n when visually selecting the text you wish to skip and you should be placed on the next first line which does not begin with the selection.
I just write a function to select identical lines:
nnoremap vii :call SelectIdenticalLines()<CR>
fun! SelectIdenticalLines()
let s = getline('.')
let n = line('.')
let i = n
let j = n
while getline(i)==s && i>0
let i-=1
endwhile
while getline(j)==s && j<=line('$')
let j+=1
endwhile
call cursor(i+1, 0)
norm V
call cursor(j-1, 0)
endfun
type vii to select identical lines (feel free to change the key-binding)
type zf to fold them.
type za to toggle folding
It's handy when you want to squeeze several empty line.
It acts like C-x C-o in emacs.
One option is to go to the bottom of the file and search backwards for the last line you want, then go down one:
G ?^dbn\.py:206?+1

How to ignore space after comments when calculating indent level in Vim

Consider writing a JavaDoc-style comment which includes an indented list (when expandtab is set and softtabstop=2):
/**
* First line:
* - Indented text
*/
Currently, after typing First line: and hitting return, Vim will correctly insert *<space>. However, when I hit tab to indent the second line, only one space will be inserted instead of two.
Is it possible to fix this, so the space after * will be ignored during indent calculations?
I am still a beginner at VimScript, but I cooked this up for you. Give it a try and let me know what you think.
function AdjustSoftTabStop()
" Only act if we are in a /* */ comment region
if match(getline('.'), '\s*\*') == 0
" Compensate for switching out of insert mode sometimes removing lone
" final space
if match(getline('.'), '\*$') != -1
" Put back in the space that was removed
substitute/\*$/\* /
" Adjust position of the cursor accordingly
normal l
endif
" Temporary new value for softtabstop; use the currect column like a
" base to extend off of the normal distance
let &softtabstop+=col('.')
endif
endfunction
function ResetSoftTabStop()
" Note that you will want to change this if you do not like your tabstop
" and softtabstop equal.
let &softtabstop=&tabstop
endfunction
" Create mapping to call the function when <TAB> is pressed. Note that because
" this is mapped with inoremap (mapping in insert mode disallowing remapping of
" character on the RHS), it does not result in infinite recursion.
inoremap <TAB> <ESC>:call AdjustSoftTabStop()<CR>a<TAB><ESC>:call ResetSoftTabStop()<CR>a

vim - set auto indent to fill the leading space with space or tabstop

It seems if we enable 'ai', vim will fill the the leading space with tabstop.
I can make it fill with just space with 'et'. I don't like a C file mixed with space and tabstop.
My vimrc:
set ts=4 et
set ai
set hlsearch
syntax on
filetype plugin indent on
autocmd FileType make setlocal noexpandtab
However, in some condition I do need to input tabstop when I hit the 'TAB' on keyboard, for example, in makefile and some others.
The 'autocmd FileType' command is not good: I can't add every file type in vimrc.
What I want is simple:
autoindent to fill leading area with
space;
when hit 'TAB' on keyboard, tabstop
input, not space (so no 'et')
How to do it?
inoremap <expr> <tab> ((getline('.')[:col('.')-2]=~'\S')?("\<C-v>\t"):(repeat(' ', &ts-((virtcol('.')-1)%&ts))))
It does the same as #Lynch answer if I read it correctly.
You can also use <C-v><Tab>: this will insert <Tab> without invoking any mappings and ignores expandtab unless you remapped <C-v> or <C-v><Tab> for some reason.
If you want to just insert tab do
inoremap <Tab> <C-v><Tab>
It will ignore expandtab setting.
I did it using a function. I tested it, but maybe in some particular case you will have to fix some bugs. Try adding this to your vimrc:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
if pos != 1 && substitute(before, "[ \t]", "", "g") != ""
let insert = "\t"
else
let insert = " "
endif
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
Basicaly it does remap your key in visual mode to the function Inserttab(). Also note that if you change ts for something other than 4 it will still output 4 spaces instead of two because the value is hard coded.
Also im not very familiar with vim scripts, but I think all the variables used will be global which is a bad thing.
I forgot to mention that to "see" white spaces you can use set list. You disable this with set nolist. Also in normal mode you can use ga to see information about the character your cursor is on.
Edit
I realise that you may want to insert tab at the beginin of the line. My script insert space at the begining and tab anywhere else.
If you really want a tab every time you hit tab key you could simply use this:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
let insert = "\t"
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
But I dont see the point, with this version you will never be able to indent manually from insert mode.
One way to do it is
:set sw=4 (or whatever you want)
:set ts=46 (or some large number)
Then autoindent will not insert tabs unless you reach 46 spaces, in which case you can put in a higher number.
Only drag about this is if someone else is using tabs, then you have to reset ts to agree with the file you are editing. On the other hand, it will make the tabs immediately obvious, which can be desirable as well.

Resources