diff options
Diffstat (limited to 'src/nvim/lua/xdiff.c')
| -rw-r--r-- | src/nvim/lua/xdiff.c | 334 | 
1 files changed, 334 insertions, 0 deletions
| diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c new file mode 100644 index 0000000000..d50f874a82 --- /dev/null +++ b/src/nvim/lua/xdiff.c @@ -0,0 +1,334 @@ +#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 enum { +  kNluaXdiffModeUnified =  0, +  kNluaXdiffModeOnHunkCB, +  kNluaXdiffModeLocations, +} NluaXdiffMode; + +typedef struct { +  lua_State *lstate; +  Error     *err; +} hunkpriv_t; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/xdiff.c.generated.h" +#endif + +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; +} + +// hunk_func callback used when opts.hunk_lines = true +static int hunk_locations_cb(long start_a, long count_a, +                             long start_b, long count_b, void *cb_data) +{ +  // Mimic extra offsets done by xdiff, see: +  // src/nvim/xdiff/xemit.c:284 +  // src/nvim/xdiff/xutils.c:(356,368) +  if (count_a > 0) { +    start_a += 1; +  } +  if (count_b > 0) { +    start_b += 1; +  } + +  lua_State * lstate = (lua_State *)cb_data; +  lua_createtable(lstate, 0, 0); + +  lua_pushinteger(lstate, start_a); +  lua_rawseti(lstate, -2, 1); +  lua_pushinteger(lstate, count_a); +  lua_rawseti(lstate, -2, 2); +  lua_pushinteger(lstate, start_b); +  lua_rawseti(lstate, -2, 3); +  lua_pushinteger(lstate, count_b); +  lua_rawseti(lstate, -2, 4); + +  lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2)+1); + +  return 0; +} + +// hunk_func callback used when opts.on_hunk is given +static int call_on_hunk_cb(long start_a, long count_a, +                           long start_b, long count_b, void *cb_data) +{ +  // Mimic extra offsets done by xdiff, see: +  // src/nvim/xdiff/xemit.c:284 +  // src/nvim/xdiff/xutils.c:(356,368) +  if (count_a > 0) { +    start_a += 1; +  } +  if (count_b > 0) { +    start_b += 1; +  } + +  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_pushinteger(lstate, start_a); +  lua_pushinteger(lstate, count_a); +  lua_pushinteger(lstate, start_b); +  lua_pushinteger(lstate, count_b); + +  if (lua_pcall(lstate, 4, 1, 0) != 0) { +    api_set_error(err, kErrorTypeException, +                  "error running function on_hunk: %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; +} + +// Helper function for validating option types +static bool check_xdiff_opt(ObjectType actType, ObjectType expType, +                            const char *name, Error *err) +{ +  if (actType != expType) { +    const char * type_str = +      expType == kObjectTypeString  ? "string"   : +      expType == kObjectTypeInteger ? "integer"  : +      expType == kObjectTypeBoolean ? "boolean"  : +      expType == kObjectTypeLuaRef  ? "function" : +                                      "NA"; + +    api_set_error(err, kErrorTypeValidation, "%s is not a %s", name, +                  type_str); +    return true; +  } + +  return false; +} + +static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, +                                           xdemitconf_t *cfg, +                                           xpparam_t *params, Error *err) +{ +  const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err); + +  NluaXdiffMode mode = kNluaXdiffModeUnified; + +  bool had_on_hunk = false; +  bool had_result_type_indices = false; +  for (size_t i = 0; i < opts.size; i++) { +    String k = opts.items[i].key; +    Object *v = &opts.items[i].value; +    if (strequal("on_hunk", k.data)) { +      if (check_xdiff_opt(v->type, kObjectTypeLuaRef, "on_hunk", err)) { +        goto exit_1; +      } +      had_on_hunk = true; +      nlua_pushref(lstate, v->data.luaref); +    } else if (strequal("result_type", k.data)) { +      if (check_xdiff_opt(v->type, kObjectTypeString, "result_type", err)) { +        goto exit_1; +      } +      if (strequal("unified", v->data.string.data)) { +      } else if (strequal("indices", v->data.string.data)) { +        had_result_type_indices = true; +      } else { +        api_set_error(err, kErrorTypeValidation, "not a valid result_type"); +        goto exit_1; +      } +    } else if (strequal("algorithm", k.data)) { +      if (check_xdiff_opt(v->type, kObjectTypeString, "algorithm", err)) { +        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 (check_xdiff_opt(v->type, kObjectTypeInteger, "ctxlen", err)) { +        goto exit_1; +      } +      cfg->ctxlen = v->data.integer; +    } else if (strequal("interhunkctxlen", k.data)) { +      if (check_xdiff_opt(v->type, kObjectTypeInteger, "interhunkctxlen", +                          err)) { +        goto exit_1; +      } +      cfg->interhunkctxlen = v->data.integer; +    } 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_blank_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 (check_xdiff_opt(v->type, kObjectTypeBoolean, flags[j].name, +                              err)) { +            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; +    } +  } + +  if (had_on_hunk) { +    mode = kNluaXdiffModeOnHunkCB; +    cfg->hunk_func = call_on_hunk_cb; +  } else if (had_result_type_indices) { +    mode = kNluaXdiffModeLocations; +    cfg->hunk_func = hunk_locations_cb; +  } + +exit_1: +  api_free_dictionary(opts); +  return mode; +} + +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(¶ms, 0, sizeof(params)); +  memset(&ecb   , 0, sizeof(ecb)); + +  NluaXdiffMode mode = kNluaXdiffModeUnified; + +  if (lua_gettop(lstate) == 3) { +    if (lua_type(lstate, 3) != LUA_TTABLE) { +      return luaL_argerror(lstate, 3, "expected table"); +    } + +    mode = process_xdl_diff_opts(lstate, &cfg, ¶ms, &err); + +    if (ERROR_SET(&err)) { +      goto exit_0; +    } +  } + +  luaL_Buffer buf; +  hunkpriv_t *priv = NULL; +  switch (mode) { +    case kNluaXdiffModeUnified: +      luaL_buffinit(lstate, &buf); +      ecb.priv = &buf; +      ecb.outf = write_string; +      break; +    case kNluaXdiffModeOnHunkCB: +      priv = xmalloc(sizeof(*priv)); +      priv->lstate = lstate; +      priv->err = &err; +      ecb.priv = priv; +      break; +    case kNluaXdiffModeLocations: +      lua_createtable(lstate, 0, 0); +      ecb.priv = lstate; +      break; +  } + +  if (xdl_diff(&ma, &mb, ¶ms, &cfg, &ecb) == -1) { +    if (!ERROR_SET(&err)) { +      api_set_error(&err, kErrorTypeException, +                    "Error while performing diff operation"); +    } +  } + +  XFREE_CLEAR(priv); + +exit_0: +  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); +  } else if (mode == kNluaXdiffModeUnified) { +    luaL_pushresult(&buf); +    return 1; +  } else if (mode == kNluaXdiffModeLocations) { +    return 1; +  } +  return 0; +} | 
