vim null symbol effecting string ^# - python-3.x

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)

Related

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.

How to make vim's `:global` command confirm-able before executing the ex Command?

How can I make vim's :global command ask the user if they want to execute the ex command? Similar to what happens with the :substite command with the 'c' option, for example %s:Foo:Fighters:gc
I tried:
:g/mypattern/.s:.*\n::gc
and
:g/mypattern/s:.*\n::gc
but if there is a match on below line it is jumped. For example:
MATCH
NONMATCH
MATCH
MATCH
MATCH
The result is:
NONMATCH
MATCH <<-- this should be erased.
A promptable g/FOO/d would be perfect.
There is no native way to do this. The typical method would be to record a macro and repeat a macro. Making sure you n or / at the end of the macro to advance to the next match. Skipping is now simply n and ## to execute the macro.
Custom :Global command
However if you truly want to have :global command with a confirm you can sort of mimic this by using confirm() inside the your command. The general idea is to do something like this:
:g/pat/if confirm("&yes\n&no", 2) == 1 | cmd | endif
This doesn't quite work for the following reasons:
You have no idea where your cursor is. Need something like :match and :redraw
Does not abort well. Need a way to throw an exception to abort
Very unwieldily to type this all out
I have come up with the following confirming :Global/:G command
command! -nargs=+ -range=% -complete=command Global <line1>,<line2>call <SID>global_confirm(<q-args>)
command! -nargs=+ -range=% -complete=command G <line1>,<line2>call <SID>global_confirm(<q-args>)
function! s:global_confirm(args) range
let args = a:args
let sep = args[0]
let [pat, cmd; _] = split(args[1:], '\v([^\\](\\\\)*\\)#<!%d' . char2nr(sep), 1) + ['', '']
match none
let options = ['throw "Global: Abort"', cmd, '', 'throw "Global: Abort"']
let cmd = 'exe ''match IncSearch /\c\%''.line(''.'').''l''.#/.''/'''
let cmd .= '| redraw'
let cmd .= '| exe get(options, confirm("Execute?", "&yes\n&no\n&abort", 2))'
try
execute a:firstline . ',' . a:lastline . 'g'.sep.pat.sep.cmd
catch /Global: Abort/
finally
match none
endtry
endfunction
Note: Use as-is. Uses IncSearch for highlight and forces \c.
Now you can run :G/foo/d.
Custom :Confirm command
If you rather use a similar technique to the one #Randy Morris provided and use the following :Confirm {cmd} command to confirm {cmd} before execution.
command! -nargs=+ -complete=command Confirm execute <SID>confirm(<q-args>) | match none
function! s:confirm(cmd)
let abort = 'match none | throw "Confirm: Abort"'
let options = [abort, a:cmd, '', abort]
match none
execute 'match IncSearch /\c\%' . line('.') . 'l' . #/ . '/'
redraw
return get(options, confirm('Execute?', "&yes\n&no\n&abort", 2), abort)
endfunction
This will allow you to use :g/foo/Confirm d
For more help see:
:h #
:h q
:h confirm()
:h :exe
:h get()
:h :match
:h :redraw
As far as I know there is no way to do this natively. I think I've hacked together a way to do this but it's probably buggy as I haven't written vimscript in a long time. In this I've defined a command C which accepts an ex command as its arguments. Each line returned via :global is then passed to this ex command if you press y or Y. Any other key causes this line to be skipped.
let s:GlobalConfirmSignNumber = 42
sign define GlobalConfirmMarker text=>> texthl=Search
function GlobalConfirm(cmd)
let line = getpos(".")[1]
execute "sign place " . s:GlobalConfirmSignNumber . " line=" . line . " name=GlobalConfirmMarker file=" . expand("%:p")
redraw!
echomsg "Execute? (y/N) "
try
let char = nr2char(getchar())
if (char == "y" || char == "Y")
execute a:cmd
endif
finally
" Ensure signs are cleaned up if execution is aborted.
execute "sign unplace " . s:GlobalConfirmSignNumber
endtry
redraw!
endfunction
command -nargs=* C call GlobalConfirm(<q-args>)
Here's a gif of it in action. In this gif I'm running the command norm! gUU for every line which contains ba. In this case I confirmed every match by pressing y three times.
If anyone can make improvements to this (especially the signs bit) please edit at will.

Yanking all marked lines in vim

Often times when reviewing log files in vim, I'll highlight interesting lines using marks. At some point, I'd like to be able to copy all of the interesting lines (either all marked lines, or a list of marks) to either a register or another file (it doesn't really matter which; the goal is to facilitate writing a summary). I haven't been able to find any built in way to do this; is it possible in vim?
I suppose it's probably a fairly straightforward function; probably looking something like this, but my vimscript abilities are very weak:
for cur_mark in list_of_marks
goto mark
yank current line and append to register
Has anyone ever written anything similar that they can point me to?
Thanks
EDIT: I posted the accepted solution at https://github.com/mikeage/vim-yankmarks
As always, there are few things that are more motivating than asking for help. Here's what I came up with; feedback welcome.
function! Yankmark()
let save_cursor = getpos(".")
let n = 0
" I should really make this a parameter...
let marks_to_yank="abcdefghijklmnopqrstuvwxyz"
let nummarks = strlen(marks_to_yank)
" Clear the a register
let #a=''
while n < nummarks
let c = strpart(marks_to_yank, n, 1)
" Is the mark defined
if getpos("'".c)[2] != 0
" using g' instead of ' doesn't mess with the jumplist
exec "normal g'".c
normal "Ayy
endif
let n = n + 1
endwhile
call setpos('.', save_cursor)
endfunction
Mikeage had a great idea; here's a more refined version of his function turned into a command:
":YankMarks [{marks}] [{register}]
" Yank all marked (with [a-z] / {marks} marks) lines into
" the default register / {register} (in the order of the
" marks).
function! s:YankMarks( ... )
let l:marks = 'abcdefghijklmnopqrstuvwxyz'
let l:register = '"'
if a:0 > 2
echohl ErrorMsg
echomsg 'Too many arguments'
echohl None
return
elseif a:0 == 2
let l:marks = a:1
let l:register = a:2
elseif a:0 == 1
if len(a:1) == 1
let l:register = a:1
else
let l:marks = a:1
endif
endif
let l:lines = ''
let l:yankedMarks = ''
for l:mark in split(l:marks, '\zs')
let l:lnum = line("'" . l:mark)
if l:lnum > 0
let l:yankedMarks .= l:mark
let l:lines .= getline(l:lnum) . "\n"
endif
endfor
call setreg(l:register, l:lines, 'V')
echomsg printf('Yanked %d line%s from mark%s %s',
\ len(l:yankedMarks),
\ len(l:yankedMarks) == 1 ? '' : 's',
\ len(l:yankedMarks) == 1 ? '' : 's',
\ l:yankedMarks
\) . (l:register ==# '"' ? '' : ' into register ' . l:register)
endfunction
command! -bar -nargs=* YankMarks call <SID>YankMarks(<f-args>)
A different way of accomplishing this might be using the :global command. The global command takes the form :g/{pattern}/{cmd}. The command, {cmd}, will be executed on all lines matching {pattern}.
Append lines matching a pattern to a register:
:g/pattern/yank A
Append matching line to a log file:
:g/pattern/w >> file.log
Of course if you want to find line matching a mark you can match it in your pattern. The following pattern matches a line with mark m.
:g/\%'m/w >> file.log
To do something like this. (Note: I am using \v to turn on very magic)
:g/\v(%'a|%'b|%'m)/yank A
Of course if a pattern won't work you can do this by hand. Instead of marking the lines just build up the lines as you go. Just yank a line to an uppercase register to append.
"Ayy
Or do a write append with a range of a single line
:.w >> file.log
For more help see
:h :g
:h :w_a
:h /\%'m
:h /\v
You can do something like:
:redir #a
:silent marks XYZN
:redir END
"ap
That way the output of the :marks command will be redirected to the a register. Note, that it will only lists (in the above case) the X, Y, Z and N marks (as the arguments), and if there was an a register, it will be deleted/overwritten.
Also note, that it might not give the desired output, but gives you a starting point...
I like the solution from Mikeage, though I would probably solve this with the multiselect - Create multiple selections and operate plugin. This also has the benefit that you don't run out of marks.
With the plugin, you can select lines with <Leader>msa or :MSAdd. Finally, yank all lines with:
:let #a=''
:MSExecCmd yank A
If you use an upper-case register name when yanking into a specific register, Vim will append the yanked content instead of overwriting the register's value.
So, for example:
"ayy - yank current line to register a, overwriting
[move]
"Ayy - append this line to register a
[move]
"ap - paste all yanked material
See :help quotea for more details.

Create a mapping for Vim's command-line that escapes the contents of a register before inserting it

Suppose that I have a document like this, and I want to search for all occurences of the URL:
Vim resources: [http://example.com/search?q=vim][q]
...
[q]: http://example.com/search?q=vim
I don't want to type it out in full, so I'll place my cursor on the first URL, and run "uyi[ to yank it into the 'u' register. Now to search for it, I'd like to just paste the contents of that register into the search field by running:
/\V<c-r>u<CR>
This results in Vim searching for the string 'http:' - because the '/' character terminates the search field.
I can get around the problem by running this instead:
/\V<c-r>=escape(#u, '\/')<CR><CR>
But it's a lot of typing!
How can I create a mapping for Vim's commandline that simplifies this workflow?
My ideal workflow would go something like this:
press /\V to bring up the search prompt, and use very nomagic mode
hit ctrl-x to trigger the custom mapping (ctrl-x is available)
Vim listens for the next key press... (pressing <Esc> would cancel)
pressing 'u' would escape the contents of the 'u' register, and insert on the command line
Try this:
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
" show some kind of feedback
echo ":".getcmdline()."..."
" get a character from the user
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
return escape(register_content, '\/')
endif
endfunction
By the way, something that might be useful to know (if you don't already) is that you can use ? as the delimiter for :s. Which means that you could write a search-and-replace for an url like so:
:s?http://foo.com?http://bar.com?g
I've accepted Andrew Radev's solution, which solved the hard parts. But here's the version that I've added to my vimrc file, which adds a couple of enhancements:
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
echo "\\".getcmdline()."\""
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
let escaped_register = escape(register_content, '\'.getcmdtype())
return substitute(escaped_register, '\n', '\\n', 'g')
endif
endfunction
This should work:
whether you use / or ? (to search forwards, or backwards)
and when the pasted register includes multiple lines
Also, I changed the prompt. While waiting for a register, the prompt switches to \ - which seems like a suitable cue for 'PasteEscaped'. Also, I've appended a ", which mimics Vim's behavior after pressing <c-r> at the command line.
If you've any further suggestions for improvements, please leave a comment.
How about different workflow? For example, creating your own operator to search target text as is:
" https://gist.github.com/1213642
" Requiement: https://github.com/kana/vim-operator-user
map YourFavoriteKeySequence <Plug>(operator-search-target-text)
call operator#user#define('search-target-text', 'OperatorSerachTargetText')
function! OperatorSerachTargetText(motion_wise)
execute 'normal!' '`['.operator#user#visual_command_from_wise_name(a:motion_wise).'`]"xy'
let #/ = '\V' . escape(substitute(#x, '[\r\n]$', '', ''), '\')
normal! n
endfunction
I like #nelstrom's solution and made a small change to support escaping [ and ].
cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
echo "\\".getcmdline()."\""
let char = getchar()
if char == "\<esc>"
return ''
else
let register_content = getreg(nr2char(char))
let escaped_register = escape(register_content, '\'.getcmdtype())
let escaped_register2 = substitute(escaped_register,'[','\\[','g')
let escaped_register3 = substitute(escaped_register2,']','\\]','g')
return substitute(escaped_register3, '\n', '\\n', 'g')
endif
endfunction

How do I run a vim script that alters the current buffer?

I'm trying to write a beautify.vim script that makes C-like code adhere to a standard that I can easily read.
My file contains only substitution commands that all begin with %s/...
However, when I try to run the script with my file open, in the manner :source beautify.vim, or :runtime beautify.vim, it runs but all the substitute commands state that their pattern wasn't found (patterns were tested by entering them manually and should work).
Is there some way to make vim run the commands in the context of the current buffer?
beautify.vim:
" add spaces before open braces
sil! :%s/\%>1c\s\#<!{/ {/g
" beautify for
sil! :%s/for *( *\([^;]*\) *; *\([^;]*\) *; *\([^;]*\) *)/for (\1; \2; \3)/
" add spaces after commas
sil! :%s/,\s\#!/, /g
In my tests the first :s command should match (it matches when applied manually).
I just recently wrote a similar beautifier script but I implemented it in what I think is a more flexible way; plus, I tried to come up with a mechanism to avoid substituting stuff within strings.
" {{{ regex silly beautifier (avoids strings, works with ranges)
function! Foo_SillyRegexBeautifier(start, end)
let i = a:start
while i <= a:end
let line = getline(i)
" ignore preprocessor directives
if match(line, '^\s*#') == 0
let i += 1
continue
endif
" ignore content of strings, splitting at double quotes characters not
" preceded by escape characters
let chunks = split(line, '\(\([^\\]\|^\)\\\(\\\\\)*\)\#<!"', 1)
let c = 0
for c in range(0, len(chunks), 2)
let chunk = chunks[c]
" add whitespace in couples
let chunk = substitute(chunk, '[?({\[,]', '\0 ', 'g')
let chunk = substitute(chunk, '[?)}\]]', ' \0', 'g')
" continue like this by calling substitute() on chunk and
" reassigning it
" ...
let chunks[c] = chunk
endfor
let line = join(chunks, '"')
" remove spaces at the end of the line
let line = substitute(line, '\s\+$', '', '')
call setline(i, line)
let i += 1
endw
endfunction
" }}}
Then I define a mapping that affects the whole file in normal mode, and only the selected lines in visual mode. This is good when you have some carefully formatted parts of the file that you don't want to touch.
nnoremap ,bf :call Foo_SillyRegexBeautifier(0, line('$'))<CR>
vnoremap ,bf :call Foo_SillyRegexBeautifier(line("'<"), line("'>"))<CR>

Resources