vim script: can't echo newline after input and if statement - vim

The following function does not echo the result variable.
fu! Test()
let input = input(">")
let result = "error\n"
if 1
echo result
endif
endf
Removing the newline from result, removing the input, or removing the if statement will fix this issue. Any ideas why this happens?
In my actual function the result variable is set from executing a system command and I would prefer not parsing/correcting the result before echoing it.

Vimscript can be strange. When I have issues with echo not showing when it should, usually a call to 'redraw' either before or after the echo fixes it for me.

Try replacing the \n newline with \r, as mentioned at “How to replace a character for a newline in Vim?”:
fu! Test()
let input = input(">")
let result = "error\r"
if 1
echo result
endif
endf
Note that in running the above function I do not get the input cleared before result is echoed, so that if I enter >foo for the input, result is echoed directly and I get >fooerror. Echoing a newline before result is echoed takes care of this:
fu! Test()
let input = input(">")
let result = "error\r"
if 1
echo "\r"
echo result
endif
endf

Related

Get a parsable string with carriage returns

I sometimes use the string() function in order to generate parsable strings.
For example, :echo string("hello world") shows:
'hello world'
But if I add real carriage returns into the string, the result becomes unparsable, simply because
the carriage returns are not transformed.
For example, :echo string("hello\nworld") shows:
'hello
world'
I would have expected:
"hello\nworld"
Is there a way to get a parsable version of any string, without writing a dedicated function?
EDIT
To be more precise, I need "parsable" strings to be used with the :execute command, in order to create customizable mappings. The basic idea is to be able to use the following code, even when there are some CRs in the a:toinsert argument:
function! InsertMapping(lhs, toinsert)
let l:rhs = printf('<c-r>=%s<cr>', string(a:toinsert))
exe 'inoremap' a:lhs l:rhs
endf
" This call is working:
call InsertMapping('<c-r><c-e>', "hello world")
" This one throws an error:
call InsertMapping('<c-r><c-e>', "hello\nworld")
" E492: Not an editor command: world')<cr>
Indeed, the last call to InsertMapping() will try to execute:
inoremap <c-r><c-e> <c-r>='hello
world'<cr>
Buf of course, I need to execute instead:
inoremap <c-r><c-e> <c-r>="hello\nworld"<cr>
Please note that I need to keep <c-r>= because my real use case is more complex, and needs some function calls; so the mapping can't be simplified like this:
inoremap <c-r><c-e> hello<cr>world
Don't know what do you exactly mean "parsable". If you want to use eval() to get the original string value, it is working. You see the real linebreak was printed out because of the echo() function, it expanded the \n as a linebreak.
Give this a try:
let a="hi\nworld"
echo a==eval(string(a))
echo a==eval(string("hi\nworld"))
both will return 1 (true). So it is "parsable".
If I didn't understand the meaning of your "parsable", please make some example.
I finally wrote a dedicated function to stringify a vim string; it's far from perfect, but it will give a "correct" output for all the tested cases. When really nothing at all is special inside the string, it returns a single-quoted string, which may slightly improve performance in some rare cases.
Here is the code:
let s:specials = {
\ "\b":'\b', "\e":'\e', "\f":'\f', "\n":'\n',
\ "\r":'\r', "\t":'\t', "\\":'\\', "\"":'\"',
\}
function! Stringify(source)
let output = ''
let string_is_special = v:false
for i in range(strlen(a:source))
let char = a:source[i]
let ascii = char2nr(char)
let char_is_special = v:false
for [key, str] in items(s:specials)
if char == key
let output .= str
let char_is_special = v:true
let string_is_special = v:true
break
endif
endfor
if !char_is_special
if ascii < 32
let output .= printf('\x%02x', ascii)
let string_is_special = v:true
else
let output .= char
endif
endif
endfor
return printf(string_is_special ? '"%s"' : "'%s'", output)
endf
Here are some quick tests:
let tests = [
\ "simple string",
\ 'back \ slash',
\ "carriage \n return",
\ "utf8 frénçh àccènts",
\ "\x47\x6e\x75",
\ "ctrl-w special key: \<c-w>",
\ ]
for s in tests
echo Stringify(s)
endfor
Here is the tests output:
'simple string'
"back \\ slash"
"carriage \n return"
'utf8 frénçh àccènts'
'Gnu'
"ctrl-w special key: \x17"
In Vim, single quotes and double quotes have different behaviors.
Between double quotes, special characters like \n are interpreted, so:
"hello\nworld"
becomes:
hello
world
and you would have to double the \ to actually get a \n:
"hello\\nworld"
Between single quotes, special characters are not interpreted, so:
'hello\nworld'
becomes:
hello\nworld
All of that is explained under :help expr-" and :help expr-'.

vim script about concating a variable and a const string

I'm writing a function in my ~/.vimrc file, but I got a problem.
I want to concat a variable and a const string, so I do this like below:
let linux_version=system('uname -r')
let host_kernel_dir= "/lib/modules/" . linux_version . "/build"
echo host_kernel_dir
I wanted result is /lib/modules/4.8.0-52-generic/build, but I got this result
"/lib/modules/4.8.0-52-generic
/build "
So it seems a \n was added. So how to get rid of this \n?
This removes newlines from the system output
let linux_version = substitute(system('uname -r'), '\n\+$', '', '')
system() result must be chomped (for those who have known perl), I use the following:
function! lh#os#system(cmd)
return system(a:cmd)[:-2]
endfunction
Another solution using /proc pseudo filesystem:
let linux_version=readfile('/proc/sys/kernel/osrelease')[0]

Escaping "%" in Vim when shelling out, so it's not expanded to filename?

Say I have this vimscript as "/tmp/example.vim":
let g:input = "START; % END"
exec("! clear && echo " . shellescape(g:input))
If I open that file and run it with :so %, the output will be
START; /tmp/example.vim END
because the "%" is expanded to the buffer name. I want the output to be
START; % END
I can use the generic escape() method to escape percent signs in particular. This works:
let g:input = "START; % END"
exec("! clear && echo " . escape(shellescape(g:input), "%"))
But is that really the best way? I'm sure there're more characters I should escape. Is there a specific escape function for this purpose? Or a better way to shell out?
For use with the :! command, you need to pass the optional {special} argument to shellescape():
When the {special} argument is present and it's a non-zero
Number or a non-empty String (|non-zero-arg|), then special
items such as !, %, # and <cword> will be preceded by
a backslash. This backslash will be removed again by the |:!|
command.
:exec("! clear && echo " . shellescape(g:input, 1))
You need to properly escape the '%'. So it should be:
let g:input = "START; \\% END"
This seems to do it:
let g:input = "START; % END"
echo system("echo " . shellescape(g:input))
It should be noted I don't really care about the output; I'll use this with silent in a larger script.

How to get a list of files that match some pattern?

How to get a list of files that match some pattern if filenames may contain \n character?
Update: I want solution in pure vimscript, so that it will depend on nothing but vim.
Update2:
Expected output of glob function
Consider the following script:
:!touch /test ; mkdir /test$'\n' ; touch /test$'\n'/test
:echo glob('/**/test')
/test
/test
/test
That is the output of glob function. I want it be the following:
:echo NewGlob('/**/test')
['/test', '/test
/test']
you may try using ls with -b option. check the man page
:echo split( glob("pattern", '.'), "\r")
If you want the pattern to match files containing \n exclusively, use "*\n*".
EDIT:
I see, the character you use in the filename is the same as the one used by glob() to distinguish results. As a consequence, we can't rely of glob().
ghostdog74 gave a good answer then:
:echo split( system('ls -1bd test*'), "\n")
Of course, this is not portable. But I do not really call this the general case -- I never see this kind of names. If glob() cannot handle this general case, then glob() must be fixed.
May be you can try with embedded python or ruby as arnold suggested. But that isn't portable either.
Try this python program. It will match files like abc\n1, abc\n2abc etc.
#!/usr/bin/env python
import os, re
dirlist = os.listdir('.')
pattern = 'abc\n\\d'
for fname in dirlist:
if re.search(pattern, fname):
print fname.replace('\n', '\\n')
It will replace line end ('\n') characters with "\n" string for clarity.
I finally had to write the following function that returns just the same results as python's os.listdir:
function s:globdir(directory, ...)
return split(glob(escape(a:directory.g:os#pathSeparator, '`*[]\').
\ get(a:000, 0, '*')),
\"\n", 1)
endfunction
function s:GetDirContents(directory)
let dirlist = s:globdir(a:directory)+s:globdir(a:directory, '.*')
let nlnum=len(split(a:directory, "\n", 1))-1
let r=[]
let i=0
let addfragment=""
for directory in dirlist
if i<nlnum
let i+=1
let addfragment=directory."\n"
continue
else
let directory=addfragment.directory
let i=0
let addfragment=""
endif
let tail=fnamemodify(directory, ':t')
if tail==#'.' || tail==#'..'
continue
endif
if directory[0]!=#'/'
let r[-1].="\n".directory
else
call add(r, tail)
endif
endfor
return r
endfunction

How do I substitute from a list of strings in VIM?

I am a vim user, and I want to be able to loop over a range of substrings when I am substituting. How can I use some vim magic to go from a set of lines like this:
Afoo
Bfoo
Cfoo
Dfoo
to
Abar
Bbar
Cbaz
Dbaz
?
I want to search my file from the start for the next occurance of foo, and replace the first two instances with bar, and the second two with baz. Is using a for loop the best option? If so, then how do I use the loop variable in the substitution command?
I would use a function that has a state, and call this function from %s. Something like:
" untested code
function! InitRotateSubst()
let s:rs_idx = 0
endfunction
function! RotateSubst(list)
let res = a:list[s:rs_idx]
let s:rs_idx += 1
if s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
return res
endfunction
And use them with:
:call InitRotateSubst()
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/
The call to the two commands could be encapsulated into a single command if you wish.
EDIT: Here is a version integrated as a command that:
accepts as many replacements as we wish, all the replacements needs to be separated with the separator-character ;
supports back-references ;
can replace only the N first occurrences, N == the number of replacements specified if the command call is banged (with a !)
does not support usual flags like g, i (:h :s_flags) -- for that, we would have for instance to impose the command call to always ends up with a / (or whatever separator-character), if not the last text is interpreted as flags.
Here is the command definition:
:command! -bang -nargs=1 -range RotateSubstitute <line1>,<line2>call s:RotateSubstitute("<bang>", <f-args>)
function! s:RotateSubstitute(bang, repl_arg) range
let do_loop = a:bang != "!"
" echom "do_loop=".do_loop." -> ".a:bang
" reset internal state
let s:rs_idx = 0
" obtain the separator character
let sep = a:repl_arg[0]
" obtain all fields in the initial command
let fields = split(a:repl_arg, sep)
" prepare all the backreferences
let replacements = fields[1:]
let max_back_ref = 0
for r in replacements
let s = substitute(r, '.\{-}\(\\\d\+\)', '\1', 'g')
" echo "s->".s
let ls = split(s, '\\')
for d in ls
let br = matchstr(d, '\d\+')
" echo '##'.(br+0).'##'.type(0) ." ~~ " . type(br+0)
if !empty(br) && (0+br) > max_back_ref
let max_back_ref = br
endif
endfor
endfor
" echo "max back-ref=".max_back_ref
let sm = ''
for i in range(0, max_back_ref)
let sm .= ','. 'submatch('.i.')'
" call add(sm,)
endfor
" build the action to execute
let action = '\=s:DoRotateSubst('.do_loop.',' . string(replacements) . sm .')'
" prepare the :substitute command
let args = [fields[0], action ]
let cmd = a:firstline . ',' . a:lastline . 's' . sep . join(args, sep)
" echom cmd
" and run it
exe cmd
endfunction
function! s:DoRotateSubst(do_loop, list, replaced, ...)
" echom string(a:000)
if ! a:do_loop && s:rs_idx == len(a:list)
return a:replaced
else
let res0 = a:list[s:rs_idx]
let s:rs_idx += 1
if a:do_loop && s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
let res = ''
while strlen(res0)
let ml = matchlist(res0, '\(.\{-}\)\(\\\d\+\)\(.*\)')
let res .= ml[1]
let ref = eval(substitute(ml[2], '\\\(\d\+\)', 'a:\1', ''))
let res .= ref
let res0 = ml[3]
endwhile
return res
endif
endfunction
which could be used this way:
:%RotateSubstitute#foo#bar#bar#baz#baz#
or even, considering the initial text:
AfooZ
BfooE
CfooR
DfooT
the command
%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/
would produce:
ZbarA
BbarE
RbarC
DbarT
This is Not strictly what you want but can be useful for cycles.
I've written a plugin swapit http://www.vim.org/scripts/script.php?script_id=2294 which among other things can help with cycling through lists of strings. Eg.
:Swaplist foobar foo bar baz
then type
This line is a foo
create a simple yank/paste line, go to last word and ctrl-a swap.
qqyyp$^A
then execute the swap pattern
100#q
to get
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
...
It could probably be applied to your problem although its {cword} sensitive.
Why not:
:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/
I'm not sure that it covers the breadth of the problem but does have the virtue of being a simple substitute. A more complex one may cover the solution if this one doesn't.
This is how I'd attempt that macro.
qa Records macro in buffer a
/foo<CR> Search for the next instance of 'foo'
3s Change the next three characters
bar To the word bar
<Esc> Back to command mode.
n Get the next instance of foo
. Repeat last command
n Get the next instance of foo
3s Change next three letters
baz To the word bar
<Esc> Back to command mode.
. Repeat last command
q Stop recording.
1000#a Do a many times.
Any advice on how to do it better is welcome.
thanks,
Martin.
It's probably going to be much easier to record a macro that can replace the first two, and then use :s for the rest.
The macro might look like /foo^Mcwbar^[. If you're not familiar with macro mode, just hit q, a (the register to store it in) and then the keystrokes /foo <Enter> cwbar <Escape>.
Now once you've got that macro, do 2#a to replace the first two occurrences in the current buffer and use :s normally to replace the rest.

Resources