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, ¶ms, &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, ¶ms, &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 }