In VIM, you can search a specified string. Then you can press n or N to navigate next or previous match. When you press n or N, the cursor will be moved to the matched text. My question is, how to quickly copy the matched text under cursor?
Edit:
What I need is the current match under cursor, not all matches in the document.
You want to execute the following
y//e
Overview:
The basic idea is after you search or press n or N the cursor will be at the beginning of the matched text. Then you yank to the end of the last search.
Explanation
y will yank from the current position through the following motion
// searches using the last search pattern.
//e The e flag will position the cursor at the end of the matched text
As a word of warning this will change the current search pattern, because it adds the /e flag. Therefore following n and/or N will move the cursor to the end of the match.
This is very similar to the following post.
One can write a function extracting the match of the last search pattern
around the cursor, and create a mapping to call it.
nnoremap <silent> <leader>y :call setreg('"', MatchAround(#/), 'c')<cr>
function! MatchAround(pat)
let [sl, sc] = searchpos(a:pat, 'bcnW')
let [el, ec] = searchpos(a:pat, 'cenW')
let t = map(getline(sl ? sl : -1, el), 'v:val."\n"')
if len(t) > 0
let t[0] = t[0][sc-1:]
let ec -= len(t) == 1 ? sc-1 : 0
let t[-1] = t[-1][:matchend(t[-1], '.', ec-1)-1]
end
return join(t, '')
endfunction
The function above determines the starting and ending positions of the match
and carefully takes out the matching text, correctly handling multiline
patterns and multibyte characters.
Another option is to create text object mappings (see :help text-object) for
operating on the last search pattern match under the cursor.
vnoremap <silent> i/ :<c-u>call SelectMatch()<cr>
onoremap <silent> i/ :call SelectMatch()<cr>
function! SelectMatch()
if search(#/, 'bcW')
norm! v
call search(#/, 'ceW')
else
norm! gv
endif
endfunction
To copy the current match using these mappings, use yi/. As for other text
objects, it is also possible, for example, to visually select it using vi/,
or delete it using di/.
Press y and the text under the cursor will be copied to the unamed default register, then you can press p to paste.
I guess this should do it:
command! -register CopyExactMatchUnderCursor call s:CopyExactMatchUnderCursor('<reg>')
function! s:CopyExactMatchUnderCursor(reg)
let cpos = getpos('.')
let line = cpos[1]
let idx = cpos[2] - 1
let txt = getline(line)
let mend = matchend(txt, #/, idx)
if mend > idx
let sel = strpart(txt, idx, mend - idx)
let reg = empty(a:reg) ? '"' : a:reg
execute 'let #' . reg . ' = sel . "\n"'
end
endfunction
It grabs the cursor position first with getpos then searches for the end of the match (beginning with the column where the cursor is using matchend) and then returns the substring in the register provided - by default " is used.
All the necessary methods were basically in #eckes answer.
Related
Hi I am currently trying to reformat python 2 code that was written using camelCase style variables and I need to convert them to snake_case.
I've written two small vimscript functions to aid in doing so. I would like to this one step further. I would like to on a single keystroke to search for the word under my cursor, and invoke my function FindAndReplace which will take the current search term and do the processing as opposed to me having to manually type in the search term using input.
function! SplitDelim(expr, pat)
let result = []
let expr = a:expr
while 1
let [w, s, e] = matchstrpos(expr, a:pat)
if s == -1
break
endif
call add(result, s ? expr[:s-1] : '')
call add(result, join(['', tolower(w)], '_'))
let expr = expr[e:]
endwhile
call add(result, expr)
return join(result, '')
endfunction
function! FindAndReplace()
" get current cursor position to keep screen constant
let cur_cursor_pos = getpos('.')
call inputsave()
let g:search_term = input("Enter search term: ")
call inputrestore()
execute '%s' . '/' . g:search_term . '/' . SplitDelim(g:search_term, '[A-Z]') . '/'
" set cursor back to where it was at start of invocation from execing s/
call setpos('.', cur_cursor_pos)
endfunction
Examples
The contents of some file
fooBarBaz
invoking the function
call FindAndReplace()
pass search term through input
fooBarBaz
resultant file now reads
foo_bar_baz.
What I want
Open some file whose content is
fooBarBaz (place cursor over word)
press ctrl-q and the contents of the file becomes
foo_bar_baz
I've determined the solution to my question.
expand('<cword>') will return the current word under your cursor. The keybinding was a simple nnoremap.
Full solution
function! SplitDelim(expr, pat)
let result = []
let expr = a:expr
while 1
let [w, s, e] = matchstrpos(expr, a:pat)
if s == -1
break
endif
call add(result, s ? expr[:s-1] : '')
call add(result, join(['', tolower(w)], '_'))
let expr = expr[e:]
endwhile
call add(result, expr)
return join(result, '')
endfunction
function! FindAndReplace()
" get current cursor position to keep screen constant
let cur_cursor_pos = getpos('.')
let search_term = expand('<cword>')
execute '%s' . '/' . search_term . '/' . SplitDelim(search_term, '[A-Z]') . '/'
" set cursor back to where it was at start of invocation from execing s/
call setpos('.', cur_cursor_pos)
endfunction
nnoremap <C-Q> :call FindAndReplace()<CR>
I would to write a character, and instantly replicate it across the whole line. How can I write a function that mimics a keyboard shortcut in vim?
Go to the start of the line, visually select all the existing characters,
and replace them with the letter x:
0v$rx
Or if there are no characters already, you could insert an x 80 times in a
row:
80ix
An alternative would be to type the letter, then repeat it another 79 times:
ix
79.
Here's a function that will do it for you (called with <LEADER>r):
function! RepChar()
let char = input("character to replicate: ")
let line_length = &textwidth
if line_length == 0
let line_length = input("'textwidth' option not set... how long is a line: ")
endif
execute 'normal! '.line_length.'i'.char
endfunction
nnoremap <LEADER>r :call RepChar()<CR>
I know that it is possible to increment/decrement an integer by a repeat count using the <repeat-count><ctrl>-a and <repeat-count><ctrl>-x commands.
Now I was wondering whether there are analogous commands for multiplication and division. And if there is no such command, how can I implement this in my own .vimrc?
Here is a quick and dirty attempt:
function! Multiply()
" save count
let cnt = v:count1
" save register v
let old_reg = getreg("v")
" select the number under the cursor
call search('\d\([^0-9\.]\|$\)', 'cW')
normal v
call search('\(^\|[^0-9\.]\d\)', 'becW')
" yank it into register v then reselect
normal "vygv
" change the selection with the yanked number multiplied by the count
execute "normal c" . #v * cnt
" restore register v
call setreg("v", old_reg)
endfunction
nnoremap <F5> :<C-u>call Multiply()<CR>
Now, press 5<F5> to multiply the number under the cursor by 5.
If you want to do it without mappings/functions:
v{motion}c<C-r>=<C-r>"*5<CR><Esc>
I'm trying to change "X" to " " and vice versa to mark a checkbox in a markdown file in normal mode:
- [X] Zucchini
- [ ] Nutmeg
Here's what I've tried:
First
function! ToggleComplete()
if getline('.')[col('.')-1] == 'X'
return ' '
else
return 'X'
endif
endfunction
nnoremap <C-x> :call ToggleComplete()<CR>
Second
function! ToggleComplete()
if getline('.')[col('.')-1] == 'X'
return '\r\<Space>'
else
return '\rX'
endif
endfunction
nnoremap <C-x> :call ToggleComplete()<CR>
It really can't work like this; the main reason is how you use the return statement : your function returns a space or an X char, but the returned value is never used, and is lost when you use call ToggleComplete(). Actually there's nothing in your code that changes the content of your buffer.
A secondary point: your if test is very restrictive; it requires your cursor to be exactly on the right char in the line, in order to work (because of [col('.')-1]). Maybe it's what you want, but you may also add some flexibility by using a test which works without depending on the cursor column.
The following is one possibility of doing what you want:
function! ToggleComplete()
" Get current line:
let l:line = getline('.')
" Get the char to test with the help of a pattern, ' ' or 'X':
" \zs and \ze lets you retrieve only the part between themselves:
let l:char = matchstr(l:line, '\[\zs.\ze]')
" Invert the value:
if l:char == 'X'
let l:char = ' '
else
let l:char = 'X'
endif
" Replace the current line with a new one, with the right
" char substituted:
call setline(line('.'), substitute(l:line, '\[\zs.\ze]', l:char, ''))
" Please note that this last line is doing the desired job. There is
" no need to return anything
endfunction
Let us say I have the following three paragraphs of text (separated
from each other by empty lines—number 3 and 7, here):
This is my first paragraph line 1
This is my first paragraph line 2
This is my second paragraph line 4
This is my second paragraph line 5
This is my second paragraph line 6
This is my third paragraph line 8
This is my third paragraph line 9
Question 1: How can I number these paragraphs automatically,
to obtain this result:
1 This is my first paragraph line 1
This is my first paragraph line 2
2 This is my second paragraph line 4
This is my second paragraph line 5
This is my second paragraph line 6
3 This is my third paragraph line 8
This is my third paragraph line 9
(I succeeded to do this, but only via a clumsy macro.)
Question 2: Is it possible to refer to these paragraphs? For
instance, is it possible to index a text file as answered (by Prince
Goulash and Herbert Sitz) in the earlier question, but this time
with the paragraph numbers and not the line numbers?
Thanks in advance.
Here's one way to do the ref numbers, with a pair of functions:
function! MakeRefMarkers()
" Remove spaces from empty lines:
%s/^ \+$//
" Mark all spots for ref number:
%s/^\_$\_.\zs\(\s\|\S\)/_parref_/
" Initialize ref val:
let s:i = 0
" Replace with ref nums:
%s/^_parref_/\=GetRef()/
endfunction
function! GetRef()
let s:i += 1
return s:i . '. '
endfunction
Then just do it by calling MakeRefMarkers(). It doesn't remove existing ref numbers if they're there, that would require another step. And it doesn't catch first paragraph if it's first line in file (i.e, without preceding blank line). But it does handle situations where there's more than one blank line between paragraphs.
Question One
Here is a function to enumerate paragraphs. Simply do :call EnumeratePara() anywhere in your file. The variable indent can be adjusted as you wish. Let me know if anything needs correcting or explaining.
function! EnumeratePara()
let indent = 5
let lnum = 1
let para = 1
let next_is_new_para = 1
while lnum <= line("$")
let this = getline(lnum)
if this =~ "^ *$"
let next_is_new_para=1
elseif next_is_new_para == 1 && this !~ "^ *$"
call cursor(lnum, 1)
sil exe "normal i" . para . repeat(" ", indent-len(para))
let para+=1
let next_is_new_para = 0
else
call cursor(lnum, 1)
sil exe "normal i" . repeat(" ", indent)
endif
let lnum += 1
endwhile
endfunction
Question Two
This isn't a very elegant approach, but it seems to work. First of all, here's a function that maps each line in the file to a paragraph number:
function! MapLinesToParagraphs()
let lnum = 1
let para_lines = []
let next_is_new_para = 1
let current_para = 0
while lnum <= line("$")
let this = getline(lnum)
if this =~ "^ *$"
let next_is_new_para = 1
elseif next_is_new_para == 1
let current_para += 1
let next_is_new_para = 0
endif
call add(para_lines, current_para)
let lnum += 1
endwhile
return para_lines
endfunction
So that para_lines[i] will give the paragraph of line i.
Now we can use the existing IndexByWord() function, and use MapLinesToParagraph() to convert the line numbers into paragraph numbers before we return them:
function! IndexByParagraph(wordlist)
let temp_dict = {}
let para_lines = MapLinesToParagraphs()
for word in a:wordlist
redir => result
sil! exe ':g/' . word . '/#'
redir END
let tmp_list = split(strtrans(result), "\\^\# *")
let res_list = []
call map(tmp_list, 'add(res_list, str2nr(matchstr(v:val, "^[0-9]*")))')
call map(res_list, 'para_lines[v:val]')
let temp_dict[word] = res_list
endfor
let result_list = []
for key in sort(keys(temp_dict))
call add(result_list, key . ' : ' . string(temp_dict[key])[1:-2])
endfor
return join(result_list, "\n")
endfunction
I have not tested these functions very thoroughly, but they seem to work okay, at least in your example text. Let me know how you get on!
Both problems could be solved much easier than it is suggested
by the other two answers.
1. In order to solve the first problem of numbering paragraphs,
the following two steps are ample.
Indent the paragraphs (using tabs, here):
:v/^\s*$/s/^/\t/
Insert paragraph numbering (see also my answer to
the question on substitution with counter):
:let n=[0] | %s/^\s*\n\zs\ze\s*\S\|\%1l/\=map(n,'v:val+1')
2. The second problem of creating index requires some scripting in
order to be solved by Vim means only. Below is the listing of a small
function, WordParIndex() that is supposed to be run after paragraphs
are numbered according to the first problem's description.
function! WordParIndex()
let [p, fq] = [0, {}]
let [i, n] = [1, line('$')]
while i <= n
let l = getline(i)
if l !~ '^\s*$'
let [p; ws] = ([p] + split(l, '\s\+'))[l=~'^\S':]
for w in ws
let t = get(fq, w, [p])
let fq[w] = t[-1] != p ? t + [p] : t
endfor
endif
let i += 1
endwhile
return fq
endfunction
The return value of the WordParIndex() function is the target index
dictionary. To append its text representation at the end of a buffer,
run
:call map(WordParIndex(), 'append(line("$"),v:key.": ".join(v:val,","))')
My approach would be macro based:
Yank the number "0" somehow and move to the start of the first paragraph.
Record a macro to
Indent the paragraph with >}
Paste the stored number at the correct position p
Increment the number by one with <ctrl>-a
Yank the pasted number with yiw
Move to the next paragraph with }l or /^\S
Execute the macro as many times as needed to reach the end of the document.
The method of pasting a number, incrementing it, and then reyanking it inside a macro is quite a useful technique that comes in handy whenever you need to number things. And it's simple enough to just do it in a throw-away fashion. I mainly use it for carpet logging, but it has other uses as your question demonstrates.