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
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>
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 unfold only the folds containing a fold, to get an outline of my document?
If everything is folded and I press zr a few times I get something close to what I want, except that if parts have different depths I'm either not seeing some folds or seeing some content.
In this example:
# Title {{{1
# Subtitle {{{2
some code here
# Another Title {{{1
code here directly under the level 1 title
I would like to see this when folded:
# Title {{{1
# Subtitle {{{2
# Another Title {{{1
That's not trivial; I've solved this with a recursive function that determines the level of nesting, and then closes the innermost folds.
" [count]zy Unfold all folds containing a fold / containing at least
" [count] levels of folds. Like |zr|, but counting from
" the inside-out. Useful to obtain an outline of the Vim
" buffer that shows the overall structure while hiding the
" details.
function! s:FoldOutlineRecurse( count, startLnum, endLnum )
silent! keepjumps normal! zozj
if line('.') > a:endLnum
" We've moved out of the current parent fold.
" Thus, there are no contained folds, and this one should be closed.
execute a:startLnum . 'foldclose'
return [0, 1]
elseif line('.') == a:startLnum && foldclosed('.') == -1
" We've arrived at the last fold in the buffer.
execute a:startLnum . 'foldclose'
return [1, 1]
else
let l:nestLevelMax = 0
let l:isDone = 0
while ! l:isDone && line('.') <= a:endLnum
let l:endOfFold = foldclosedend('.')
let l:endOfFold = (l:endOfFold == -1 ? line('$') : l:endOfFold)
let [l:isDone, l:nestLevel] = s:FoldOutlineRecurse(a:count, line('.'), l:endOfFold)
if l:nestLevel > l:nestLevelMax
let l:nestLevelMax = l:nestLevel
endif
endwhile
if l:nestLevelMax < a:count
execute a:startLnum . 'foldclose'
endif
return [l:isDone, l:nestLevelMax + 1]
endif
endfunction
function! s:FoldOutline( count )
let l:save_view = winsaveview()
try
call cursor(1, 0)
keepjumps normal! zM
call s:FoldOutlineRecurse(a:count, 1, line('$'))
catch /^Vim\%((\a\+)\)\=:E490:/ " E490: No fold found
" Ignore, like zr, zm, ...
finally
call winrestview(l:save_view)
endtry
endfunction
nnoremap <silent> zy :<C-u>call <SID>FoldOutline(v:count1)<CR>
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.