Replicate character across whole line on Vim/Neovim - vim

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>

Related

Efficient way to delete line containing certain text in vim with prompt

At present i can search for text
/text
and then delete line using dd and if i don't want to delete i can go for next match with n.
But is there any more fast way to do that!
This command below deletes all the lines containing text, but the problem is that it deletes all lines at once, sometimes that text is in some line that is exception.
:g/text/d
But i want something simple like like
:%s/text/some_other_text/gc
because this gives the option to substitute or not to.
You don't need a global command for this. The substitute command in by itself will suffice by
adding a wildcard
and adding an end-of-line.
example
%s/.*text.*\n//gc
You can mix :help global and :help substitute:
:g/text/s/.*\n//c
This will ask for confirmation before deleting every line containing text:
I've tried to found a way to use global and :substitute, and that correctly handles matches on consecutive lines, and matches on the first line, but alas, I'm not inspired.
So, I'm back to my basics: I've implemented what I think is missing: :confirm global.
The result has been pushed in my library plugin.
How it works:
I prepare a stateful variable that remembers the previous user choice when it matters (always, or quit, or last).
I execute global on the pattern, and for each match I check what the user wishes to do.
I either use the don't-ask-again states
or I ask using the StatusLineNC highlight group with echo "\rmessage" + :redraw. This is a very old trick we used to do even before Vim 6 IIRC.
The related code is the following:
" Function: lh#ui#ask(message) {{{3
function! lh#ui#ask(message) abort
redraw! " clear the msg line
echohl StatusLineNC
echo "\r".a:message
echohl None
let key = nr2char(getchar())
return key
endfunction
" Function: lh#ui#confirm_command(command) {{{3
" states:
" - ask
" - ignore
" - always
function! s:check() dict abort
if self.state == 'ignore'
return
elseif self.state == 'always'
let shall_execute_command = 1
elseif self.state == 'ask'
try
let cleanup = lh#on#exit()
\.restore('&cursorline')
\.restore_highlight('CursorLine')
set cursorline
hi CursorLine cterm=NONE ctermbg=black ctermfg=white guibg=black guifg=white
let choice = lh#ui#ask(self.message)
if choice == 'q'
let self.state = 'ignore'
let shall_execute_command = 0
" TODO: find how not to blink
redraw! " clear the msg line
elseif choice == 'a'
let self.state = 'always'
let shall_execute_command = 1
" TODO: find how not to blink
redraw! " clear the msg line
elseif choice == 'y'
" leave state as 'ask'
let shall_execute_command = 1
elseif choice == 'n'
" leave state as 'ask'
let shall_execute_command = 0
elseif choice == 'l'
let shall_execute_command = 1
let self.state = 'ignore'
endif
finally
call cleanup.finalize()
endtry
endif
if shall_execute_command
execute self.command
endif
endfunction
function! s:getSID() abort
return eval(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_getSID$'))
endfunction
let s:k_script_name = s:getSID()
function! lh#ui#make_confirm_command(command, message) abort
let res = lh#object#make_top_type(
\ { 'state': 'ask'
\ , 'command': a:command
\ , 'message': a:message . ' (y/n/a/q/l/^E/^Y)'
\ })
call lh#object#inject_methods(res, s:k_script_name, 'check')
return res
endfunction
" Function: lh#ui#global_confirm_command(pattern, command, message [, sep='/']) {{{3
" Exemple: to remove lines that match a pattern:
" > call lh#ui#global_confirm_command(pattern, 'd', 'delete line?')
function! lh#ui#global_confirm_command(pattern, command, message, ...) abort
let cmd = lh#ui#make_confirm_command(a:command, a:message)
let sep = get(a:, 1, '/')
exe 'g'.sep.a:pattern.sep.'call cmd.check()'
endfunction
" Function: lh#ui#_confirm_global(param) {{{3
function! lh#ui#_confirm_global(param) abort
let sep = a:param[0]
let parts = split(a:param, sep)
if len(parts) < 2
throw "Not enough arguments to `ConfirmGlobal`!"
endif
let cmd = join(parts[1:])
call lh#ui#global_confirm_command(parts[0], cmd, cmd . ' on line?', sep)
endfunction
command! -nargs=1 ConfirmGlobal call lh#ui#_confirm_global('<args>')
From here you could either type:
:call lh#ui#global_confirm_command(pattern, 'd', 'delete line?')
or :ConfirmGlobal/pattern/d which generates a less instructive prompt
The most efficient way is to combine :glboal and :norm
:g/test/norm dd

How can I multiply or divide value under the cursor by a repeat count?

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>

Vim function toggle replace character under cursor

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

VIM incremental search, how to copy the matched string under cursor?

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.

Vim: How to number paragraphs automatically and how to refer to this numbering?

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.

Resources