Skip to content

Commit

Permalink
Add lua C bindings for xdiff
Browse files Browse the repository at this point in the history
  • Loading branch information
lewis6991 committed Jul 26, 2021
1 parent 2fcb7cc commit 215404e
Show file tree
Hide file tree
Showing 5 changed files with 401 additions and 0 deletions.
41 changes: 41 additions & 0 deletions runtime/doc/lua.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1581,4 +1581,45 @@ uri_to_fname({uri}) *vim.uri_to_fname()*
Return: ~
Filename

==============================================================================
Lua module: xdiff *lua-xdiff*

xdl_diff({a}, {b}, {opts}) *vim.xdl_diff()*
Run diff on strings {a} and {b}.

Parameters: ~
{a} First string to compare
{b} Second string to compare
{opts} Optional parameters:
`hunk_func`: Lua callback invoked for each hunk
in the diff. Return a negative number to cancel
the callback for any remaining hunks.
Args:
• start_a
• count_a
• start_b
• count_b
`algorithm`: Diff algorithm to use.
• "myers" the default algorithm
• "minimal" spend extra time to generate the
smallest possible diff
• "patience" patience diff algorithm
• "histogram" histogram diff algorithm
`ctxlen`: Integer
`interhunkctxlen`: Integer
`emit_funcnames`: Boolean
`emit_funccontext`: Boolean
`ignore_whitespace`: Boolean
`ignore_whitespace_change`: Boolean
`ignore_whitespace_change_at_eol`: Boolean
`ignore_cr_at_eol`: Boolean
`ignore_self_lines`: Boolean
`indent_heuristic`: Boolean.
Use the indent heuristic for the internal
diff library.


Return: ~
Diff as string, nil if {opts.hunk_func} is given.

vim:tw=78:ts=8:ft=help:norl:
10 changes: 10 additions & 0 deletions src/nvim/lua/executor.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/lua/treesitter.h"
#include "nvim/lua/xdiff.h"

#include "luv/luv.h"

Expand Down Expand Up @@ -517,6 +518,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// internal vim._treesitter... API
nlua_add_treesitter(lstate);

// internal vim.xdl_... API
nlua_add_xdiff(lstate);

lua_setglobal(lstate, "vim");

{
Expand Down Expand Up @@ -1454,6 +1458,12 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "_ts_get_language_version");
}

static void nlua_add_xdiff(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
lua_pushcfunction(lstate, &nlua_xdl_diff);
lua_setfield(lstate, -2, "xdl_diff");
}

int nlua_expand_pat(expand_T *xp,
char_u *pat,
int *num_results,
Expand Down
251 changes: 251 additions & 0 deletions src/nvim/lua/xdiff.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include "nvim/vim.h"
#include "nvim/xdiff/xdiff.h"
#include "nvim/lua/xdiff.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/api/private/helpers.h"

typedef struct s_hunkpriv {
lua_State *lstate;
Error *err;
} hunkpriv_t;

static int write_string(void *priv, mmbuffer_t *mb, int nbuf)
{
luaL_Buffer *buf = (luaL_Buffer *)priv;
for (int i = 0; i < nbuf; i++) {
const long size = mb[i].size;
for (long total = 0; total < size; total += LUAL_BUFFERSIZE) {
const int tocopy = MIN((int)(size - total), LUAL_BUFFERSIZE);
char *p = luaL_prepbuffer(buf);
if (!p) {
return -1;
}
memcpy(p, mb[i].ptr + total, (unsigned)tocopy);
luaL_addsize(buf, (unsigned)tocopy);
}
}
return 0;
}

static int hunk_func(
long start_a, long count_a, long start_b, long count_b, void *cb_data)
{
hunkpriv_t *priv = (hunkpriv_t *)cb_data;
lua_State * lstate = priv->lstate;
Error *err = priv->err;
const int fidx = lua_gettop(lstate);
lua_pushvalue(lstate, fidx);
lua_pushnumber(lstate, (float)start_a);
lua_pushnumber(lstate, (float)count_a);
lua_pushnumber(lstate, (float)start_b);
lua_pushnumber(lstate, (float)count_b);

if (lua_pcall(lstate, 4, 1, 0) != 0) {
api_set_error(err, kErrorTypeException,
"error running function hunk_func: %s",
lua_tostring(lstate, -1));
return -1;
}

int r = 0;
if (lua_isnumber(lstate, -1)) {
r = (int)lua_tonumber(lstate, -1);
}

lua_pop(lstate, 1);
lua_settop(lstate, fidx);
return r;
}

static mmfile_t get_string_arg(lua_State *lstate, int idx)
{
if (lua_type(lstate, idx) != LUA_TSTRING) {
luaL_argerror(lstate, idx, "expected string");
}
mmfile_t mf;
mf.ptr = (char *)lua_tolstring(lstate, idx, (size_t *)&mf.size);
return mf;
}

static void process_xdl_diff_opts(
lua_State *lstate, Error *err, xdemitconf_t *cfg, xpparam_t *params)
{
const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err);

for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("hunk_func", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "hunk_func is not a function");
goto exit_1;
}
nlua_pushref(lstate, v->data.luaref);
cfg->hunk_func = hunk_func;
} else if (strequal("algorithm", k.data)) {
if (v->type != kObjectTypeString) {
api_set_error(err, kErrorTypeValidation, "algorithm is not a string");
goto exit_1;
}
if (strequal("myers", v->data.string.data)) {
// default
} else if (strequal("minimal", v->data.string.data)) {
cfg->flags |= XDF_NEED_MINIMAL;
} else if (strequal("patience", v->data.string.data)) {
cfg->flags |= XDF_PATIENCE_DIFF;
} else if (strequal("histogram", v->data.string.data)) {
cfg->flags |= XDF_HISTOGRAM_DIFF;
} else {
api_set_error(err, kErrorTypeValidation, "not a valid algorithm");
goto exit_1;
}
} else if (strequal("ctxlen", k.data)) {
if (v->type != kObjectTypeInteger) {
api_set_error(err, kErrorTypeValidation, "ctxlen is not an integer");
goto exit_1;
}
cfg->ctxlen = v->data.integer;
} else if (strequal("interhunkctxlen", k.data)) {
if (v->type != kObjectTypeInteger) {
api_set_error(err, kErrorTypeValidation,
"interhunkctxlen is not an integer");
goto exit_1;
}
cfg->interhunkctxlen = v->data.integer;
} else if (strequal("emit_funcnames", k.data)) {
if (v->type != kObjectTypeBoolean) {
api_set_error(err, kErrorTypeValidation,
"emit_funcnames is not a boolean");
goto exit_1;
}
if (v->data.boolean) {
cfg->flags |= XDL_EMIT_FUNCNAMES;
}
} else if (strequal("emit_funccontext", k.data)) {
if (v->type != kObjectTypeBoolean) {
api_set_error(err, kErrorTypeValidation,
"emit_funccontext is not a boolean");
goto exit_1;
}
if (v->data.boolean) {
cfg->flags |= XDL_EMIT_FUNCCONTEXT;
}
} else {
struct {
const char *name;
unsigned long value;
} flags[] = {
{ "ignore_whitespace" , XDF_IGNORE_WHITESPACE },
{ "ignore_whitespace_change" , XDF_IGNORE_WHITESPACE_CHANGE },
{ "ignore_whitespace_change_at_eol", XDF_IGNORE_WHITESPACE_AT_EOL },
{ "ignore_cr_at_eol" , XDF_IGNORE_CR_AT_EOL },
{ "ignore_self_lines" , XDF_IGNORE_BLANK_LINES },
{ "indent_heuristic" , XDF_INDENT_HEURISTIC },
{ NULL , 0 },
};
bool key_used = false;
for (size_t j = 0; flags[j].name; j++) {
if (strequal(flags[j].name, k.data)) {
if (v->type != kObjectTypeBoolean) {
api_set_error(err, kErrorTypeValidation,
"%s is not a boolean", flags[j].name);
goto exit_1;
}
if (v->data.boolean) {
params->flags |= flags[j].value;
}
key_used = true;
break;
}
}

if (key_used) {
continue;
}

api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
goto exit_1;
}
}

exit_1:
api_free_dictionary(opts);
}

int nlua_xdl_diff(lua_State *lstate)
{
if (lua_gettop(lstate) < 2) {
return luaL_error(lstate, "Expected at least 2 arguments");
}
mmfile_t ma = get_string_arg(lstate, 1);
mmfile_t mb = get_string_arg(lstate, 2);

Error err = ERROR_INIT;

xdemitconf_t cfg;
xpparam_t params;
xdemitcb_t ecb;

memset(&cfg , 0, sizeof(cfg));
memset(&params, 0, sizeof(params));
memset(&ecb , 0, sizeof(ecb));

if (lua_gettop(lstate) == 3) {
if (lua_type(lstate, 3) != LUA_TTABLE) {
return luaL_argerror(lstate, 3, "expected table");
}

process_xdl_diff_opts(lstate, &err, &cfg, &params);

if (ERROR_SET(&err)) {
goto exit_0;
}
}

luaL_Buffer buf;
hunkpriv_t *priv;
if (cfg.hunk_func == NULL) {
luaL_buffinit(lstate, &buf);
ecb.priv = &buf;
ecb.outf = write_string;
} else {
priv = xmalloc(sizeof(*priv));
priv->lstate = lstate;
priv->err = &err;
ecb.priv = priv;
}

if (xdl_diff(&ma, &mb, &params, &cfg, &ecb) == -1) {
if (!ERROR_SET(&err)) {
api_set_error(&err, kErrorTypeException,
"Error while performing diff operation");
}
}

exit_0:
if (cfg.hunk_func != NULL) {
xfree(priv);
}
if (ERROR_SET(&err)) {
luaL_where(lstate, 1);
lua_pushstring(lstate, err.msg);
api_clear_error(&err);
lua_concat(lstate, 2);
return lua_error(lstate);
}
if (cfg.hunk_func == NULL) {
luaL_pushresult(&buf);
return 1;
}
return 0;
}
10 changes: 10 additions & 0 deletions src/nvim/lua/xdiff.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef NVIM_LUA_XDIFF_H
#define NVIM_LUA_XDIFF_H

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int nlua_xdl_diff(lua_State *lstate);

#endif // NVIM_LUA_XDIFF_H
Loading

0 comments on commit 215404e

Please sign in to comment.
-