22

I'm struggling with Python in vim.
I still haven't found out how I can import a value from a python script (in a vim function) back to vim p.e.

function! myvimscript()

  python << endpython
    import vim, random, sys
    s = vim.eval("mylist")
    # do operation with variable "s" in python
  endpython

  " import variable "s" from above 
  " do operation with "s" in vimscript
endfunction

1) How can I use "s" again in vim (how can I import "s" from the python code back to vim)?

I can't find out as well how to use vim.current.buffer with a selection.

function! myvimscript()
  let startline = line("'<")
  let endline = line("'>")

  python << endpython
    start = vim.eval("startline")
    end = vim.eval("endline")
    cb = vim.current.buffer 
    l = cb[start:end]
  endpython
endfunction

2) How can I assign the dynamic value "start" and "end" to "l"

Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146
Reman
  • 7,931
  • 11
  • 55
  • 97
  • is this a python script running from a bash script? – Tall Paul Jul 15 '13 at 14:26
  • @TallPaul, Un pythonscript running in a vimscript (and invoked with a map in vimrc) – Reman Jul 15 '13 at 14:46
  • Related: [How do I get the value returned from a function in Python & Vimscript?](http://stackoverflow.com/q/16756613). – glts Jul 15 '13 at 15:32
  • **Note:** this basic approach of printf-style string interpolation `vim.command("let sInVim = '%s'"% s)` is a source of potential bugs. Specifically, this approach will not work as expected if the interpolated value causes [delimiter collision](https://en.wikipedia.org/wiki/Delimiter#Delimiter_collision) on the single-quote character – dreftymac Nov 22 '22 at 19:24

3 Answers3

28

First of all, please define your function name starting with uppercase.

Here is an example for your two questions. I hope it helps:

function! TestPy() range

    let startline = line("'<")
    let endline = line("'>")
    echo "vim-start:".startline . " vim-endline:".endline
python << EOF
import vim
s = "I was set in python"
vim.command("let sInVim = '%s'"% s)
start = vim.eval("startline")
end = vim.eval("endline")
print "start, end in python:%s,%s"% (start, end)
EOF
    echo sInVim
endfunction

first I paste the output of a small test: I visual selected 3,4,5, three lines, and :call TestPy()

The output I had:

vim-start:3 vim-endline:5
start, end in python:3,5
I was set in python

So I explain the output a bit, you may need to read the example function codes a little for understanding the comment below.

vim-start:3 vim-endline:5   #this line was printed in vim,  by vim's echo.
start, end in python:3,5    # this line was prrinted in py, using the vim var startline and endline. this answered your question two.
I was set in python         # this line was printed in vim, the variable value was set in python. it answered your question one.

I added a range for your function. because, if you don't have it, for each visual-selected line, vim will call your function once. in my example, the function will be executed 3 times (3,4,5). with range, it will handle visualselection as a range. It is sufficient for this example. If your real function will do something else, you could remove the range.

With range, better with a:firstline and a:lastline. I used the line("'<") just for keep it same as your codes.

EDIT with list variable:

check this function:

function! TestPy2()
python << EOF
import vim
s = range(10)
vim.command("let sInVim = %s"% s)
EOF
    echo type(sInVim)
    echo sInVim
endfunction

if you call it, the output is:

3
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

the "3" means type list (check type() function). and one line below is the string representation of list.

Kent
  • 189,393
  • 32
  • 233
  • 301
  • great explication Kent! But what if `s` is not a string but a list. It gives errors with `vim.command("let sInVim = '%s'"% s)` – Reman Jul 15 '13 at 15:40
  • Thank you very much. I will study your answer a bit more. You gave me so much information. But now everything works fine! – Reman Jul 15 '13 at 17:38
  • Now with you info I'm trying to test a random function using python. `:py import vim, random as r; cb = vim.current.buffer ; start = vim.eval('line("'<")'); end = vim.eval('line("'>")'); l = cb[start:end] ; r.shuffle(l) ; cb[start:end] = l` Something still doesn't work. error: must be string not bool. – Reman Jul 15 '13 at 17:49
  • Found the solution :) I had to declare the variables 'start' and 'end' as integers :) – Reman Jul 15 '13 at 19:05
  • For global variables there is another option for `vim.command("let sInVim = '%s'"% s)`: `vim.vars["sInVim"] = s` – mMontu Nov 26 '13 at 11:22
5

As of v7.3.569, vim built-in functions pyeval() and py3eval() allow you to evaluate a python expression, and return its result as a vimscript value. It can handle simple scalar values, but also lists and dictionaries - see :help pyeval()

On 1): For using a python-defined variable in vim:

python << endPython
py_var = 8
endPython

let vim_var = pyeval('py_var')

On 2): I assume you want the buffer lines last highlighted in visual mode, as a list of strings in vimscript:

python << endPython
import vim
cb = vim.current.buffer
start = int(vim.eval("""line("'<")"""))
end = int(vim.eval("""line("'>")"""))
lines = cb[(start - 1) : end]
endPython
let lines = pyeval('lines')

Note: This is a somewhat contrived example, as you could get the same directly in vimscript:

let lines = getline("'<", "'>")
tossbyte
  • 380
  • 4
  • 9
  • I use this one: `python << endPython | my_val = ... | vim.command("let my_val = '%s'" % my_val) | endpython || let vim_val = my_val` What is the difference between both? – Reman May 31 '19 at 14:35
  • 1
    @Reman: `pyeval` might facilitate using existing python libs, which are usually not aware of `vim` context. Also, with `vim.command`, lists and dictionaries might be slightly more brittle, due to potential quoting issues. – tossbyte Jun 02 '19 at 15:03
  • 1
    rippleslash, does it also work with Python 3.7? I do receive errors after having changed a few vim.command in ...pyeval(): "cannot find python library 2.7" - I don't use python 2.7 – Reman Jun 02 '19 at 22:09
  • rippleslash, what to do if a list or dictionary has only integers? py3eval() can't convert the object: `E859: Failed to convert returned python object to vim value` – Reman Jun 04 '19 at 13:36
  • 1
    @Reman, `py3eval("[1, 2, 3]")` and `py3eval("{ 'foo': 1 }")` seem to work, but `py3eval("{ 1: 'foo' }")` doesn't (tried in vim 8.1.1137). According to `:help Dictionary`, vim always uses strings as dict keys, maybe that's why it fails in this case. Best I can think of is to stringify your dict keys - i.e. `def convert(d): return { str(k): v for k, v in d.items() }` in python and `py3eval("convert({ 1: 'foo' })")` in vim. – tossbyte Jun 05 '19 at 12:20
  • rippleslash, thank you for your help. Very appreciated but I decided to keep my vim.command() code, now when I need to convert my dictionaries. – Reman Jun 05 '19 at 21:16
1

In short:

:py3 vim.command("let vimvar = '%s'" % 'pyvalue')

or

:let vimvar = py3eval("'pyvalue'")

Where vimvar is a vim variable and can be b: w: t: g: l: s: a: v: and 'pyvalue' is a python variable.

Tinmarino
  • 3,693
  • 24
  • 33
  • **Note:** this basic approach of printf-style string interpolation `vim.command("let sInVim = '%s'"% s)` is a source of potential bugs. Specifically, this approach will not work as expected if the interpolated value causes [delimiter collision](https://en.wikipedia.org/wiki/Delimiter#Delimiter_collision) on the single-quote character – dreftymac Nov 22 '22 at 19:24