Trouble inserting blank characters in an inoremap <expr> statement - vim

I'm trying to make a <Space> keymap that inserts padding around the cursor when it is inside empty brackets. Essentially, I desire [.] -> [ . ], where the period is the cursor position. Everything in the following code is working as expected for me, but I run into trouble at the 3rd exec statement in the for loop:
let bracket_pairs = {'{':'}', '(':')', '[':']'}
def InEmptyBrackets(left_brack: string, right_brack: string): bool
return functions#CharAt(-1) == left_brack && functions#CharAt(0) == right_brack
enddef
for [key, value] in items(bracket_pairs)
" auto insert closing bracket
exec "inoremap " . key . " " . key . value . "<Esc>i"
" auto format indentation when inside empty brackets on 'Enter' keypress
exec "inoremap <expr> <CR> InEmptyBrackets('" . key . "','" . value . "') ? \"\<CR>\<Esc>O\" : \"\<CR>\""
" pad cursor on both sides when inside empty brackets on 'Space' keypress
exec "inoremap <expr> <Space> InEmptyBrackets('" . key . "','" . value . "') ? \"\<Space>\<Space>\<Esc>i\" : \"\<Space>\""
" skip closing bracket if already present when typed
exec "inoremap <expr> " . value . " functions#CharAt(0) == '" . value . "' ? \"\<Esc>la\" : \"" . value . "\""
endfor
I've tried many variations on the \"\<Space>\" term including \" \", ' ', etc. with no luck. Any old crusty Vim veterans out there know a solution?
Here is the CharAt() function if necessary.
def functions#CharAt(distance_from_cursor: number): string
return getline('.')[col('.') + distance_from_cursor - 1]
enddef

Related

vim null symbol effecting string ^#

I am using the following function to get the Selected test
let s:drawscript = "somerandom.py"
func! GetSelectedText()
normal gv"xy
let result = getreg("x")
normal gv
return result
endfunc
vnoremap <tab><tab> :<c-u>call Box(GetSelectedText())<CR>
func! Box(text)
let s:b = '"' . a:text . '"'
echom s:b
" exec boxcmd
"echom 'hi'
let c = ["python3", s:drawscript, s:b]
execute ":.!".join(c, " ")
endfunc
I am trying to pass in the text selected to my python file, it works when I only select 1 line, but when I select multiple lines, there are "^#" symbols in the selected text which caused automatic execution which leads to an error. I just wanna pass in the text I have selected into the .py file.
It's a matter of escaping special characters for the shell. While you correctly thought of quoting the text, you missed to escape the line separator. There's the function shellescape() which takes care of this and more, so you can replace
let s:b = '"' . a:text . '"'
by
let s:b = shellescape(a:text, 1)

vim: escaping strings for substitution (vimscript)

I currently write a substitute function that I often need for programming in vim.
The functions I already wrote look like this and run basically okay for searching and replacing strings which do not have any special characters inside. I already realized to escape the "/" automatically. My question is, how do I have to adapt the escape() function in the line
execute ':silent :argdo %s/' . escape(searchPattern, '/') . '/' . escape(replacePattern, '/') . '/ge'
So that automatically all of the characters that have to be escaped will be escaped?
" MAIN FUNCTION
" inspired by http://vimcasts.org/episodes/project-wide-find-and-replace/
function! SubstituteProjectwide(searchInput)
:set hidden
let cwd = getcwd()
let filename=expand('%:p')
call inputsave()
let searchPattern = input('Search for: ', a:searchInput)
let replacePattern = input('Replace "' . searchPattern . '" with: ')
let filePattern = input('Filepattern: ', cwd . '/**/*.*')
call inputrestore()
execute ':silent :args ' . filePattern
execute ':silent :vimgrep /' . searchPattern . '/g ##'
execute ':silent :Qargs'
execute ':silent :argdo %s/' . escape(searchPattern, '/\') . '/' . escape(replacePattern, '/\') . '/ge'
execute ':silent :edit ' . filename
echo 'Replaced "' . searchPattern . '" with "' . replacePattern . '" in ' . filePattern
endfunction
" VISUAL ENTRYPOINT WITH SELECTED TEXT
function! SubstituteProjectwideVisual()
let v = #*
call SubstituteProjectwide(GetVisualSelectedText())
endfunction
:vnoremap <F6> :call SubstituteProjectwideVisual()<cr>
" NORMAL ENTRYPOINT WIHT WORD UNDER CURSOR
function! SubstituteProjectwideNormal()
let wordUnderCursor = expand("<cword>")
call SubsituteProjectwide(wordUnderCursor)
endfunction
:nnoremap <F6> :call SubstituteProjectwideNormal()<cr>
" GETTING THE FILES WICH CONTAIN SEARCH PATTERN
" copied from http://vimcasts.org/episodes/project-wide-find-and-replace/
command! -nargs=0 -bar Qargs execute 'args' QuickfixFilenames()
function! QuickfixFilenames()
let buffer_numbers = {}
for quickfix_item in getqflist()
let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
endfor
return join(map(values(buffer_numbers), 'fnameescape(v:val)'))
endfunction
" GETTING THE CURRENT VISUAL SELECTION
" copied from: https://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript
function! GetVisualSelectedText()
let [line_start, column_start] = getpos("'<")[1:2]
let [line_end, column_end] = getpos("'>")[1:2]
let lines = getline(line_start, line_end)
if len(lines) == 0
return ''
endif
let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
let lines[0] = lines[0][column_start - 1:]
return join(lines, "\n")
endfunction
UPDATE
I managed to escape many characters like that
escape(searchPattern, ' / \') . '/' . escape(replacePattern, ' / \')
But how do I know which list of characters i have to escape, when it is basically possible, that every character can be inside the search and also the replace string?
To do a literal substitution, specify "very-nomagic" (:help /\V) , and escape the separator (/) and \
in the source.
In the replacement, & and ~ must be escaped, too, if the 'magic' option is set. (\V doesn't work here.)
execute ':silent :argdo %s/\V' . escape(searchPattern, '/\') . '/' . escape(replacePattern, '/\' . (&magic ? '&~' : '')) . '/ge'
Line breaks (if possible) must be changed from ^M to \n:
execute ':silent :argdo %s/\V' . substitute(escape(searchPattern, '/\'),"\n",'\\n','ge') . '/' . escape(replacePattern, '/\' . (&magic ? '&~' : '')) . '/ge'
This doesn't exactly answer your question but is another way of looking at the problem you're trying to solve. I don't entirely follow what the :args setup is doing for you since the quickfix has all of the info you need after the :vimgrep.
I have this in my vimrc:
nnoremap <F3> :vimgrep // $PROJECT_ROOT_DIR/src/**/*.{cpp,h,c,inl,msg}<C-Left><C-Left><Right>
Obviously you'll want to customize the search path, as this focuses on just the above five file extensions in a specific file hierarchy that was configured each time I launched Vim...
Anyway, once you've got that, :cr makes sure you're at the beginning, then do the search&replace you want inside of a macro. You can actually test it out on the first few finds if you want, but then...
qbq Clear the 'b' register.
qa Start recording the 'a' macro'
:%s/this/that/g Start the macro and substitute 'that' for 'this'. (Press enter)
:w|cnf write the file and go to the next one (Press enter)
q Stop recording the 'a' macro.
Then qb#a#bq will run the macro once, saving it in #b. Then just run
(type) #b once more and it'll keep calling itself until it's done.

Vim. set command line from a function

I'm trying to write a function that replaces text in all buffers. So I call Ack to search all the matches and next step I want to set into Quickfix command line this code
:QuickFixDoAll %s/foo/boo/gc
Seems like I can only call 'exec' function which runs this command immediately and there is no ablility to edit it or cancel at all
I also tried "input" function to read user input but got this error at runtime
not an editor command
Any ideas?
.vimrc:
function! ReplaceInFiles(o, n)
exec "Ack '" . a:o . "'"
exec "QuickFixDoAll %s/" . a:o . "/" . a:n . "/gc"
endfunction
" QuickFixDoAll
function! QuickFixDoAll(command)
if empty(getqflist())
return
endif
let s:prev_val = ""
for d in getqflist()
let s:curr_val = bufname(d.bufnr)
if (s:curr_val != s:prev_val)
exec "edit " . s:curr_val
exec a:command
endif
let s:prev_val = s:curr_val
endfor
exec "quit"
endfunction
command! -nargs=+ QuickFixDoAll call QuickFixDoAll(<f-args>)
Using input()
This queries both values interactively:
function! ReplaceInFiles()
let l:o = input('search? ')
let l:n = input('replace? ')
exec "Ack '" . l:o . "'"
exec "QuickFixDoAll %s/" . l:o . "/" . l:n . "/gc"
endfunction
nnoremap <Leader>r :call ReplaceInFiles()<CR>
Incomplete mapping
nnoremap <Leader>r :let o = ''<Bar>exec "Ack '" . o . "'"<Bar>exec "QuickFixDoAll %s/" . o . "//gc"<Home><Right><Right><Right><Right><Right><Right><Right><Right><Right>
This one puts the cursor on the right spot for the search. As this value is used twice (Ack and QuickFixDoAll), it is assigned to a variable. After that, move to the end of the command and fill in the replacement in between the //gc.
Custom parsing
The most comfortable option would be a custom command :AckAndSubstAll/search/replacement/. For that, you'd need to parse the two parts in the custom command (like :s does). You could do that with matchstr(), or use ingo#cmdargs#substitute#Parse() from my ingo-library plugin.
First use vim-qargs to copy all files from the quickfix window into Vim's arglist by calling :Qargs.
Then run your replace on all arguments in the arglist by doing :argdo %s/search/replace/gc

Inserting two new lines in Vim?

Is it possible to do this with a Vim command?
[] = Cursor Normal Mode
[ = Cursor Insert Mode
Before
Text []
After
Text
[
Before
Text []
After
[
Text
I've changed the [count] behavior of o / O with the following mapping. I think this does what you want:
" o/O Start insert mode with [count] blank lines.
" The default behavior repeats the insertion [count]
" times, which is not so useful.
function! s:NewLineInsertExpr( isUndoCount, command )
if ! v:count
return a:command
endif
let l:reverse = { 'o': 'O', 'O' : 'o' }
" First insert a temporary '$' marker at the next line (which is necessary
" to keep the indent from the current line), then insert <count> empty lines
" in between. Finally, go back to the previously inserted temporary '$' and
" enter insert mode by substituting this character.
" Note: <C-\><C-n> prevents a move back into insert mode when triggered via
" |i_CTRL-O|.
return (a:isUndoCount && v:count ? "\<C-\>\<C-n>" : '') .
\ a:command . "$\<Esc>m`" .
\ v:count . l:reverse[a:command] . "\<Esc>" .
\ 'g``"_s'
endfunction
nnoremap <silent> <expr> o <SID>NewLineInsertExpr(1, 'o')
nnoremap <silent> <expr> O <SID>NewLineInsertExpr(1, 'O')
do these two mapping help?
nnoremap <leader>O O<ESC>O
nnoremap <leader>o o<cr>
the first by pressing <leader>O will add two empty lines above current line, and bring you to INSERT mode. The 2nd one by pressing <leader>o will add two lines after your current.

How do you check that a vim command (a motion) succeeded or failed?

I wanted to do something like this
try
normal! t_
catch
normal! w
endtry
meaning that it will go the next underscore _ or to the next word boundary if there is no underscore.
How do you actually check if a motion was successful? I was think to compare the getpos('.') before and after the command but I realized that doesn't necessarily mean that the motion was unsuccessful. For example if the cursos was already before the _
Your best shot will be to keep current position (setting a mark or saving current position) before motion and check position after that.
:mark a
# or
:let col = col(".")
:let line = line(".")
# motion command (say w,j,zj,f.)
# check difference
:echo col("'a") . "," . line("'a") . " = " . col('.') . "," . line('.')
# or
:echo col . "," . line . " = " . col('.') . "," . line('.')
Documentation:
col() function
line() function
Depending on your needs, col() maybe omitted as a movement detector.
More accurate position can be obtained with getpos():
getpos({expr}) Get the position for {expr}. For possible values of {expr} see |line()|.
The result is a |List| with four numbers:
[bufnum, lnum, col, off]
Documentation
getpos() function
:mark a
# or
:let old_pos = getpos(".")
# motion command (say w,j,zj,f.)
:let mark_a = getpos("'a")
:let current_pos = getpos(".")
# check difference
:echo mark_a[2] . "," . mark_a[1] . " = " . current_pos[2] . "," . current_pos[1]
# or
:echo old_pos[2] . "," . old_pos[1] . " = " . current_pos[2] . "," . current_pos[1]
check real usage, with save for old mark if it is already used (lnum > 0), here:
ag.vim autoload/ag.vim:215

Resources