how can I get last echoed message in vimscript? - vim

Is there any way to retrieve last echoed message into a variable?
For example: if i call function, that does:
echo 'foo'
Can I somehow retrieve this 'foo' into a variable?
Thanks!

You can't retrieve last echoed message. But there are other options:
If you can place a :redir command before this function call and another one after, you can catch everything it echoes. But be aware that redirections do not nest, so if function uses :redir itself, you may get nothing:
redir => s:messages
echo "foo"
redir END
let s:lastmsg=get(split(s:messages, "\n"), -1, "")
If function uses :echomsg instead of :echo, then you can use :messages command and :redir:
echom "foo"
redir => s:messages
messages
redir END
let s:lastmsg=get(split(s:messages, "\n"), -1, "")

Related

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]

Change and restore pastetoggle

Suppose that my pastetoggle is set to <F10>, if I run echo &pastetoggle it prints out <80>k; (Question number 1) how can I reach its value as string "<F10>" instead of this <80>k; code. I mean is it possible to set a variable to "<F10>" based on the value of pastetoggle?
Now if I run let #a=&pastetoggle and then run echo #a it prints out the same <80>k; string, but if I run let &pastetoggle=#a afterwards and run echo &pastetogggle it prints out <80><fe>Xk; (Question number 2) why? (Question number 3) how can I set pastetoggle based on value in #a?
The <80>k; is the internal keycode representation of <F10>; unfortunately, as you've found out, it cannot be saved and then reassigned to &pastetoggle.
You can get the "actual" value via
:set pastetoggle?
To capture that, you'd have to use :redir and string extraction:
redir => setOutput
silent! set pastetoggle?
redir END
let pasteToggleKey = matchstr(setOutput, 'pastetoggle=\zs.*')
echo pasteToggleKey

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.

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

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

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

Resources