Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add vim.create_command for defining commands that callback Lua directly #11613

Closed
wants to merge 2 commits into from

Conversation

norcalli
Copy link

@norcalli norcalli commented Dec 25, 2019

Functioning example of vim.create_command(name, callback, force, options) which allows you to define an ex command which is backed by a Lua callback.

callback(arg: string, options: table) is the current signature, where arg is the argument passed to the command directly without any kind of tokenization or further parsing. The options are things like whether bang was specified or a range or whatnot.

options in the original will be things like bang or range, etc.

The only things to bikeshed are the exact design and I have to read more on the existing ucmd interface and all of the options available, because it's quite thick.

I think that passing the eap->arg as-is to the callback isn't a bad idea as compared to supporting options like <f-args> or <q-args> because the expectation should be that achieving those things from Lua should be doable and ergonomic. We already have vim.split and vim.gsplit which makes it possible to tokenize the argument as you please.

TODO:

  • Validate the command name as being user only
  • Add support for buffer local commands
  • Finish definitions of all other options.
  • Add tests
  • Add printing representation for the commands which display commands.

@norcalli norcalli changed the title WIP Groundwork for vim.create_command [WIP] Add vim.create_command for defining commands that callback Lua directly Dec 25, 2019
@norcalli norcalli added WIP lua stdlib labels Dec 25, 2019
XFREE_CLEAR(cmd->uc_rep);
XFREE_CLEAR(cmd->uc_compl_arg);
if (cmd->uc_argt & EX_LUA_CB) {
lua_State* L = executor_enter_lua();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use executor_free_luaref()

if (eap->forceit == 1) {
lua_pushliteral(L, "bang");
lua_pushboolean(L, eap->forceit == 1);
lua_rawset(L, -3);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use lua_setfield() here (it invokes metamethods, but is safe for a freshly created table)

Copy link
Member

@justinmk justinmk Dec 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not put this logic in lua/executor.c instead of exposing executor_enter_lua ?

lua_do_ucmd(eap);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on how many places I expected to need lua. With bfredl's recommendation for using executor_free_luaref(), I would only need to use executor_enter_lua() once now, which means I could replace it with a single function.

Architecturally, I don't find exposing the ability to do arbitrary Lua elsewhere to be terrible as long as it's used responsibly if it was the choice between that and a bunch of little utility functions.

In this case I think it's fine, but I'm also looking at adding Lua functions for keymappings and autocmds (I know there's an existing PR out there which I'm hoping to pick up). So I'll say for sure after spending more time with that.

But good point, nonetheless.

Copy link
Member

@justinmk justinmk Dec 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, a "bunch of functions" in lua/executor.c seems like a better tradeoff than managing Lua state all over the project. That keeps lua/executor.c encapsulated.

lua_pushboolean(L, eap->forceit == 1);
lua_rawset(L, -3);
}
lua_pcall(L, 2, 0, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the `nlua_error" pattern can be used here:

if (lua_pcall(...)) {
  nlua_error(L, "E5108: Error executing lua: %.*s");
}

@@ -91,6 +91,7 @@ typedef struct ucmd {
int uc_addr_type; // The command's address type
sctx_T uc_script_ctx; // SCTX where the command was defined
char_u *uc_compl_arg; // completion argument if any
LuaRef lua_cb; // A lua callback
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Articles are generally noise, and steal title-case from more important words (though "Lua" should be capitalized usually anyways).

Suggested change
LuaRef lua_cb; // A lua callback
LuaRef lua_cb; // Lua callback

see :help dev-doc

}
}

void define_lua_command(const char *name,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the module common prefix.

Suggested change
void define_lua_command(const char *name,
void uc_define_lua_command(const char *name,

@justinmk
Copy link
Member

vim.create_command(name, callback, force, options)

Can we call it vim.new_cmd or vim.def_cmd? "We need a less-ceremonious/enterprisey name than "create", it is starting to appear in more and more places.

I think that passing the eap->arg as-is to the callback isn't a bad idea as compared to supporting options like <f-args> or <q-args> because the expectation should be that achieving those things from Lua should be doable and ergonomic.

Agreed.

We already have vim.split and vim.gsplit which makes it possible to tokenize the argument as you please.

Vimscript :command makes the plugin author choose via <f-args> et al., so we don't really have a choice.

@noahfrederick
Copy link
Contributor

vim.create_command(name, callback, force, options)

I would advocate for defaulting force to true and allowing it to be set in options instead:

vim.create_command(name, callback, {force = true})

I don't know about others, by I've never found guarding against overwriting a command by emitting an error to be a useful feature as a user or a plugin author. So what motivates this suggestion is:

  1. Simplifies defining a command by making the argument optional and providing a sensible default.
  2. Usage is self-documenting, unlike a boolean argument.

@rr-
Copy link

rr- commented Jul 5, 2021

This is good but will this work for Python? @justinmk

@gpanders
Copy link
Member

Superseded by #16752.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lua stdlib
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants