Highlight entire line according to pattern match - vim

I'm trying to get to vim or nvim to highlight the entire gui line if there is a match on the screen.
I have no idea how to begin approaching this or what to search for.
I'm able to get vim to highlght according to a pattern match, but I want it to highlight the entire gui width like it does at the bottom of the screen (in black) as shown above.
How can I achieve this?

As far as I know, it is not possible to highlight the entire line except sign feature. The following example uses #/ register value to find the last searched lines and will show the Search highlight on them.
function! s:ToggleHighlightSearchLines()
if !exists('s:id')
let s:id = localtime()
let lc = [line('.'), col('.')]
call cursor([1, 1])
let s:sc = &signcolumn
let &signcolumn = 'no'
sign define hlsLines linehl=Search
let s:sp = 0
while 1
let ln = search(#/, 'W')
if ln == 0 | break | endif
execute 'sign place ' . s:id . ' line=' . ln . ' name=hlsLines'
let s:sp += 1
endwhile
call cursor(lc)
else
while 0 < s:sp
execute 'sign unplace ' . s:id
let s:sp -= 1
endwhile
sign undefine hlsLines
let &signcolumn = s:sc
unlet s:id s:sp s:sc
endif
endfunction
command! ToggleHLSLines call s:ToggleHighlightSearchLines()

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

Howto replace elements of one list of values by elements of second list interactively?

When writing sometimes I have to replace several different words successively, say a1 -> a2, b1->b2, c1->c2 and so on.
For this I manually edit the command history so I start with
:s/a1/a2/gc
after go and replace a1 and a2 by b1 and b2, do the substitution and continue to the next item.
The work flow I would like to have would be something like:
:let in = ['a1', 'b1', 'c1']
:let out = ['a2', 'b2', 'c2']
:call ReplaceAllWithConfirmation(in, out)
where ReplaceAllWithConfirmation would be a function that would perform the the substitutions but asking me to confirm each time.
Would this be possible?
Thanks!
You don't need to write a function. Just run this command:
:%s/[a-c]1/\={'a1':'a2','b1':'b2','c1':'c2'}[submatch(0)]/gc
You can have a expression after \=.
With Tim Pope's Abolish plugin, your example would be
%Subvert/{a,b,c}1/{}2/g
After I asked the questions I came up with this function and mapping that almost does what I wanted. This is not as elegant as the two other answers, but it can be adapted to execute a succession of general commands specified by a list pausing in between each of the commands so I am writing here.
function! MyLatexReplaceText(...)
if a:0 > 0
if a:0 != 2
echom "provide two lists to the function"
return ''
endif
let g:replace_list_in = a:1
let g:replace_list_out = a:2
let g:replace_ncur = 0
endif
if a:0 == 0 && !exists("g:replace_ncur")
let g:replace_list_in = ['a1', 'b1', 'c1']
let g:replace_list_out = ['a2', 'b2', 'c2']
let g:replace_ncur = 0
endif
if g:replace_ncur == len(g:replace_list_in)
let g:replace_cmd = ":echom 'reached end of list'\<CR>"
let g:replace_ncur = -1
else
let range_ = "'<,'>"
let extra_flags = 'gc'
let g:replace_cmd = ':' .range_ . "s/" . g:replace_list_in[g:replace_ncur]
\ . "/" . g:replace_list_out[g:replace_ncur] . "/" . extra_flags
\ . "\<CR>"
call histadd("cmd", g:replace_cmd)
endif
let g:replace_ncur += 1
return g:replace_cmd
endfunction
nnoremap <silent><expr> <Leader>ns MyLatexReplaceText()

How to resize a window to fit, taking into account only logical lines?

I'm looking to write a function that I can call from a map. The idea is to resize a window to fit the buffer contents. This isn't too difficult:
fu! ResizeWindow(vert) "{{{
if a:vert
let longest = max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
exec "vertical resize " . (longest+4)
else
exec 'resize ' . line('$')
1
endif
endfu "}}}
I would, however, like the function to take logical lines into account when calculating the height (I'm not overly worried about width).
For example, a line that has wrapped (due to :set wrap) would count as two or more lines. A block of 37 lines that are folded would only count as one.
Does anyone know of a convenient way of getting this 'logical line count' without having to try and calculate it manually? If I do need to do this manually are there any other cases I'm missing that would cause a line to be represented with a potentially different number of lines?
For anyone interested, I gave up trying to find a simple solution to this. Below is the code I ended up with. It takes into account a couple of obvious edge cases; I'm sure others remain. Suggestions for improvement are very welcome.
fu! Sum(vals) "{{{
let acc = 0
for val in a:vals
let acc += val
endfor
return acc
endfu "}}}
fu! LogicalLineCounts() "{{{
if &wrap
let width = winwidth(0)
let line_counts = map(range(1, line('$')), "foldclosed(v:val)==v:val?1:(virtcol([v:val, '$'])/width)+1")
else
let line_counts = [line('$')]
endif
return line_counts
endfu "}}}
fu! LinesHiddenByFoldsCount() "{{{
let lines = range(1, line('$'))
call filter(lines, "foldclosed(v:val) > 0 && foldclosed(v:val) != v:val")
return len(lines)
endfu "}}}
fu! AutoResizeWindow(vert) "{{{
if a:vert
let longest = max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
exec "vertical resize " . (longest+4)
else
let line_counts = LogicalLineCounts()
let folded_lines = LinesHiddenByFoldsCount()
let lines = Sum(line_counts) - folded_lines
exec 'resize ' . lines
1
endif
endfu "}}}

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