neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

xdiff.c (10044B)


      1 #include <lauxlib.h>
      2 #include <limits.h>
      3 #include <lua.h>
      4 #include <stdbool.h>
      5 #include <stdint.h>
      6 #include <string.h>
      7 
      8 #include "luaconf.h"
      9 #include "nvim/api/keysets_defs.h"
     10 #include "nvim/api/private/defs.h"
     11 #include "nvim/api/private/dispatch.h"
     12 #include "nvim/api/private/helpers.h"
     13 #include "nvim/linematch.h"
     14 #include "nvim/lua/converter.h"
     15 #include "nvim/lua/executor.h"
     16 #include "nvim/lua/xdiff.h"
     17 #include "nvim/macros_defs.h"
     18 #include "nvim/memory.h"
     19 #include "nvim/pos_defs.h"
     20 #include "xdiff/xdiff.h"
     21 
     22 #define COMPARED_BUFFER0 (1 << 0)
     23 #define COMPARED_BUFFER1 (1 << 1)
     24 
     25 typedef enum {
     26  kNluaXdiffModeUnified = 0,
     27  kNluaXdiffModeOnHunkCB,
     28  kNluaXdiffModeLocations,
     29 } NluaXdiffMode;
     30 
     31 typedef struct {
     32  lua_State *lstate;
     33  Error *err;
     34  mmfile_t *ma;
     35  mmfile_t *mb;
     36  int64_t linematch;
     37  bool iwhite;
     38 } hunkpriv_t;
     39 
     40 #include "lua/xdiff.c.generated.h"
     41 
     42 static void lua_pushhunk(lua_State *lstate, long start_a, long count_a, long start_b, long count_b)
     43 {
     44  // Mimic extra offsets done by xdiff, see:
     45  // src/xdiff/xemit.c:284
     46  // src/xdiff/xutils.c:(356,368)
     47  if (count_a > 0) {
     48    start_a += 1;
     49  }
     50  if (count_b > 0) {
     51    start_b += 1;
     52  }
     53  lua_createtable(lstate, 0, 0);
     54  lua_pushinteger(lstate, start_a);
     55  lua_rawseti(lstate, -2, 1);
     56  lua_pushinteger(lstate, count_a);
     57  lua_rawseti(lstate, -2, 2);
     58  lua_pushinteger(lstate, start_b);
     59  lua_rawseti(lstate, -2, 3);
     60  lua_pushinteger(lstate, count_b);
     61  lua_rawseti(lstate, -2, 4);
     62  lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1);
     63 }
     64 
     65 static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, int start_a,
     66                                  int count_a, int start_b, int count_b, bool iwhite)
     67 {
     68  // get the pointer to char of the start of the diff to pass it to linematch algorithm
     69  mmfile_t ma0 = fastforward_buf_to_lnum(*ma, (linenr_T)start_a + 1);
     70  mmfile_t mb0 = fastforward_buf_to_lnum(*mb, (linenr_T)start_b + 1);
     71 
     72  const mmfile_t *diff_begin[2] = { &ma0, &mb0 };
     73  int diff_length[2] = { count_a, count_b };
     74 
     75  int *decisions = NULL;
     76  size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite);
     77 
     78  int lnuma = start_a;
     79  int lnumb = start_b;
     80 
     81  int hunkstarta = lnuma;
     82  int hunkstartb = lnumb;
     83  int hunkcounta = 0;
     84  int hunkcountb = 0;
     85  for (size_t i = 0; i < decisions_length; i++) {
     86    if (i && (decisions[i - 1] != decisions[i])) {
     87      lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb);
     88 
     89      hunkstarta = lnuma;
     90      hunkstartb = lnumb;
     91      hunkcounta = 0;
     92      hunkcountb = 0;
     93      // create a new hunk
     94    }
     95    if (decisions[i] & COMPARED_BUFFER0) {
     96      lnuma++;
     97      hunkcounta++;
     98    }
     99    if (decisions[i] & COMPARED_BUFFER1) {
    100      lnumb++;
    101      hunkcountb++;
    102    }
    103  }
    104  lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb);
    105  xfree(decisions);
    106 }
    107 
    108 static int write_string(void *priv, mmbuffer_t *mb, int nbuf)
    109 {
    110  luaL_Buffer *buf = (luaL_Buffer *)priv;
    111  for (int i = 0; i < nbuf; i++) {
    112    const int size = mb[i].size;
    113    for (int total = 0; total < size; total += LUAL_BUFFERSIZE) {
    114      const int tocopy = MIN((int)(size - total), LUAL_BUFFERSIZE);
    115      char *p = luaL_prepbuffer(buf);
    116      if (!p) {
    117        return -1;
    118      }
    119      memcpy(p, mb[i].ptr + total, (unsigned)tocopy);
    120      luaL_addsize(buf, (unsigned)tocopy);
    121    }
    122  }
    123  return 0;
    124 }
    125 
    126 // hunk_func callback used when opts.hunk_lines = true
    127 static int hunk_locations_cb(int start_a, int count_a, int start_b, int count_b, void *cb_data)
    128 {
    129  hunkpriv_t *priv = (hunkpriv_t *)cb_data;
    130  lua_State *lstate = priv->lstate;
    131  if (priv->linematch > 0 && count_a + count_b <= priv->linematch) {
    132    get_linematch_results(lstate, priv->ma, priv->mb, start_a, count_a, start_b, count_b,
    133                          priv->iwhite);
    134  } else {
    135    lua_pushhunk(lstate, start_a, count_a, start_b, count_b);
    136  }
    137 
    138  return 0;
    139 }
    140 
    141 // hunk_func callback used when opts.on_hunk is given
    142 static int call_on_hunk_cb(int start_a, int count_a, int start_b, int count_b, void *cb_data)
    143 {
    144  // Mimic extra offsets done by xdiff, see:
    145  // src/xdiff/xemit.c:284
    146  // src/xdiff/xutils.c:(356,368)
    147  if (count_a > 0) {
    148    start_a += 1;
    149  }
    150  if (count_b > 0) {
    151    start_b += 1;
    152  }
    153 
    154  hunkpriv_t *priv = (hunkpriv_t *)cb_data;
    155  lua_State *lstate = priv->lstate;
    156  Error *err = priv->err;
    157  const int fidx = lua_gettop(lstate);
    158  lua_pushvalue(lstate, fidx);
    159  lua_pushinteger(lstate, start_a);
    160  lua_pushinteger(lstate, count_a);
    161  lua_pushinteger(lstate, start_b);
    162  lua_pushinteger(lstate, count_b);
    163 
    164  if (lua_pcall(lstate, 4, 1, 0) != 0) {
    165    api_set_error(err, kErrorTypeException, "on_hunk: %s", lua_tostring(lstate, -1));
    166    return -1;
    167  }
    168 
    169  int r = 0;
    170  if (lua_isnumber(lstate, -1)) {
    171    r = (int)lua_tonumber(lstate, -1);
    172  }
    173 
    174  lua_pop(lstate, 1);
    175  lua_settop(lstate, fidx);
    176  return r;
    177 }
    178 
    179 static mmfile_t get_string_arg(lua_State *lstate, int idx)
    180 {
    181  if (lua_type(lstate, idx) != LUA_TSTRING) {
    182    luaL_argerror(lstate, idx, "expected string");
    183  }
    184  mmfile_t mf;
    185  size_t size;
    186  mf.ptr = (char *)lua_tolstring(lstate, idx, &size);
    187  if (size > INT_MAX) {
    188    luaL_argerror(lstate, idx, "string too long");
    189  }
    190  mf.size = (int)size;
    191  return mf;
    192 }
    193 
    194 static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params,
    195                                           int64_t *linematch, Error *err)
    196 {
    197  Dict(xdl_diff) opts = KEYDICT_INIT;
    198  char *err_param = NULL;
    199  KeySetLink *KeyDict_xdl_diff_get_field(const char *str, size_t len);
    200  nlua_pop_keydict(lstate, &opts, KeyDict_xdl_diff_get_field, &err_param, NULL, err);
    201 
    202  NluaXdiffMode mode = kNluaXdiffModeUnified;
    203 
    204  bool had_result_type_indices = false;
    205 
    206  if (HAS_KEY(&opts, xdl_diff, result_type)) {
    207    if (strequal("unified", opts.result_type.data)) {
    208      // the default
    209    } else if (strequal("indices", opts.result_type.data)) {
    210      had_result_type_indices = true;
    211    } else {
    212      api_set_error(err, kErrorTypeValidation, "not a valid result_type");
    213      goto exit_1;
    214    }
    215  }
    216 
    217  if (HAS_KEY(&opts, xdl_diff, algorithm)) {
    218    if (strequal("myers", opts.algorithm.data)) {
    219      // default
    220    } else if (strequal("minimal", opts.algorithm.data)) {
    221      params->flags |= XDF_NEED_MINIMAL;
    222    } else if (strequal("patience", opts.algorithm.data)) {
    223      params->flags |= XDF_PATIENCE_DIFF;
    224    } else if (strequal("histogram", opts.algorithm.data)) {
    225      params->flags |= XDF_HISTOGRAM_DIFF;
    226    } else {
    227      api_set_error(err, kErrorTypeValidation, "not a valid algorithm");
    228      goto exit_1;
    229    }
    230  }
    231 
    232  if (HAS_KEY(&opts, xdl_diff, ctxlen)) {
    233    cfg->ctxlen = (long)opts.ctxlen;
    234  }
    235 
    236  if (HAS_KEY(&opts, xdl_diff, interhunkctxlen)) {
    237    cfg->interhunkctxlen = (long)opts.interhunkctxlen;
    238  }
    239 
    240  if (HAS_KEY(&opts, xdl_diff, linematch)) {
    241    if (opts.linematch.type == kObjectTypeBoolean) {
    242      *linematch = opts.linematch.data.boolean ? INT64_MAX : 0;
    243    } else if (opts.linematch.type == kObjectTypeInteger) {
    244      *linematch = opts.linematch.data.integer;
    245    } else {
    246      api_set_error(err, kErrorTypeValidation, "linematch must be a boolean or integer");
    247      goto exit_1;
    248    }
    249  }
    250 
    251  params->flags |= opts.ignore_whitespace ? XDF_IGNORE_WHITESPACE : 0;
    252  params->flags |= opts.ignore_whitespace_change ? XDF_IGNORE_WHITESPACE_CHANGE : 0;
    253  params->flags |= opts.ignore_whitespace_change_at_eol ? XDF_IGNORE_WHITESPACE_AT_EOL : 0;
    254  params->flags |= opts.ignore_cr_at_eol ? XDF_IGNORE_CR_AT_EOL : 0;
    255  params->flags |= opts.ignore_blank_lines ? XDF_IGNORE_BLANK_LINES : 0;
    256  params->flags |= opts.indent_heuristic ? XDF_INDENT_HEURISTIC : 0;
    257 
    258  if (HAS_KEY(&opts, xdl_diff, on_hunk)) {
    259    mode = kNluaXdiffModeOnHunkCB;
    260    nlua_pushref(lstate, opts.on_hunk);
    261    if (lua_type(lstate, -1) != LUA_TFUNCTION) {
    262      api_set_error(err, kErrorTypeValidation, "on_hunk is not a function");
    263    }
    264  } else if (had_result_type_indices) {
    265    mode = kNluaXdiffModeLocations;
    266  }
    267 
    268 exit_1:
    269  api_free_string(opts.result_type);
    270  api_free_string(opts.algorithm);
    271  api_free_luaref(opts.on_hunk);
    272  return mode;
    273 }
    274 
    275 int nlua_xdl_diff(lua_State *lstate)
    276 {
    277  if (lua_gettop(lstate) < 2) {
    278    return luaL_error(lstate, "Expected at least 2 arguments");
    279  }
    280  mmfile_t ma = get_string_arg(lstate, 1);
    281  mmfile_t mb = get_string_arg(lstate, 2);
    282 
    283  Error err = ERROR_INIT;
    284 
    285  xdemitconf_t cfg;
    286  xpparam_t params;
    287  xdemitcb_t ecb;
    288  int64_t linematch = 0;
    289 
    290  CLEAR_FIELD(cfg);
    291  CLEAR_FIELD(params);
    292  CLEAR_FIELD(ecb);
    293 
    294  NluaXdiffMode mode = kNluaXdiffModeUnified;
    295 
    296  if (lua_gettop(lstate) == 3) {
    297    if (lua_type(lstate, 3) != LUA_TTABLE) {
    298      return luaL_argerror(lstate, 3, "expected table");
    299    }
    300 
    301    mode = process_xdl_diff_opts(lstate, &cfg, &params, &linematch, &err);
    302 
    303    if (ERROR_SET(&err)) {
    304      goto exit_0;
    305    }
    306  }
    307 
    308  luaL_Buffer buf;
    309  hunkpriv_t priv;
    310  switch (mode) {
    311  case kNluaXdiffModeUnified:
    312    luaL_buffinit(lstate, &buf);
    313    ecb.priv = &buf;
    314    ecb.out_line = write_string;
    315    break;
    316  case kNluaXdiffModeOnHunkCB:
    317    cfg.hunk_func = call_on_hunk_cb;
    318    priv = (hunkpriv_t) {
    319      .lstate = lstate,
    320      .err = &err,
    321    };
    322    ecb.priv = &priv;
    323    break;
    324  case kNluaXdiffModeLocations:
    325    cfg.hunk_func = hunk_locations_cb;
    326    priv = (hunkpriv_t) {
    327      .lstate = lstate,
    328      .ma = &ma,
    329      .mb = &mb,
    330      .linematch = linematch,
    331      .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0
    332    };
    333    ecb.priv = &priv;
    334    lua_createtable(lstate, 0, 0);
    335    break;
    336  }
    337 
    338  if (xdl_diff(&ma, &mb, &params, &cfg, &ecb) == -1) {
    339    if (!ERROR_SET(&err)) {
    340      api_set_error(&err, kErrorTypeException, "diff operation failed");
    341    }
    342  }
    343 
    344 exit_0:
    345  if (ERROR_SET(&err)) {
    346    luaL_where(lstate, 1);
    347    lua_pushstring(lstate, err.msg);
    348    api_clear_error(&err);
    349    lua_concat(lstate, 2);
    350    return lua_error(lstate);
    351  } else if (mode == kNluaXdiffModeUnified) {
    352    luaL_pushresult(&buf);
    353    return 1;
    354  } else if (mode == kNluaXdiffModeLocations) {
    355    return 1;
    356  }
    357  return 0;
    358 }