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

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

Related

Search for word under cursor and invoke function on key press VimScript

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>

Highlight entire line according to pattern match

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()

Change title case (the first letters of all words capitalized) in Vim

There are a lot of solutions around to change the case of the entire word, but what I want is change the case in a way like this:
From "What is the meaning of life? A new proposal" I want: "What is The Meaning of Life? A new Proposal" if that's too hard this: "What Is The Meaning of Life? A New Proposal" would be enough.
This is commonly called title case; there is a visual mode mapping solution from the Vim Tips Wiki
function! TwiddleCase(str)
if a:str ==# toupper(a:str)
let result = tolower(a:str)
elseif a:str ==# tolower(a:str)
let result = substitute(a:str,'\(\<\w\+\>\)', '\u\1', 'g')
else
let result = toupper(a:str)
endif
return result
endfunction
vnoremap ~ y:call setreg('', TwiddleCase(#"), getregtype(''))<CR>gv""Pgvl
Alternative
If you want to implement a more robust solution yourself, my TextTransform plugin can help with setting up x{motion}, xx and {Visual}x mappings, so you just need to write the actual transformation function.
Edit: Ah well, couldn't stop myself, here is an implementation that is able to handle exceptions:
if ! exists('g:TitleCase_ExceptionPattern')
" Source:
" http://grammar.yourdictionary.com/capitalization/rules-for-capitalization-in-titles.html
let g:TitleCase_ExceptionPattern = '^\%(amid\|a[nst]\?\|and\|are\|but\|by\|down\|for\|from\|i[ns]\|into\|like\|near\|new\|nor\|old\|o[fnr]\|off\|onto\|over\|past\|per\|plus\|than\|the\|to\|up\|upon\|via\|with\)$'
endif
function! TitleCase( text )
return substitute(a:text, '\(^\<\w\+\>\)\|\<\w\+\>', '\=s:TitleCase(submatch(0), ! empty(submatch(1)))', 'g')
endfunction
function! s:TitleCase( word, isException )
if ! a:isException && a:word =~# g:TitleCase_ExceptionPattern
return tolower(a:word)
endif
return substitute(a:word, '\w', '\u&', '')
endfunction
call TextTransform#MakeMappings('', '<Leader>s~', 'TitleCase')
And here's a variant that cycles through Title Case with exceptions → Title Case all (without exceptions) → lowercase:
function! subs#TitleCase#Do( text )
let l:wordExpr = '\(^\<\w\+\>\|\<\w\+\>$\)\|\<\w\+\>'
let l:text = substitute(a:text, l:wordExpr, '\=s:TitleCase(submatch(0), ! empty(submatch(1)))', 'g')
if l:text ==# a:text
let l:text = substitute(a:text, l:wordExpr, '\=s:TitleCase(submatch(0), 1)', 'g')
if l:text ==# a:text
let l:text = substitute(a:text, l:wordExpr, '\L&', 'g')
endif
endif
return l:text
endfunction

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

Fold up to the fold start, instead of the indent start

I use foldmethod=indent and when I fold code like this:
def cake():
#cake!
print( "cake" )
print( "for" )
print( "you" )
I see
def cake():
#cake!
print( "cake" ) +++ 3 lines folded
but I want to see
def cake(): +++ 5 lines folded
Is there a way to do fold up to the first line (def cake():) like this?
Chapters 48 and 49 of Learn Vimscript the Hard Way talk about how to do that, using foldmethod=expr instead of indent. Basically you need to make a custom ftplugin and put a folding script in it; the script contains functions used to determine what fold level different lines should have.
As luck would have it, the example code given in those two chapters is for the Potion language which, like Python, is whitespace-sensitive, so it should be pretty easy to adapt it to Python. Since Vim already comes with a Python ftplugin, I think you can put the folding script described on the site into .vim/after/ftplugin/python instead of .vim/ftplugin/potion.
I solved this using this tutorial.
This is the finished bunch of functions:
fu! Indent_level(lnum)
return indent(a:lnum) / &shiftwidth
endfunction
fu! Next_non_blank_line(lnum)
let numlines = line('$')
let current = a:lnum + 1
while current <= numlines
if getline(current) =~? '\v\S'
return current
endif
let current += 1
endwhile
return -2
endfunction
fu! Custom_fold_expr(lnum)
if getline(a:lnum) =~? '\v^\s*$'
return '-1'
endif
let this_indent = Indent_level(a:lnum)
let next_indent = Indent_level(Next_non_blank_line(a:lnum))
if next_indent == this_indent
return this_indent
elseif next_indent < this_indent
return this_indent
elseif next_indent > this_indent
return '>' . next_indent
endif
endf
set foldexpr=Custom_fold_expr(v:lnum)
foldmethod=expr
Please don't edit the indentation of the "end" markers on this post, it looks gorgeous after you put this in your vimrc.

Resources