Patch 8.2.1054
Problem:    Not so easy to pass a lua function to Vim.
Solution:   Convert a Lua function and closure to a Vim funcref. (Prabir
            Shrestha, closes #6246)
Files:      runtime/doc/if_lua.txt, src/if_lua.c, src/proto/userfunc.pro,
            src/structs.h, src/testdir/test_lua.vim, src/userfunc.c


*** ../vim-8.2.1053/runtime/doc/if_lua.txt      2020-05-17 14:32:30.584490790 
+0200
--- runtime/doc/if_lua.txt      2020-06-25 19:13:56.712302695 +0200
***************
*** 333,338 ****
--- 333,346 ----
        :lua l = d.len -- assign d as 'self'
        :lua print(l())
  <
+ Lua functions and closures are automatically converted to a Vim |Funcref| and
+ can be accessed in Vim scripts.  Example:
+ >
+       lua <<EOF
+       vim.fn.timer_start(1000, function(timer)
+           print('timer callback')
+       end)
+       EOF
  
  ==============================================================================
  7. Buffer userdata                                    *lua-buffer*
*** ../vim-8.2.1053/src/if_lua.c        2020-05-31 14:07:02.688752446 +0200
--- src/if_lua.c        2020-06-25 19:17:40.263764414 +0200
***************
*** 35,40 ****
--- 35,47 ----
  } luaV_Funcref;
  typedef void (*msgfunc_T)(char_u *);
  
+ typedef struct {
+     int lua_funcref;    // ref to a lua func
+     int lua_tableref;   // ref to a lua table if metatable else LUA_NOREF. 
used
+                       // for __call
+     lua_State *L;
+ } luaV_CFuncState;
+ 
  static const char LUAVIM_DICT[] = "dict";
  static const char LUAVIM_LIST[] = "list";
  static const char LUAVIM_BLOB[] = "blob";
***************
*** 45,50 ****
--- 52,59 ----
  static const char LUAVIM_LUAEVAL[] = "luaV_luaeval";
  static const char LUAVIM_SETREF[] = "luaV_setref";
  
+ static const char LUA___CALL[] = "__call";
+ 
  // most functions are closures with a cache table as first upvalue;
  // get/setudata manage references to vim userdata in cache table through
  // object pointers (light userdata)
***************
*** 64,70 ****
  #define luaV_emsg(L) luaV_msgfunc((L), (msgfunc_T) emsg)
  #define luaV_checktypval(L, a, v, msg) \
      do { \
!         if (luaV_totypval(L, a, v) == FAIL) \
            luaL_error(L, msg ": cannot convert value"); \
      } while (0)
  
--- 73,79 ----
  #define luaV_emsg(L) luaV_msgfunc((L), (msgfunc_T) emsg)
  #define luaV_checktypval(L, a, v, msg) \
      do { \
!       if (luaV_totypval(L, a, v) == FAIL) \
            luaL_error(L, msg ": cannot convert value"); \
      } while (0)
  
***************
*** 72,77 ****
--- 81,88 ----
  static luaV_Dict *luaV_pushdict(lua_State *L, dict_T *dic);
  static luaV_Blob *luaV_pushblob(lua_State *L, blob_T *blo);
  static luaV_Funcref *luaV_pushfuncref(lua_State *L, char_u *name);
+ static int luaV_call_lua_func(int argcount, typval_T *argvars, typval_T 
*rettv, void *state);
+ static void luaV_call_lua_func_free(void *state);
  
  #if LUA_VERSION_NUM <= 501
  #define luaV_openlib(L, l, n) luaL_openlib(L, NULL, l, n)
***************
*** 591,596 ****
--- 602,646 ----
            tv->vval.v_number = (varnumber_T) lua_tointeger(L, pos);
  #endif
            break;
+       case LUA_TFUNCTION:
+       {
+           char_u *name;
+           lua_pushvalue(L, pos);
+           luaV_CFuncState *state = ALLOC_CLEAR_ONE(luaV_CFuncState);
+           state->lua_funcref = luaL_ref(L, LUA_REGISTRYINDEX);
+           state->L = L;
+           state->lua_tableref = LUA_NOREF;
+           name = register_cfunc(&luaV_call_lua_func,
+                                             &luaV_call_lua_func_free, state);
+           tv->v_type = VAR_FUNC;
+           tv->vval.v_string = vim_strsave(name);
+           break;
+       }
+       case LUA_TTABLE:
+       {
+           lua_pushvalue(L, pos);
+           int lua_tableref = luaL_ref(L, LUA_REGISTRYINDEX);
+           if (lua_getmetatable(L, pos)) {
+               lua_getfield(L, -1, LUA___CALL);
+               if (lua_isfunction(L, -1)) {
+                   char_u *name;
+                   int lua_funcref = luaL_ref(L, LUA_REGISTRYINDEX);
+                   luaV_CFuncState *state = ALLOC_CLEAR_ONE(luaV_CFuncState);
+                   state->lua_funcref = lua_funcref;
+                   state->L = L;
+                   state->lua_tableref = lua_tableref;
+                   name = register_cfunc(&luaV_call_lua_func,
+                                             &luaV_call_lua_func_free, state);
+                   tv->v_type = VAR_FUNC;
+                   tv->vval.v_string = vim_strsave(name);
+                   break;
+               }
+           }
+           tv->v_type = VAR_NUMBER;
+           tv->vval.v_number = 0;
+           status = FAIL;
+           break;
+       }
        case LUA_TUSERDATA:
        {
            void *p = lua_touserdata(L, pos);
***************
*** 2415,2418 ****
--- 2465,2517 ----
      }
  }
  
+ /*
+  * Native C function callback
+  */
+     static int
+ luaV_call_lua_func(
+       int      argcount,
+       typval_T *argvars,
+       typval_T *rettv,
+       void     *state)
+ {
+     int i;
+     int luaargcount = argcount;
+     luaV_CFuncState *funcstate = (luaV_CFuncState*)state;
+     lua_rawgeti(funcstate->L, LUA_REGISTRYINDEX, funcstate->lua_funcref);
+ 
+     if (funcstate->lua_tableref != LUA_NOREF)
+     {
+       // First arg for metatable __call method is a table
+       luaargcount += 1;
+       lua_rawgeti(funcstate->L, LUA_REGISTRYINDEX, funcstate->lua_tableref);
+     }
+ 
+     for (i = 0; i < argcount; ++i)
+       luaV_pushtypval(funcstate->L, &argvars[i]);
+ 
+     if (lua_pcall(funcstate->L, luaargcount, 1, 0))
+     {
+       luaV_emsg(funcstate->L);
+       return FCERR_OTHER;
+     }
+ 
+     luaV_checktypval(funcstate->L, -1, rettv, "get return value");
+     return FCERR_NONE;
+ }
+ 
+ /*
+  * Free up any lua references held by the func state.
+  */
+     static void
+ luaV_call_lua_func_free(void *state)
+ {
+     luaV_CFuncState *funcstate = (luaV_CFuncState*)state;
+     luaL_unref(L, LUA_REGISTRYINDEX, funcstate->lua_funcref);
+     funcstate->L = NULL;
+     if (funcstate->lua_tableref != LUA_NOREF)
+       luaL_unref(L, LUA_REGISTRYINDEX, funcstate->lua_tableref);
+     VIM_CLEAR(funcstate);
+ }
+ 
  #endif
*** ../vim-8.2.1053/src/proto/userfunc.pro      2020-05-24 23:00:06.440196016 
+0200
--- src/proto/userfunc.pro      2020-06-25 19:09:32.524908200 +0200
***************
*** 4,9 ****
--- 4,10 ----
  int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, 
garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T 
*eap, char_u **line_to_free);
  char_u *get_lambda_name(void);
  int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate);
+ char_u *register_cfunc(cfunc_T cb, cfunc_free_T free_cb, void *state);
  char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int 
no_autoload);
  void emsg_funcname(char *ermsg, char_u *name);
  int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, 
funcexe_T *funcexe);
*** ../vim-8.2.1053/src/structs.h       2020-06-24 20:33:59.561106332 +0200
--- src/structs.h       2020-06-25 19:20:10.439298026 +0200
***************
*** 1529,1534 ****
--- 1529,1537 ----
      char      bv_lock;        // zero, VAR_LOCKED, VAR_FIXED
  };
  
+ typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void 
*state);
+ typedef void (*cfunc_free_T)(void *state);
+ 
  #if defined(FEAT_EVAL) || defined(PROTO)
  typedef struct funccall_S funccall_T;
  
***************
*** 1562,1567 ****
--- 1565,1575 ----
      char_u    *uf_va_name;    // name from "...name" or NULL
      type_T    *uf_va_type;    // type from "...name: type" or NULL
      type_T    *uf_func_type;  // type of the function, &t_func_any if unknown
+ # if defined(FEAT_LUA)
+     cfunc_T     uf_cb;                // callback function for cfunc
+     cfunc_free_T uf_cb_free;    // callback function to free cfunc
+     void        *uf_cb_state;   // state of uf_cb
+ # endif
  
      garray_T  uf_lines;       // function lines
  # ifdef FEAT_PROFILE
***************
*** 1607,1612 ****
--- 1615,1621 ----
  #define FC_EXPORT   0x100     // "export def Func()"
  #define FC_NOARGS   0x200     // no a: variables in lambda
  #define FC_VIM9           0x400       // defined in vim9 script file
+ #define FC_CFUNC    0x800     // defined as Lua C func
  
  #define MAX_FUNC_ARGS 20      // maximum number of function arguments
  #define VAR_SHORT_LEN 20      // short variable name length
*** ../vim-8.2.1053/src/testdir/test_lua.vim    2020-05-31 14:07:02.688752446 
+0200
--- src/testdir/test_lua.vim    2020-06-25 19:09:32.528908191 +0200
***************
*** 541,546 ****
--- 541,575 ----
    call assert_equal("hello from lua", 
luaeval("require('testluaplugin').hello()"))
  endfunc
  
+ func Vim_func_call_lua_callback(Concat, Cb)
+   let l:message = a:Concat("hello", "vim")
+   call a:Cb(l:message)
+ endfunc
+ 
+ func Test_pass_lua_callback_to_vim_from_lua()
+   lua pass_lua_callback_to_vim_from_lua_result = ""
+   call assert_equal("", luaeval("pass_lua_callback_to_vim_from_lua_result"))
+   lua <<EOF
+   vim.funcref('Vim_func_call_lua_callback')(
+     function(greeting, message)
+       return greeting .. " " .. message
+     end,
+     function(message)
+       pass_lua_callback_to_vim_from_lua_result = message
+     end)
+ EOF
+   call assert_equal("hello vim", 
luaeval("pass_lua_callback_to_vim_from_lua_result"))
+ endfunc
+ 
+ func Vim_func_call_metatable_lua_callback(Greet)
+   return a:Greet("world")
+ endfunc
+ 
+ func Test_pass_lua_metatable_callback_to_vim_from_lua()
+   let result = 
luaeval("vim.funcref('Vim_func_call_metatable_lua_callback')(setmetatable({ 
space = ' '}, { __call = function(tbl, msg) return 'hello' .. tbl.space .. msg  
end }) )")
+   call assert_equal("hello world", result)
+ endfunc
+ 
  " Test vim.line()
  func Test_lua_line()
    new
*** ../vim-8.2.1053/src/userfunc.c      2020-06-24 20:33:59.569106308 +0200
--- src/userfunc.c      2020-06-25 19:22:31.434268094 +0200
***************
*** 341,346 ****
--- 341,391 ----
      return name;
  }
  
+ #if defined(FEAT_LUA) || defined(PROTO)
+ /*
+  * Registers a native C callback which can be called from Vim script.
+  * Returns the name of the Vim script function.
+  */
+     char_u *
+ register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
+ {
+     char_u    *name = get_lambda_name();
+     ufunc_T   *fp = NULL;
+     garray_T  newargs;
+     garray_T  newlines;
+ 
+     ga_init(&newargs);
+     ga_init(&newlines);
+ 
+     fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+     if (fp == NULL)
+         goto errret;
+ 
+     fp->uf_dfunc_idx = UF_NOT_COMPILED;
+     fp->uf_refcount = 1;
+     fp->uf_varargs = TRUE;
+     fp->uf_flags = FC_CFUNC;
+     fp->uf_calls = 0;
+     fp->uf_script_ctx = current_sctx;
+     fp->uf_lines = newlines;
+     fp->uf_args = newargs;
+     fp->uf_cb = cb;
+     fp->uf_cb_free = cb_free;
+     fp->uf_cb_state = state;
+ 
+     set_ufunc_name(fp, name);
+     hash_add(&func_hashtab, UF2HIKEY(fp));
+ 
+     return name;
+ 
+ errret:
+     ga_clear_strings(&newargs);
+     ga_clear_strings(&newlines);
+     vim_free(fp);
+     return NULL;
+ }
+ #endif
+ 
  /*
   * Parse a lambda expression and get a Funcref from "*arg".
   * Return OK or FAIL.  Returns NOTDONE for dict or {expr}.
***************
*** 1027,1032 ****
--- 1072,1088 ----
        vim_free(((type_T **)fp->uf_type_list.ga_data)
                                                  [--fp->uf_type_list.ga_len]);
      ga_clear(&fp->uf_type_list);
+ 
+ #ifdef FEAT_LUA
+     if (fp->uf_cb_free != NULL)
+     {
+       fp->uf_cb_free(fp->uf_cb_state);
+       fp->uf_cb_free = NULL;
+     }
+ 
+     fp->uf_cb_state = NULL;
+     fp->uf_cb = NULL;
+ #endif
  #ifdef FEAT_PROFILE
      VIM_CLEAR(fp->uf_tml_count);
      VIM_CLEAR(fp->uf_tml_total);
***************
*** 1973,1978 ****
--- 2029,2042 ----
  
            if (fp != NULL && (fp->uf_flags & FC_DELETED))
                error = FCERR_DELETED;
+ #ifdef FEAT_LUA
+           else if (fp != NULL && (fp->uf_flags & FC_CFUNC))
+           {
+               cfunc_T cb = fp->uf_cb;
+ 
+               error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state);
+           }
+ #endif
            else if (fp != NULL)
            {
                if (funcexe->argv_func != NULL)
*** ../vim-8.2.1053/src/version.c       2020-06-25 19:01:32.989844093 +0200
--- src/version.c       2020-06-25 19:11:30.652642598 +0200
***************
*** 756,757 ****
--- 756,759 ----
  {   /* Add new patch number below this line */
+ /**/
+     1054,
  /**/

-- 
There is no right or wrong, there is only your personal opinion.
                 (Bram Moolenaar)

 /// Bram Moolenaar -- b...@moolenaar.net -- http://www.Moolenaar.net   \\\
///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\  an exciting new programming language -- http://www.Zimbu.org        ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202006251728.05PHSNZo2269803%40masaka.moolenaar.net.

Raspunde prin e-mail lui