summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2017-01-28 16:06:38 +0100
committerBram Moolenaar <Bram@vim.org>2017-01-28 16:06:38 +0100
commitf42dd3c3901ea0ba38e67a616aea9953cae81b8d (patch)
tree3626ca40161dbfaa5e72a4dbeaa46b24480ddc8c /src
parent0c0590d9827cb07a33c1552cb3558b94bddcb4dc (diff)
patch 8.0.0251: not easy to select Python 2 or 3v8.0.0251
Problem: It is not so easy to write a script that works with both Python 2 and Python 3, even when the Python code works with both. Solution: Add 'pyxversion', :pyx, etc. (Marc Weber, Ken Takata)
Diffstat (limited to 'src')
-rw-r--r--src/Makefile3
-rw-r--r--src/evalfunc.c62
-rw-r--r--src/ex_cmds.h12
-rw-r--r--src/ex_cmds2.c188
-rw-r--r--src/ex_docmd.c5
-rw-r--r--src/if_python.c9
-rw-r--r--src/if_python3.c9
-rw-r--r--src/option.c28
-rw-r--r--src/option.h3
-rw-r--r--src/proto/ex_cmds2.pro4
-rw-r--r--src/testdir/Make_all.mak2
-rw-r--r--src/testdir/pyxfile/py2_magic.py4
-rw-r--r--src/testdir/pyxfile/py2_shebang.py4
-rw-r--r--src/testdir/pyxfile/py3_magic.py4
-rw-r--r--src/testdir/pyxfile/py3_shebang.py4
-rw-r--r--src/testdir/pyxfile/pyx.py2
-rw-r--r--src/testdir/test_pyx2.vim74
-rw-r--r--src/testdir/test_pyx3.vim74
-rw-r--r--src/userfunc.c4
-rw-r--r--src/version.c2
20 files changed, 487 insertions, 10 deletions
diff --git a/src/Makefile b/src/Makefile
index f993c85281..c675c06a5b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2151,6 +2151,8 @@ test_arglist \
test_popup \
test_profile \
test_put \
+ test_pyx2 \
+ test_pyx3 \
test_quickfix \
test_regexp_latin \
test_regexp_utf8 \
@@ -2754,6 +2756,7 @@ shadow: runtime pixmaps
../../testdir/*.vim \
../../testdir/*.py \
../../testdir/python* \
+ ../../testdir/pyxfile \
../../testdir/sautest \
../../testdir/samples \
../../testdir/test83-tags? \
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 99d01c42de..a3441a0cc2 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -289,6 +289,9 @@ static void f_py3eval(typval_T *argvars, typval_T *rettv);
#ifdef FEAT_PYTHON
static void f_pyeval(typval_T *argvars, typval_T *rettv);
#endif
+#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
+static void f_pyxeval(typval_T *argvars, typval_T *rettv);
+#endif
static void f_range(typval_T *argvars, typval_T *rettv);
static void f_readfile(typval_T *argvars, typval_T *rettv);
static void f_reltime(typval_T *argvars, typval_T *rettv);
@@ -716,6 +719,9 @@ static struct fst
#ifdef FEAT_PYTHON
{"pyeval", 1, 1, f_pyeval},
#endif
+#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
+ {"pyxeval", 1, 1, f_pyxeval},
+#endif
{"range", 1, 3, f_range},
{"readfile", 1, 3, f_readfile},
{"reltime", 0, 2, f_reltime},
@@ -5734,15 +5740,13 @@ f_has(typval_T *argvars, typval_T *rettv)
#ifdef FEAT_PERSISTENT_UNDO
"persistent_undo",
#endif
-#ifdef FEAT_PYTHON
-#ifndef DYNAMIC_PYTHON
+#if defined(FEAT_PYTHON) && !defined(DYNAMIC_PYTHON)
"python",
+ "pythonx",
#endif
-#endif
-#ifdef FEAT_PYTHON3
-#ifndef DYNAMIC_PYTHON3
+#if defined(FEAT_PYTHON3) && !defined(DYNAMIC_PYTHON3)
"python3",
-#endif
+ "pythonx",
#endif
#ifdef FEAT_POSTSCRIPT
"postscript",
@@ -5972,17 +5976,30 @@ f_has(typval_T *argvars, typval_T *rettv)
else if (STRICMP(name, "ruby") == 0)
n = ruby_enabled(FALSE);
#endif
-#ifdef FEAT_PYTHON
#ifdef DYNAMIC_PYTHON
else if (STRICMP(name, "python") == 0)
n = python_enabled(FALSE);
#endif
-#endif
-#ifdef FEAT_PYTHON3
#ifdef DYNAMIC_PYTHON3
else if (STRICMP(name, "python3") == 0)
n = python3_enabled(FALSE);
#endif
+#if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3)
+ else if (STRICMP(name, "pythonx") == 0)
+ {
+# if defined(DYNAMIC_PYTHON) && defined(DYNAMIC_PYTHON3)
+ if (p_pyx == 0)
+ n = python3_enabled(FALSE) || python_enabled(FALSE);
+ else if (p_pyx == 3)
+ n = python3_enabled(FALSE);
+ else if (p_pyx == 2)
+ n = python_enabled(FALSE);
+# elif defined(DYNAMIC_PYTHON)
+ n = python_enabled(FALSE);
+# elif defined(DYNAMIC_PYTHON3)
+ n = python3_enabled(FALSE);
+# endif
+ }
#endif
#ifdef DYNAMIC_PERL
else if (STRICMP(name, "perl") == 0)
@@ -8007,6 +8024,9 @@ f_py3eval(typval_T *argvars, typval_T *rettv)
char_u *str;
char_u buf[NUMBUFLEN];
+ if (p_pyx == 0)
+ p_pyx = 3;
+
str = get_tv_string_buf(&argvars[0], buf);
do_py3eval(str, rettv);
}
@@ -8022,11 +8042,35 @@ f_pyeval(typval_T *argvars, typval_T *rettv)
char_u *str;
char_u buf[NUMBUFLEN];
+ if (p_pyx == 0)
+ p_pyx = 2;
+
str = get_tv_string_buf(&argvars[0], buf);
do_pyeval(str, rettv);
}
#endif
+#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
+/*
+ * "pyxeval()" function
+ */
+ static void
+f_pyxeval(typval_T *argvars, typval_T *rettv)
+{
+# if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
+ init_pyxversion();
+ if (p_pyx == 2)
+ f_pyeval(argvars, rettv);
+ else
+ f_py3eval(argvars, rettv);
+# elif defined(FEAT_PYTHON)
+ f_pyeval(argvars, rettv);
+# elif defined(FEAT_PYTHON3)
+ f_py3eval(argvars, rettv);
+# endif
+}
+#endif
+
/*
* "range()" function
*/
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index cb2fadcec0..d70ff6a7b7 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -1132,6 +1132,18 @@ EX(CMD_python3, "python3", ex_py3,
EX(CMD_py3file, "py3file", ex_py3file,
RANGE|FILE1|NEEDARG|CMDWIN,
ADDR_LINES),
+EX(CMD_pyx, "pyx", ex_pyx,
+ RANGE|EXTRA|NEEDARG|CMDWIN,
+ ADDR_LINES),
+EX(CMD_pyxdo, "pyxdo", ex_pyxdo,
+ RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN,
+ ADDR_LINES),
+EX(CMD_pythonx, "pythonx", ex_pyx,
+ RANGE|EXTRA|NEEDARG|CMDWIN,
+ ADDR_LINES),
+EX(CMD_pyxfile, "pyxfile", ex_pyxfile,
+ RANGE|FILE1|NEEDARG|CMDWIN,
+ ADDR_LINES),
EX(CMD_quit, "quit", ex_quit,
BANG|RANGE|COUNT|NOTADR|TRLBAR|CMDWIN,
ADDR_WINDOWS),
diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c
index 7a6047a5bb..59dc4d5975 100644
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -3675,6 +3675,194 @@ ex_options(
}
#endif
+#if defined(FEAT_PYTHON3) || defined(FEAT_PYTHON) || defined(PROTO)
+
+# if (defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)) || defined(PROTO)
+/*
+ * Detect Python 3 or 2, and initialize 'pyxversion'.
+ */
+ void
+init_pyxversion(void)
+{
+ if (p_pyx == 0)
+ {
+ if (python3_enabled(FALSE))
+ p_pyx = 3;
+ else if (python_enabled(FALSE))
+ p_pyx = 2;
+ }
+}
+# endif
+
+/*
+ * Does a file contain one of the following strings at the beginning of any
+ * line?
+ * "#!(any string)python2" => returns 2
+ * "#!(any string)python3" => returns 3
+ * "# requires python 2.x" => returns 2
+ * "# requires python 3.x" => returns 3
+ * otherwise return 0.
+ */
+ static int
+requires_py_version(char_u *filename)
+{
+ FILE *file;
+ int requires_py_version = 0;
+ int i, lines;
+
+ lines = (int)p_mls;
+ if (lines < 0)
+ lines = 5;
+
+ file = mch_fopen((char *)filename, "r");
+ if (file != NULL)
+ {
+ for (i = 0; i < lines; i++)
+ {
+ if (vim_fgets(IObuff, IOSIZE, file))
+ break;
+ if (i == 0 && IObuff[0] == '#' && IObuff[1] == '!')
+ {
+ /* Check shebang. */
+ if (strstr((char *)IObuff + 2, "python2") != NULL)
+ {
+ requires_py_version = 2;
+ break;
+ }
+ if (strstr((char *)IObuff + 2, "python3") != NULL)
+ {
+ requires_py_version = 3;
+ break;
+ }
+ }
+ IObuff[21] = '\0';
+ if (STRCMP("# requires python 2.x", IObuff) == 0)
+ {
+ requires_py_version = 2;
+ break;
+ }
+ if (STRCMP("# requires python 3.x", IObuff) == 0)
+ {
+ requires_py_version = 3;
+ break;
+ }
+ }
+ fclose(file);
+ }
+ return requires_py_version;
+}
+
+
+/*
+ * Source a python file using the requested python version.
+ */
+ static void
+source_pyx_file(exarg_T *eap, char_u *fname)
+{
+ exarg_T ex;
+ int v = requires_py_version(fname);
+
+# if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
+ init_pyxversion();
+# endif
+ if (v == 0)
+ {
+# if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
+ /* user didn't choose a preference, 'pyx' is used */
+ v = p_pyx;
+# elif defined(FEAT_PYTHON)
+ v = 2;
+# elif defined(FEAT_PYTHON3)
+ v = 3;
+# endif
+ }
+
+ /*
+ * now source, if required python version is not supported show
+ * unobtrusive message.
+ */
+ if (eap == NULL)
+ vim_memset(&ex, 0, sizeof(ex));
+ else
+ ex = *eap;
+ ex.arg = fname;
+ ex.cmd = (char_u *)(v == 2 ? "pyfile" : "pyfile3");
+
+ if (v == 2)
+ {
+# ifdef FEAT_PYTHON
+ ex_pyfile(&ex);
+# else
+ vim_snprintf((char *)IObuff, IOSIZE,
+ _("W20: Required python version 2.x not supported, ignoring file: %s"),
+ fname);
+ MSG(IObuff);
+# endif
+ return;
+ }
+ else
+ {
+# ifdef FEAT_PYTHON3
+ ex_py3file(&ex);
+# else
+ vim_snprintf((char *)IObuff, IOSIZE,
+ _("W21: Required python version 3.x not supported, ignoring file: %s"),
+ fname);
+ MSG(IObuff);
+# endif
+ return;
+ }
+}
+
+/*
+ * ":pyxfile {fname}"
+ */
+ void
+ex_pyxfile(exarg_T *eap)
+{
+ source_pyx_file(eap, eap->arg);
+}
+
+/*
+ * ":pyx"
+ */
+ void
+ex_pyx(exarg_T *eap)
+{
+# if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
+ init_pyxversion();
+ if (p_pyx == 2)
+ ex_python(eap);
+ else
+ ex_py3(eap);
+# elif defined(FEAT_PYTHON)
+ ex_python(eap);
+# elif defined(FEAT_PYTHON3)
+ ex_py3(eap);
+# endif
+}
+
+/*
+ * ":pyxdo"
+ */
+ void
+ex_pyxdo(exarg_T *eap)
+{
+# if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
+ init_pyxversion();
+ if (p_pyx == 2)
+ ex_pydo(eap);
+ else
+ ex_py3do(eap);
+# elif defined(FEAT_PYTHON)
+ ex_pydo(eap);
+# elif defined(FEAT_PYTHON3)
+ ex_py3do(eap);
+# endif
+}
+
+#endif
+
/*
* ":source {fname}"
*/
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index cff41cf148..bfb4c88d46 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -288,6 +288,11 @@ static void ex_popup(exarg_T *eap);
# define ex_py3do ex_ni
# define ex_py3file ex_ni
#endif
+#if !defined(FEAT_PYTHON) && !defined(FEAT_PYTHON3)
+# define ex_pyx ex_script_ni
+# define ex_pyxdo ex_ni
+# define ex_pyxfile ex_ni
+#endif
#ifndef FEAT_TCL
# define ex_tcl ex_script_ni
# define ex_tcldo ex_ni
diff --git a/src/if_python.c b/src/if_python.c
index 622634da75..6b2ce56ef2 100644
--- a/src/if_python.c
+++ b/src/if_python.c
@@ -1114,6 +1114,9 @@ ex_python(exarg_T *eap)
{
char_u *script;
+ if (p_pyx == 0)
+ p_pyx = 2;
+
script = script_get(eap, eap->arg);
if (!eap->skip)
{
@@ -1137,6 +1140,9 @@ ex_pyfile(exarg_T *eap)
const char *file = (char *)eap->arg;
char *p;
+ if (p_pyx == 0)
+ p_pyx = 2;
+
/* Have to do it like this. PyRun_SimpleFile requires you to pass a
* stdio file pointer, but Vim and the Python DLL are compiled with
* different options under Windows, meaning that stdio pointers aren't
@@ -1175,6 +1181,9 @@ ex_pyfile(exarg_T *eap)
void
ex_pydo(exarg_T *eap)
{
+ if (p_pyx == 0)
+ p_pyx = 2;
+
DoPyCommand((char *)eap->arg,
(rangeinitializer) init_range_cmd,
(runner)run_do,
diff --git a/src/if_python3.c b/src/if_python3.c
index 53a1313488..d68ab85a86 100644
--- a/src/if_python3.c
+++ b/src/if_python3.c
@@ -1004,6 +1004,9 @@ ex_py3(exarg_T *eap)
{
char_u *script;
+ if (p_pyx == 0)
+ p_pyx = 3;
+
script = script_get(eap, eap->arg);
if (!eap->skip)
{
@@ -1028,6 +1031,9 @@ ex_py3file(exarg_T *eap)
char *p;
int i;
+ if (p_pyx == 0)
+ p_pyx = 3;
+
/* Have to do it like this. PyRun_SimpleFile requires you to pass a
* stdio file pointer, but Vim and the Python DLL are compiled with
* different options under Windows, meaning that stdio pointers aren't
@@ -1080,6 +1086,9 @@ ex_py3file(exarg_T *eap)
void
ex_py3do(exarg_T *eap)
{
+ if (p_pyx == 0)
+ p_pyx = 3;
+
DoPyCommand((char *)eap->arg,
(rangeinitializer)init_range_cmd,
(runner)run_do,
diff --git a/src/option.c b/src/option.c
index a987a4cdf8..b90d5fdfa2 100644
--- a/src/option.c
+++ b/src/option.c
@@ -479,6 +479,17 @@ struct vimoption
# define HIGHLIGHT_INIT "8:SpecialKey,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search,m:MoreMsg,M:ModeMsg,n:LineNr,N:CursorLineNr,r:Question,s:StatusLine,S:StatusLineNC,t:Title,v:Visual,w:WarningMsg,W:WildMenu,>:SignColumn,*:TabLine,#:TabLineSel,_:TabLineFill"
#endif
+/* Default python version for pyx* commands */
+#if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
+# define DEFAULT_PYTHON_VER 0
+#elif defined(FEAT_PYTHON3)
+# define DEFAULT_PYTHON_VER 3
+#elif defined(FEAT_PYTHON)
+# define DEFAULT_PYTHON_VER 2
+#else
+# define DEFAULT_PYTHON_VER 0
+#endif
+
/*
* options[] is initialized here.
* The order of the options MUST be alphabetic for ":set all" and findoption().
@@ -2143,6 +2154,14 @@ static struct vimoption options[] =
{(char_u *)DYNAMIC_PYTHON_DLL, (char_u *)0L}
SCRIPTID_INIT},
#endif
+ {"pyxversion", "pyx", P_NUM|P_VI_DEF|P_SECURE,
+#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
+ (char_u *)&p_pyx, PV_NONE,
+#else
+ (char_u *)NULL, PV_NONE,
+#endif
+ {(char_u *)DEFAULT_PYTHON_VER, (char_u *)0L}
+ SCRIPTID_INIT},
{"quoteescape", "qe", P_STRING|P_ALLOCED|P_VI_DEF,
#ifdef FEAT_TEXTOBJ
(char_u *)&p_qe, PV_QE,
@@ -8826,6 +8845,15 @@ set_num_option(
mzvim_reset_timer();
#endif
+#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
+ /* 'pyxversion' */
+ else if (pp == &p_pyx)
+ {
+ if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3)
+ errmsg = e_invarg;
+ }
+#endif
+
/* sync undo before 'undolevels' changes */
else if (pp == &p_ul)
{
diff --git a/src/option.h b/src/option.h
index 0ad2fef64b..62e66cd3da 100644
--- a/src/option.h
+++ b/src/option.h
@@ -694,6 +694,9 @@ EXTERN char_u *p_py3dll; /* 'pythonthreedll' */
#if defined(DYNAMIC_PYTHON)
EXTERN char_u *p_pydll; /* 'pythondll' */
#endif
+#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
+EXTERN long p_pyx; /* 'pyxversion' */
+#endif
#ifdef FEAT_RELTIME
EXTERN long p_rdt; /* 'redrawtime' */
#endif
diff --git a/src/proto/ex_cmds2.pro b/src/proto/ex_cmds2.pro
index c1325a41e3..4a40f0a767 100644
--- a/src/proto/ex_cmds2.pro
+++ b/src/proto/ex_cmds2.pro
@@ -75,6 +75,10 @@ int do_in_runtimepath(char_u *name, int flags, void (*callback)(char_u *fname, v
void ex_packloadall(exarg_T *eap);
void ex_packadd(exarg_T *eap);
void ex_options(exarg_T *eap);
+void init_pyxversion(void);
+void ex_pyxfile(exarg_T *eap);
+void ex_pyx(exarg_T *eap);
+void ex_pyxdo(exarg_T *eap);
void ex_source(exarg_T *eap);
linenr_T *source_breakpoint(void *cookie);
int *source_dbg_tick(void *cookie);
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index c3db6fe303..12abb78bae 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -176,6 +176,8 @@ NEW_TESTS = test_arglist.res \
test_packadd.res \
test_perl.res \
test_profile.res \
+ test_pyx2.res \
+ test_pyx3.res \
test_quickfix.res \
test_retab.res \
test_ruby.res \
diff --git a/src/testdir/pyxfile/py2_magic.py b/src/testdir/pyxfile/py2_magic.py
new file mode 100644
index 0000000000..819892fd16
--- /dev/null
+++ b/src/testdir/pyxfile/py2_magic.py
@@ -0,0 +1,4 @@
+# requires python 2.x
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/py2_shebang.py b/src/testdir/pyxfile/py2_shebang.py
new file mode 100644
index 0000000000..13bfc491ec
--- /dev/null
+++ b/src/testdir/pyxfile/py2_shebang.py
@@ -0,0 +1,4 @@
+#!/usr/bin/python2
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/py3_magic.py b/src/testdir/pyxfile/py3_magic.py
new file mode 100644
index 0000000000..d4b7ee0071
--- /dev/null
+++ b/src/testdir/pyxfile/py3_magic.py
@@ -0,0 +1,4 @@
+# requires python 3.x
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/py3_shebang.py b/src/testdir/pyxfile/py3_shebang.py
new file mode 100644
index 0000000000..ec05808ca4
--- /dev/null
+++ b/src/testdir/pyxfile/py3_shebang.py
@@ -0,0 +1,4 @@
+#!/usr/bin/python3
+
+import sys
+print(sys.version)
diff --git a/src/testdir/pyxfile/pyx.py b/src/testdir/pyxfile/pyx.py
new file mode 100644
index 0000000000..261a6512c0
--- /dev/null
+++ b/src/testdir/pyxfile/pyx.py
@@ -0,0 +1,2 @@
+import sys
+print(sys.version)
diff --git a/src/testdir/test_pyx2.vim b/src/testdir/test_pyx2.vim
new file mode 100644
index 0000000000..50e57c3bfb
--- /dev/null
+++ b/src/testdir/test_pyx2.vim
@@ -0,0 +1,74 @@
+" Test for pyx* commands and functions with Python 2.
+
+set pyx=2
+if !has('python')
+ finish
+endif
+
+let s:py2pattern = '^2\.[0-7]\.\d\+'
+let s:py3pattern = '^3\.\d\+\.\d\+'
+
+
+func Test_has_pythonx()
+ call assert_true(has('pythonx'))
+endfunc
+
+
+func Test_pyx()
+ redir => var
+ pyx << EOF
+import sys
+print(sys.version)
+EOF
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+endfunc
+
+
+func Test_pyxdo()
+ pyx import sys
+ enew
+ pyxdo return sys.version.split("\n")[0]
+ call assert_match(s:py2pattern, split(getline('.'))[0])
+endfunc
+
+
+func Test_pyxeval()
+ pyx import sys
+ call assert_match(s:py2pattern, split(pyxeval('sys.version'))[0])
+endfunc
+
+
+func Test_pyxfile()
+ " No special comments nor shebangs
+ redir => var
+ pyxfile pyxfile/pyx.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ " Python 2 special comment
+ redir => var
+ pyxfile pyxfile/py2_magic.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ " Python 2 shebang
+ redir => var
+ pyxfile pyxfile/py2_shebang.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ if has('python3')
+ " Python 3 special comment
+ redir => var
+ pyxfile pyxfile/py3_magic.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ " Python 3 shebang
+ redir => var
+ pyxfile pyxfile/py3_shebang.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+ endif
+endfunc
diff --git a/src/testdir/test_pyx3.vim b/src/testdir/test_pyx3.vim
new file mode 100644
index 0000000000..64546b4688
--- /dev/null
+++ b/src/testdir/test_pyx3.vim
@@ -0,0 +1,74 @@
+" Test for pyx* commands and functions with Python 3.
+
+set pyx=3
+if !has('python3')
+ finish
+endif
+
+let s:py2pattern = '^2\.[0-7]\.\d\+'
+let s:py3pattern = '^3\.\d\+\.\d\+'
+
+
+func Test_has_pythonx()
+ call assert_true(has('pythonx'))
+endfunc
+
+
+func Test_pyx()
+ redir => var
+ pyx << EOF
+import sys
+print(sys.version)
+EOF
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+endfunc
+
+
+func Test_pyxdo()
+ pyx import sys
+ enew
+ pyxdo return sys.version.split("\n")[0]
+ call assert_match(s:py3pattern, split(getline('.'))[0])
+endfunc
+
+
+func Test_pyxeval()
+ pyx import sys
+ call assert_match(s:py3pattern, split(pyxeval('sys.version'))[0])
+endfunc
+
+
+func Test_pyxfile()
+ " No special comments nor shebangs
+ redir => var
+ pyxfile pyxfile/pyx.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ " Python 3 special comment
+ redir => var
+ pyxfile pyxfile/py3_magic.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ " Python 3 shebang
+ redir => var
+ pyxfile pyxfile/py3_shebang.py
+ redir END
+ call assert_match(s:py3pattern, split(var)[0])
+
+ if has('python')
+ " Python 2 special comment
+ redir => var
+ pyxfile pyxfile/py2_magic.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+
+ " Python 2 shebang
+ redir => var
+ pyxfile pyxfile/py2_shebang.py
+ redir END
+ call assert_match(s:py2pattern, split(var)[0])
+ endif
+endfunc
diff --git a/src/userfunc.c b/src/userfunc.c
index 372c9bb860..6a6cc06893 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -2102,7 +2102,9 @@ ex_function(exarg_T *eap)
arg = skipwhite(skiptowhite(p));
if (arg[0] == '<' && arg[1] =='<'
&& ((p[0] == 'p' && p[1] == 'y'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 't'))
+ && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
+ || ((p[2] == '3' || p[2] == 'x')
+ && !ASCII_ISALPHA(p[3]))))
|| (p[0] == 'p' && p[1] == 'e'
&& (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
|| (p[0] == 't' && p[1] == 'c'
diff --git a/src/version.c b/src/version.c
index 94b47bb813..3818647586 100644
--- a/src/version.c
+++ b/src/version.c
@@ -765,6 +765,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 251,
+/**/
250,
/**/
249,