/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright 2022 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2013-2022 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ /************************************************************************ This file houses the "dibu" (Diff Builder) routines. */ #include "libfossil.h" #include <assert.h> #include <memory.h> #include <stdlib.h> #include <string.h> /* for memmove()/strlen() */ #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) const fsl_dibu_opt fsl_dibu_opt_empty = fsl_dibu_opt_empty_m; const fsl_dibu fsl_dibu_empty = fsl_dibu_empty_m; fsl_dibu * fsl_dibu_alloc(fsl_size_t extra){ fsl_dibu * rc = (fsl_dibu*)fsl_malloc(sizeof(fsl_dibu) + extra); if(rc){ *rc = fsl_dibu_empty; if(extra){ rc->pimpl = ((unsigned char *)rc)+sizeof(fsl_dibu); } } return rc; } static int fdb__out(fsl_dibu *const b, char const *z, fsl_int_t n){ if(n<0) n = (fsl_int_t)fsl_strlen(z); return b->opt->out(b->opt->outState, z, (fsl_size_t)n); } static int fdb__outf(fsl_dibu * const b, char const *fmt, ...){ int rc = 0; va_list va; assert(b->opt->out); va_start(va,fmt); rc = fsl_appendfv(b->opt->out, b->opt->outState, fmt, va); va_end(va); return rc; } /** Column indexes for DiffCounter::cols. */ enum DiffCounterCols { DICO_NUM1 = 0, DICO_TEXT1, DICO_MOD, DICO_NUM2, DICO_TEXT2, DICO_count }; /** Internal state for the text-mode split diff builder. Used for calculating column widths in the builder's first pass so that the second pass can output everything in a uniform width. */ struct DiffCounter { /** Largest column width we've yet seen. These are only updated for DICO_TEXT1 and DICO_TEXT2. The others currently have fixed widths. FIXME: these are in bytes, not text columns. The current code may truncate multibyte characters. */ uint32_t maxWidths[DICO_count]; /** Max line numbers seen for the LHS/RHS input files. This is likely much higher than the number of diff lines. This can be used, e.g., to size and allocate a curses PAD in the second pass of the start() method. */ uint32_t lineCount[2]; /** The actual number of lines needed for rendering the file. */ uint32_t displayLines; }; typedef struct DiffCounter DiffCounter; static const DiffCounter DiffCounter_empty = {{1,25,3,1,25},{0,0},0}; #define DICOSTATE(VNAME) DiffCounter * const VNAME = (DiffCounter *)b->pimpl static int maxColWidth(fsl_dibu const * const b, DiffCounter const * const sst, int mwIndex){ static const short minColWidth = 10/*b->opt.columnWidth values smaller than this are treated as this value*/; switch(mwIndex){ case DICO_NUM1: case DICO_NUM2: case DICO_MOD: return sst->maxWidths[mwIndex]; case DICO_TEXT1: case DICO_TEXT2: break; default: assert(!"internal API misuse: invalid column index."); break; } int const y = (b->opt->columnWidth>0 && b->opt->columnWidth<=sst->maxWidths[mwIndex]) ? (int)b->opt->columnWidth : (int)sst->maxWidths[mwIndex]; return minColWidth > y ? minColWidth : y; } static void fdb__dico_update_maxlen(DiffCounter * const sst, int col, char const * const z, uint32_t n){ if(sst->maxWidths[col]<n){ #if 0 sst->maxWidths[col] = n; #else n = (uint32_t)fsl_strlen_utf8(z, (fsl_int_t)n); if(sst->maxWidths[col]<n) sst->maxWidths[col] = n; #endif } } static int fdb__debug_start(fsl_dibu * const b){ int rc = fdb__outf(b, "DEBUG builder starting pass #%d.\n", b->passNumber); if(1==b->passNumber){ DICOSTATE(sst); *sst = DiffCounter_empty; if(b->opt->nameLHS) ++sst->displayLines; if(b->opt->nameRHS) ++sst->displayLines; if(b->opt->hashLHS) ++sst->displayLines; if(b->opt->hashRHS) ++sst->displayLines; ++b->fileCount; return rc; } if(0==rc && b->opt->nameLHS){ rc = fdb__outf(b,"LHS: %s\n", b->opt->nameLHS); } if(0==rc && b->opt->nameRHS){ rc = fdb__outf(b,"RHS: %s\n", b->opt->nameRHS); } if(0==rc && b->opt->hashLHS){ rc = fdb__outf(b,"LHS hash: %s\n", b->opt->hashLHS); } if(0==rc && b->opt->hashRHS){ rc = fdb__outf(b,"RHS hash: %s\n", b->opt->hashRHS); } return rc; } static int fdb__debug_chunkHeader(fsl_dibu* const b, uint32_t lnnoLHS, uint32_t linesLHS, uint32_t lnnoRHS, uint32_t linesRHS ){ #if 1 if(1==b->passNumber){ DICOSTATE(sst); ++sst->displayLines; return 0; } if(b->lnLHS+1==lnnoLHS && b->lnRHS+1==lnnoRHS){ fdb__outf(b, "<<<Unfortunate chunk separator." "Ticket 746ebbe86c20b5c0f96cdadd19abd8284770de16.>>>\n"); } //fdb__outf(b, "lnLHS=%d, lnRHS=%d\n", (int)b->lnLHS, (int)b->lnRHS); return fdb__outf(b, "@@ -%" PRIu32 ",%" PRIu32 " +%" PRIu32 ",%" PRIu32 " @@\n", lnnoLHS, linesLHS, lnnoRHS, linesRHS); #else return 0; #endif } static int fdb__debug_skip(fsl_dibu * const b, uint32_t n){ if(1==b->passNumber){ DICOSTATE(sst); b->lnLHS += n; b->lnRHS += n; ++sst->displayLines; return 0; } const int rc = fdb__outf(b, "SKIP %u (%u..%u left and %u..%u right)\n", n, b->lnLHS+1, b->lnLHS+n, b->lnRHS+1, b->lnRHS+n); b->lnLHS += n; b->lnRHS += n; return rc; } static int fdb__debug_common(fsl_dibu * const b, fsl_dline const * pLine){ DICOSTATE(sst); ++b->lnLHS; ++b->lnRHS; if(1==b->passNumber){ ++sst->displayLines; fdb__dico_update_maxlen(sst, DICO_TEXT1, pLine->z, pLine->n); fdb__dico_update_maxlen(sst, DICO_TEXT2, pLine->z, pLine->n); return 0; } return fdb__outf(b, "COMMON %8u %8u %.*s\n", b->lnLHS, b->lnRHS, (int)pLine->n, pLine->z); } static int fdb__debug_insertion(fsl_dibu * const b, fsl_dline const * pLine){ DICOSTATE(sst); ++b->lnRHS; if(1==b->passNumber){ ++sst->displayLines; fdb__dico_update_maxlen(sst, DICO_TEXT1, pLine->z, pLine->n); return 0; } return fdb__outf(b, "INSERT %8u %.*s\n", b->lnRHS, (int)pLine->n, pLine->z); } static int fdb__debug_deletion(fsl_dibu * const b, fsl_dline const * pLine){ DICOSTATE(sst); ++b->lnLHS; if(1==b->passNumber){ ++sst->displayLines; fdb__dico_update_maxlen(sst, DICO_TEXT2, pLine->z, pLine->n); return 0; } return fdb__outf(b, "DELETE %8u %.*s\n", b->lnLHS, (int)pLine->n, pLine->z); } static int fdb__debug_replacement(fsl_dibu * const b, fsl_dline const * lineLhs, fsl_dline const * lineRhs) { #if 0 int rc = b->deletion(b, lineLhs); if(0==rc) rc = b->insertion(b, lineRhs); return rc; #else DICOSTATE(sst); ++b->lnLHS; ++b->lnRHS; if(1==b->passNumber){ ++sst->displayLines; fdb__dico_update_maxlen(sst, DICO_TEXT1, lineLhs->z, lineLhs->n); fdb__dico_update_maxlen(sst, DICO_TEXT2, lineRhs->z, lineRhs->n); return 0; } int rc = fdb__outf(b, "REPLACE %8u %.*s\n", b->lnLHS, (int)lineLhs->n, lineLhs->z); if(!rc){ rc = fdb__outf(b, " %8u %.*s\n", b->lnRHS, (int)lineRhs->n, lineRhs->z); } return rc; #endif } static int fdb__debug_edit(fsl_dibu * const b, fsl_dline const * lineLHS, fsl_dline const * lineRHS){ #if 0 int rc = b->deletion(b, lineLHS); if(0==rc) rc = b->insertion(b, lineRHS); return rc; #else int rc = 0; DICOSTATE(sst); ++b->lnLHS; ++b->lnRHS; if(1==b->passNumber){ sst->displayLines += 4 /* this is actually 3 or 4, but we don't know that from here */; fdb__dico_update_maxlen(sst, DICO_TEXT1, lineLHS->z, lineLHS->n); fdb__dico_update_maxlen(sst, DICO_TEXT2, lineRHS->z, lineRHS->n); return 0; } int i, j; int x; fsl_dline_change chng = fsl_dline_change_empty; #define RC if(rc) goto end rc = fdb__outf(b, "EDIT %8u %.*s\n", b->lnLHS, (int)lineLHS->n, lineLHS->z); RC; fsl_dline_change_spans(lineLHS, lineRHS, &chng); for(i=x=0; i<chng.n; i++){ int ofst = chng.a[i].iStart1; int len = chng.a[i].iLen1; if( len ){ char c = '0' + i; if( x==0 ){ rc = fdb__outf(b, "%*s", 26, ""); RC; } while( ofst > x ){ if( (lineLHS->z[x]&0xc0)!=0x80 ){ rc = fdb__out(b, " ", 1); RC; } x++; } for(j=0; j<len; j++, x++){ if( (lineLHS->z[x]&0xc0)!=0x80 ){ rc = fdb__out(b, &c, 1); RC; } } } } if( x ){ rc = fdb__out(b, "\n", 1); RC; } rc = fdb__outf(b, " %8u %.*s\n", b->lnRHS, (int)lineRHS->n, lineRHS->z); RC; for(i=x=0; i<chng.n; i++){ int ofst = chng.a[i].iStart2; int len = chng.a[i].iLen2; if( len ){ char c = '0' + i; if( x==0 ){ rc = fdb__outf(b, "%*s", 26, ""); RC; } while( ofst > x ){ if( (lineRHS->z[x]&0xc0)!=0x80 ){ rc = fdb__out(b, " ", 1); RC; } x++; } for(j=0; j<len; j++, x++){ if( (lineRHS->z[x]&0xc0)!=0x80 ){ rc = fdb__out(b, &c, 1); RC; } } } } if( x ){ rc = fdb__out(b, "\n", 1); } end: #undef RC return rc; #endif } static int fdb__debug_finish(fsl_dibu * const b){ DICOSTATE(sst); if(1==b->passNumber){ sst->lineCount[0] = b->lnLHS; sst->lineCount[1] = b->lnRHS; return 0; } int rc = fdb__outf(b, "END with %u LHS file lines " "and %u RHS lines (max. %u display lines)\n", b->lnLHS, b->lnRHS, sst->displayLines); if(0==rc){ rc = fdb__outf(b,"Col widths: num left=%u, col left=%u, " "modifier=%u, num right=%u, col right=%u\n", sst->maxWidths[0], sst->maxWidths[1], sst->maxWidths[2], sst->maxWidths[3], sst->maxWidths[4]); } return rc; } void fsl_dibu_finalizer(fsl_dibu * const b){ *b = fsl_dibu_empty; fsl_free(b); } static fsl_dibu * fsl__diff_builder_debug(void){ fsl_dibu * rc = fsl_dibu_alloc((fsl_size_t)sizeof(DiffCounter)); if(rc){ rc->chunkHeader = fdb__debug_chunkHeader; rc->start = fdb__debug_start; rc->skip = fdb__debug_skip; rc->common = fdb__debug_common; rc->insertion = fdb__debug_insertion; rc->deletion = fdb__debug_deletion; rc->replacement = fdb__debug_replacement; rc->edit = fdb__debug_edit; rc->finish = fdb__debug_finish; rc->finalize = fsl_dibu_finalizer; rc->twoPass = true; assert(0!=rc->pimpl); DiffCounter * const sst = (DiffCounter*)rc->pimpl; *sst = DiffCounter_empty; assert(0==rc->implFlags); assert(0==rc->lnLHS); assert(0==rc->lnRHS); assert(NULL==rc->opt); } return rc; } /******************** json1 diff builder ********************/ /* Description taken verbatim from fossil(1): */ /* ** This formatter generates a JSON array that describes the difference. ** ** The Json array consists of integer opcodes with each opcode followed ** by zero or more arguments: ** ** Syntax Mnemonic Description ** ----------- -------- -------------------------- ** 0 END This is the end of the diff ** 1 INTEGER SKIP Skip N lines from both files ** 2 STRING COMMON The line show by STRING is in both files ** 3 STRING INSERT The line STRING is in only the right file ** 4 STRING DELETE The STRING line is in only the left file ** 5 SUBARRAY EDIT One line is different on left and right. ** ** The SUBARRAY is an array of 3*N+1 strings with N>=0. The triples ** represent common-text, left-text, and right-text. The last string ** in SUBARRAY is the common-suffix. Any string can be empty if it does ** not apply. */ static int fdb__outj(fsl_dibu * const b, char const *zJson, int n){ return n<0 ? fdb__outf(b, "%!j", zJson) : fdb__outf(b, "%!.*j", n, zJson); } static int fdb__json1_start(fsl_dibu * const b){ int rc = fdb__outf(b, "{\"hashLHS\": %!j, \"hashRHS\": %!j, ", b->opt->hashLHS, b->opt->hashRHS); if(0==rc && b->opt->nameLHS){ rc = fdb__outf(b, "\"nameLHS\": %!j, ", b->opt->nameLHS); } if(0==rc && b->opt->nameRHS){ rc = fdb__outf(b, "\"nameRHS\": %!j, ", b->opt->nameRHS); } if(0==rc){ rc = fdb__out(b, "\"diff\":[", 8); } return rc; } static int fdb__json1_skip(fsl_dibu * const b, uint32_t n){ return fdb__outf(b, "1,%" PRIu32 ",\n", n); } static int fdb__json1_common(fsl_dibu * const b, fsl_dline const * pLine){ int rc = fdb__out(b, "2,",2); if(!rc) { rc = fdb__outj(b, pLine->z, (int)pLine->n); if(!rc) rc = fdb__out(b, ",\n",2); } return rc; } static int fdb__json1_insertion(fsl_dibu * const b, fsl_dline const * pLine){ int rc = fdb__out(b, "3,",2); if(!rc){ rc = fdb__outj(b, pLine->z, (int)pLine->n); if(!rc) rc = fdb__out(b, ",\n",2); } return rc; } static int fdb__json1_deletion(fsl_dibu * const b, fsl_dline const * pLine){ int rc = fdb__out(b, "4,",2); if(!rc){ rc = fdb__outj(b, pLine->z, (int)pLine->n); if(!rc) rc = fdb__out(b, ",\n",2); } return rc; } static int fdb__json1_replacement(fsl_dibu * const b, fsl_dline const * lineLhs, fsl_dline const * lineRhs) { int rc = fdb__out(b, "5,[\"\",",6); if(!rc) rc = fdb__outf(b,"%!.*j", (int)lineLhs->n, lineLhs->z); if(!rc) rc = fdb__out(b, ",", 1); if(!rc) rc = fdb__outf(b,"%!.*j", (int)lineRhs->n, lineRhs->z); if(!rc) rc = fdb__out(b, ",\"\"],\n",6); return rc; } static int fdb__json1_edit(fsl_dibu * const b, fsl_dline const * lineLHS, fsl_dline const * lineRHS){ int rc = 0; int i,x; fsl_dline_change chng = fsl_dline_change_empty; #define RC if(rc) goto end rc = fdb__out(b, "5,[", 3); RC; fsl_dline_change_spans(lineLHS, lineRHS, &chng); for(i=x=0; i<(int)chng.n; i++){ if(i>0){ rc = fdb__out(b, ",", 1); RC; } rc = fdb__outj(b, lineLHS->z + x, (int)chng.a[i].iStart1 - x); RC; x = chng.a[i].iStart1; rc = fdb__out(b, ",", 1); RC; rc = fdb__outj(b, lineLHS->z + x, (int)chng.a[i].iLen1); RC; x += chng.a[i].iLen1; rc = fdb__out(b, ",", 1); RC; rc = fdb__outj(b, lineRHS->z + chng.a[i].iStart2, (int)chng.a[i].iLen2); RC; } rc = fdb__out(b, ",", 1); RC; rc = fdb__outj(b, lineLHS->z + x, (int)(lineLHS->n - x)); RC; rc = fdb__out(b, "],\n",3); RC; end: return rc; #undef RC } static int fdb__json1_finish(fsl_dibu * const b){ return fdb__out(b, "0]}", 3); } static fsl_dibu * fsl__diff_builder_json1(void){ fsl_dibu * rc = fsl_dibu_alloc(0); if(rc){ rc->chunkHeader = NULL; rc->start = fdb__json1_start; rc->skip = fdb__json1_skip; rc->common = fdb__json1_common; rc->insertion = fdb__json1_insertion; rc->deletion = fdb__json1_deletion; rc->replacement = fdb__json1_replacement; rc->edit = fdb__json1_edit; rc->finish = fdb__json1_finish; rc->finalize = fsl_dibu_finalizer; assert(!rc->pimpl); assert(0==rc->implFlags); assert(0==rc->lnLHS); assert(0==rc->lnRHS); assert(NULL==rc->opt); } return rc; } /** State for the text-mode unified(-ish) diff builder. We do some hoop-jumping here in order to combine runs of delete/insert pairs into a group of deletes followed by a group of inserts. It's a cosmetic detail only but it makes for more readable output. */ struct DiBuUnified { /** True if currently processing a block of deletes, else false. */ bool deleting; /** Buffer for insertion lines which are part of delete/insert pairs. */ fsl_buffer bufIns; }; typedef struct DiBuUnified DiBuUnified; #define UIMPL(V) DiBuUnified * const V = (DiBuUnified*)b->pimpl /** If utxt diff builder b has any INSERT lines to flush, this flushes them. Sets b->impl->deleting to false. Returns non-0 on output error. */ static int fdb__utxt_flush_ins(fsl_dibu * const b){ int rc = 0; UIMPL(p); p->deleting = false; if(p->bufIns.used>0){ rc = fdb__out(b, fsl_buffer_cstr(&p->bufIns), p->bufIns.used); fsl_buffer_reuse(&p->bufIns); } return rc; } static int fdb__utxt_start(fsl_dibu * const b){ int rc = 0; UIMPL(p); p->deleting = false; if(p->bufIns.mem) fsl_buffer_reuse(&p->bufIns); else fsl_buffer_reserve(&p->bufIns, 1024 * 2); if(0==(FSL_DIFF2_NOINDEX & b->opt->diffFlags)){ rc = fdb__outf(b,"Index: %s\n%.66c\n", b->opt->nameLHS/*RHS?*/, '='); } if(0==rc){ rc = fdb__outf(b, "--- %s\n+++ %s\n", b->opt->nameLHS, b->opt->nameRHS); } return rc; } static int fdb__utxt_chunkHeader(fsl_dibu* const b, uint32_t lnnoLHS, uint32_t linesLHS, uint32_t lnnoRHS, uint32_t linesRHS ){ /* Ticket 746ebbe86c20b5c0f96cdadd19abd8284770de16: Annoying cosmetic bug: the libf impl of this diff will sometimes render two directly-adjecent chunks with a separator, e.g.: */ // $ f-vdiff --format u 072d63965188 a725befe5863 -l '*vdiff*' | head -30 // Index: f-apps/f-vdiff.c // ================================================================== // --- f-apps/f-vdiff.c // +++ f-apps/f-vdiff.c // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 36 36 fsl_buffer fname; // 37 37 fsl_buffer fcontent1; // 38 38 fsl_buffer fcontent2; // 39 39 fsl_buffer fhash; // 40 40 fsl_list globs; // 41 + fsl_dibu_opt diffOpt; // 42 + fsl_diff_builder * diffBuilder; // 41 43 } VDiffApp = { // 42 44 NULL/*glob*/, // 43 45 5/*contextLines*/, // 44 46 0/*sbsWidth*/, // 45 47 0/*diffFlags*/, // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 46 48 0/*brief*/, // 47 49 fsl_buffer_empty_m/*fname*/, // 48 50 fsl_buffer_empty_m/*fcontent1*/, /* Note now the chunks before/after the second ~~~ line are consecutive lines of code. In fossil(1) that case is accounted for in the higher-level diff engine, which can not only collapse adjacent blocks but also does the rendering of chunk headers in that main algorithm (something we cannot do in the library because we need the fsl_dibu to be able to output to arbitrary destinations). We can only _partially_ account for it here, eliminating the extraneous ~~~ line when we're in line-number mode. In non-line-number mode we have to output the chunk header as-is. If we skip it then the _previous_ chunk header, if any, will contain incorrect numbers for the chunk, invaliding the diff for purposes of tools which import unified-format diffs. */ int rc = fdb__utxt_flush_ins(b); if(0==rc){ if(FSL_DIFF2_LINE_NUMBERS & b->opt->diffFlags){ rc = (lnnoLHS == b->lnLHS+1 && lnnoRHS == b->lnRHS+1) ? 0 : fdb__outf(b, "%.40c\n", '~'); }else{ rc = fdb__outf(b, "@@ -%" PRIu32 ",%" PRIu32 " +%" PRIu32 ",%" PRIu32 " @@\n", lnnoLHS, linesLHS, lnnoRHS, linesRHS); } } return rc; } static int fdb__utxt_skip(fsl_dibu * const b, uint32_t n){ //MARKER(("SKIP\n")); int rc = fdb__utxt_flush_ins(b); b->lnLHS += n; b->lnRHS += n; return rc; } /** Outputs line numbers, if configured to, to b->opt->out. - 2 line numbers = common lines - lnL only = deletion - lnR only = insertion */ static int fdb__utxt_lineno(fsl_dibu * const b, uint32_t lnL, uint32_t lnR){ int rc = 0; if(FSL_DIFF2_LINE_NUMBERS & b->opt->diffFlags){ UIMPL(p); if(lnL){ // common or delete rc = fdb__outf(b, "%s%6" PRIu32 "%s ", (lnR ? "" : b->opt->ansiColor.deletion), lnL, (lnR ? "" : b->opt->ansiColor.reset)); }else if(p->deleting){ // insert during deletion grouping rc = fsl_buffer_append(&p->bufIns, " ", 7); }else{ // insert w/o deleting grouping rc = fdb__out(b, " ", 7); } if(0==rc){ if(!lnL && lnR && p->deleting){ // insert during deletion grouping rc = fsl_buffer_appendf(&p->bufIns, "%s%6" PRIu32 "%s ", b->opt->ansiColor.insertion, lnR, b->opt->ansiColor.reset); }else if(lnR){ // common or insert w/o deletion grouping. rc = fdb__outf(b, "%s%6" PRIu32 "%s ", (lnL ? "" : b->opt->ansiColor.insertion), lnR, (lnL ? "" : b->opt->ansiColor.reset)); }else{ // deletion rc = fdb__out(b, " ", 7); } } } return rc; } static int fdb__utxt_common(fsl_dibu * const b, fsl_dline const * pLine){ //MARKER(("COMMON\n")); int rc = fdb__utxt_flush_ins(b); if(0==rc){ ++b->lnLHS; ++b->lnRHS; rc = fdb__utxt_lineno(b, b->lnLHS, b->lnRHS); } return rc ? rc : fdb__outf(b, " %.*s\n", (int)pLine->n, pLine->z); } static int fdb__utxt_insertion(fsl_dibu * const b, fsl_dline const * pLine){ //MARKER(("INSERT\n")); int rc; ++b->lnRHS; rc = fdb__utxt_lineno(b, 0, b->lnRHS); if(0==rc){ UIMPL(p); if(p->deleting){ rc = fsl_buffer_appendf(&p->bufIns, "%s+%.*s%s\n", b->opt->ansiColor.insertion, (int)pLine->n, pLine->z, b->opt->ansiColor.reset); }else{ rc = fdb__outf(b, "%s+%.*s%s\n", b->opt->ansiColor.insertion, (int)pLine->n, pLine->z, b->opt->ansiColor.reset); } } return rc; } static int fdb__utxt_deletion(fsl_dibu * const b, fsl_dline const * pLine){ //MARKER(("DELETE\n")); UIMPL(p); int rc = p->deleting ? 0 : fdb__utxt_flush_ins(b); if(0==rc){ p->deleting = true; ++b->lnLHS; rc = fdb__utxt_lineno(b, b->lnLHS, 0); } return rc ? rc : fdb__outf(b, "%s-%.*s%s\n", b->opt->ansiColor.deletion, (int)pLine->n, pLine->z, b->opt->ansiColor.reset); } static int fdb__utxt_replacement(fsl_dibu * const b, fsl_dline const * lineLhs, fsl_dline const * lineRhs) { //MARKER(("REPLACE\n")); int rc = b->deletion(b, lineLhs); if(0==rc) rc = b->insertion(b, lineRhs); return rc; } static int fdb__utxt_edit(fsl_dibu * const b, fsl_dline const * lineLhs, fsl_dline const * lineRhs){ //MARKER(("EDIT\n")); int rc = b->deletion(b, lineLhs); if(0==rc) rc = b->insertion(b, lineRhs); return rc; } static int fdb__utxt_finish(fsl_dibu * const b){ int rc = fdb__utxt_flush_ins(b); UIMPL(p); fsl_buffer_reuse(&p->bufIns); return rc; } static void fdb__utxt_finalize(fsl_dibu * const b){ UIMPL(p); fsl_buffer_clear(&p->bufIns); fsl_free(b); } static fsl_dibu * fsl__diff_builder_utxt(void){ const DiBuUnified DiBuUnified_empty = { false, fsl_buffer_empty_m }; fsl_dibu * rc = fsl_dibu_alloc(sizeof(DiBuUnified)); if(!rc) return NULL; assert(NULL!=rc->pimpl); assert(NULL==rc->finally); *((DiBuUnified*)rc->pimpl) = DiBuUnified_empty; rc->chunkHeader = fdb__utxt_chunkHeader; rc->start = fdb__utxt_start; rc->skip = fdb__utxt_skip; rc->common = fdb__utxt_common; rc->insertion = fdb__utxt_insertion; rc->deletion = fdb__utxt_deletion; rc->replacement = fdb__utxt_replacement; rc->edit = fdb__utxt_edit; rc->finish = fdb__utxt_finish; rc->finalize = fdb__utxt_finalize; return rc; } #undef UIMPL struct DiBuTcl { /** Buffer for TCL-format string conversion */ fsl_buffer str; }; typedef struct DiBuTcl DiBuTcl; static const DiBuTcl DiBuTcl_empty = {fsl_buffer_empty_m}; #define BR_OPEN if(FSL_DIBU_TCL_BRACES & b->implFlags) \ rc = fdb__out(b, "{", 1) #define BR_CLOSE if(FSL_DIBU_TCL_BRACES & b->implFlags) \ rc = fdb__out(b, "}", 1) #define DTCL_BUFFER(B) &((DiBuTcl*)(B)->pimpl)->str static int fdb__outtcl(fsl_dibu * const b, char const *z, unsigned int n, char chAppend ){ int rc; fsl_buffer * const o = DTCL_BUFFER(b); fsl_buffer_reuse(o); rc = fsl_buffer_append_tcl_literal(o, (b->implFlags & FSL_DIBU_TCL_BRACES_ESC), z, n); if(0==rc) rc = fdb__out(b, (char const *)o->mem, o->used); if(chAppend && 0==rc) rc = fdb__out(b, &chAppend, 1); return rc; } static int fdb__tcl_start(fsl_dibu * const b){ int rc = 0; fsl_buffer_reuse(DTCL_BUFFER(b)); if(1==++b->fileCount && FSL_DIBU_TCL_TK==(b->implFlags & FSL_DIBU_TCL_TK)){ rc = fdb__out(b, "set difftxt {\n", -1); } if(0==rc && b->fileCount>1) rc = fdb__out(b, "\n", 1); if(0==rc && b->opt->nameLHS){ char const * zRHS = b->opt->nameRHS ? b->opt->nameRHS : b->opt->nameLHS; BR_OPEN; if(0==rc) rc = fdb__out(b, "FILE ", 5); if(0==rc) rc = fdb__outtcl(b, b->opt->nameLHS, (unsigned)fsl_strlen(b->opt->nameLHS), ' '); if(0==rc) rc = fdb__outtcl(b, zRHS, (unsigned)fsl_strlen(zRHS), 0); if(0==rc) {BR_CLOSE;} if(0==rc) rc = fdb__out(b, "\n", 1); } return rc; } static int fdb__tcl_skip(fsl_dibu * const b, uint32_t n){ int rc = 0; BR_OPEN; if(0==rc) rc = fdb__outf(b, "SKIP %" PRIu32, n); if(0==rc) {BR_CLOSE;} if(0==rc) rc = fdb__outf(b, "\n", 1); return rc; } static int fdb__tcl_common(fsl_dibu * const b, fsl_dline const * pLine){ int rc = 0; BR_OPEN; if(0==rc) rc = fdb__out(b, "COM ", 5); if(0==rc) rc= fdb__outtcl(b, pLine->z, pLine->n, 0); if(0==rc) {BR_CLOSE;} if(0==rc) rc = fdb__outf(b, "\n", 1); return rc; } static int fdb__tcl_insertion(fsl_dibu * const b, fsl_dline const * pLine){ int rc = 0; BR_OPEN; if(0==rc) rc = fdb__out(b, "INS ", 5); if(0==rc) rc = fdb__outtcl(b, pLine->z, pLine->n, 0); if(0==rc) {BR_CLOSE;} if(0==rc) rc = fdb__outf(b, "\n", 1); return rc; } static int fdb__tcl_deletion(fsl_dibu * const b, fsl_dline const * pLine){ int rc = 0; BR_OPEN; if(0==rc) rc = fdb__out(b, "DEL ", 5); if(0==rc) rc = fdb__outtcl(b, pLine->z, pLine->n, 0); if(0==rc) {BR_CLOSE;} if(0==rc) rc = fdb__outf(b, "\n", 1); return rc; } static int fdb__tcl_replacement(fsl_dibu * const b, fsl_dline const * lineLhs, fsl_dline const * lineRhs) { int rc = 0; BR_OPEN; if(0==rc) rc = fdb__out(b, "EDIT \"\" ", 8); if(0==rc) rc = fdb__outtcl(b, lineLhs->z, lineLhs->n, ' '); if(0==rc) rc = fdb__outtcl(b, lineRhs->z, lineRhs->n, 0); if(0==rc) {BR_CLOSE;} if(0==rc) rc = fdb__outf(b, "\n", 1); return rc; } static int fdb__tcl_edit(fsl_dibu * const b, fsl_dline const * lineLHS, fsl_dline const * lineRHS){ int rc = 0; int i, x; fsl_dline_change chng = fsl_dline_change_empty; #define RC if(rc) goto end BR_OPEN; rc = fdb__out(b, "EDIT", 4); RC; fsl_dline_change_spans(lineLHS, lineRHS, &chng); for(i=x=0; i<chng.n; i++){ rc = fdb__out(b, " ", 1); RC; rc = fdb__outtcl(b, lineLHS->z + x, chng.a[i].iStart1 - x, ' '); RC; x = chng.a[i].iStart1; rc = fdb__outtcl(b, lineLHS->z + x, chng.a[i].iLen1, ' '); RC; x += chng.a[i].iLen1; rc = fdb__outtcl(b, lineRHS->z + chng.a[i].iStart2, chng.a[i].iLen2, 0); RC; } assert(0==rc); if( x < lineLHS->n ){ rc = fdb__out(b, " ", 1); RC; rc = fdb__outtcl(b, lineLHS->z + x, lineLHS->n - x, 0); RC; } BR_CLOSE; RC; rc = fdb__out(b, "\n", 1); end: #undef RC return rc; } static int fdb__tcl_finish(fsl_dibu * const b fsl__unused){ int rc = 0; #if 0 BR_CLOSE; if(0==rc && FSL_DIBU_TCL_BRACES & b->implFlags){ rc = fdb__out(b, "\n", 1); } #endif return rc; } static int fdb__tcl_finally(fsl_dibu * const b){ int rc = 0; if(FSL_DIBU_TCL_TK==(b->implFlags & FSL_DIBU_TCL_TK)){ extern char const * fsl_difftk_cstr; if(!b->fileCount) rc = fdb__out(b,"set difftxt {\n",-1); if(0==rc) rc = fdb__out(b, "}\nset fossilcmd {}\n", -1); if(0==rc) rc = fdb__out(b, fsl_difftk_cstr, -1); } return rc; } #undef BR_OPEN #undef BR_CLOSE static void fdb__tcl_finalize(fsl_dibu * const b){ fsl_buffer_clear( &((DiBuTcl*)b->pimpl)->str ); *b = fsl_dibu_empty; fsl_free(b); } static fsl_dibu * fsl__diff_builder_tcl(void){ fsl_dibu * rc = fsl_dibu_alloc((fsl_size_t)sizeof(DiBuTcl)); if(rc){ rc->chunkHeader = NULL; rc->start = fdb__tcl_start; rc->skip = fdb__tcl_skip; rc->common = fdb__tcl_common; rc->insertion = fdb__tcl_insertion; rc->deletion = fdb__tcl_deletion; rc->replacement = fdb__tcl_replacement; rc->edit = fdb__tcl_edit; rc->finish = fdb__tcl_finish; rc->finally = fdb__tcl_finally; rc->finalize = fdb__tcl_finalize; assert(0!=rc->pimpl); DiBuTcl * const dbt = (DiBuTcl*)rc->pimpl; *dbt = DiBuTcl_empty; if(fsl_buffer_reserve(&dbt->str, 120)){ rc->finalize(rc); rc = 0; } } return rc; } static int fdb__splittxt_mod(fsl_dibu * const b, char ch){ assert(2==b->passNumber); return fdb__outf(b, " %c ", ch); } static int fdb__splittxt_lineno(fsl_dibu * const b, DiffCounter const * const sst, bool isLeft, uint32_t n){ assert(2==b->passNumber); int const col = isLeft ? DICO_NUM1 : DICO_NUM2; return n ? fdb__outf(b, "%*" PRIu32 " ", sst->maxWidths[col], n) : fdb__outf(b, "%.*c ", sst->maxWidths[col], ' '); } static int fdb__splittxt_start(fsl_dibu * const b){ int rc = 0; if(1==b->passNumber){ DICOSTATE(sst); *sst = DiffCounter_empty; ++b->fileCount; return rc; } if(b->fileCount>1){ rc = fdb__out(b, "\n", 1); } if(0==rc){ fsl_dibu_opt const * const o = b->opt; if(o->nameLHS || o->nameRHS || o->hashLHS || o->hashRHS){ rc = fdb__outf(b, "--- %s%s%s\n+++ %s%s%s\n", o->nameLHS ? o->nameLHS : "", (o->nameLHS && o->hashLHS) ? " " : "", o->hashLHS ? o->hashLHS : "", o->nameRHS ? o->nameRHS : "", (o->nameRHS && o->hashRHS) ? " " : "", o->hashRHS ? o->hashRHS : ""); } } return rc; } static int fdb__splittxt_skip(fsl_dibu * const b, uint32_t n){ b->lnLHS += n; b->lnRHS += n; if(1==b->passNumber) return 0; DICOSTATE(sst); int const maxWidth1 = maxColWidth(b, sst, DICO_TEXT1); int const maxWidth2 = maxColWidth(b, sst, DICO_TEXT2); return fdb__outf(b, "%.*c %.*c %.*c %.*c\n", sst->maxWidths[DICO_NUM1], '~', maxWidth1, '~', sst->maxWidths[DICO_NUM2], '~', maxWidth2, '~'); } static int fdb__splittxt_color(fsl_dibu * const b, int modType){ char const *z = 0; switch(modType){ case (int)'i': z = b->opt->ansiColor.insertion; break; case (int)'d': z = b->opt->ansiColor.deletion; break; case (int)'r'/*replacement*/: case (int)'e': z = b->opt->ansiColor.edit; break; case 0: z = b->opt->ansiColor.reset; break; default: assert(!"invalid color op!"); } return z&&*z ? fdb__outf(b, "%s", z) : 0; } static int fdb__splittxt_side(fsl_dibu * const b, DiffCounter * const sst, bool isLeft, fsl_dline const * const pLine){ int rc = fdb__splittxt_lineno(b, sst, isLeft, pLine ? (isLeft ? b->lnLHS : b->lnRHS) : 0U); if(0==rc){ uint32_t const w = maxColWidth(b, sst, isLeft ? DICO_TEXT1 : DICO_TEXT2); if(pLine){ fsl_size_t const nU = /* Measure column width in UTF8 characters, not bytes! */ fsl_strlen_utf8(pLine->z, (fsl_int_t)pLine->n); rc = fdb__outf(b, "%#.*s", (int)(w < nU ? w : nU), pLine->z); if(0==rc && w>nU){ rc = fdb__outf(b, "%.*c", (int)(w - nU), ' '); } }else{ rc = fdb__outf(b, "%.*c", (int)w, ' '); } if(0==rc && !isLeft) rc = fdb__out(b, "\n", 1); } return rc; } static int fdb__splittxt_common(fsl_dibu * const b, fsl_dline const * const pLine){ int rc = 0; DICOSTATE(sst); ++b->lnLHS; ++b->lnRHS; if(1==b->passNumber){ fdb__dico_update_maxlen(sst, DICO_TEXT1, pLine->z, pLine->n); fdb__dico_update_maxlen(sst, DICO_TEXT2, pLine->z, pLine->n); return 0; } rc = fdb__splittxt_side(b, sst, true, pLine); if(0==rc) rc = fdb__splittxt_mod(b, ' '); if(0==rc) rc = fdb__splittxt_side(b, sst, false, pLine); return rc; } static int fdb__splittxt_insertion(fsl_dibu * const b, fsl_dline const * const pLine){ int rc = 0; DICOSTATE(sst); ++b->lnRHS; if(1==b->passNumber){ fdb__dico_update_maxlen(sst, DICO_TEXT1, pLine->z, pLine->n); return rc; } rc = fdb__splittxt_color(b, 'i'); if(0==rc) rc = fdb__splittxt_side(b, sst, true, NULL); if(0==rc) rc = fdb__splittxt_mod(b, '>'); if(0==rc) rc = fdb__splittxt_side(b, sst, false, pLine); if(0==rc) rc = fdb__splittxt_color(b, 0); return rc; } static int fdb__splittxt_deletion(fsl_dibu * const b, fsl_dline const * const pLine){ int rc = 0; DICOSTATE(sst); ++b->lnLHS; if(1==b->passNumber){ fdb__dico_update_maxlen(sst, DICO_TEXT2, pLine->z, pLine->n); return rc; } rc = fdb__splittxt_color(b, 'd'); if(0==rc) rc = fdb__splittxt_side(b, sst, true, pLine); if(0==rc) rc = fdb__splittxt_mod(b, '<'); if(0==rc) rc = fdb__splittxt_side(b, sst, false, NULL); if(0==rc) rc = fdb__splittxt_color(b, 0); return rc; } static int fdb__splittxt_replacement(fsl_dibu * const b, fsl_dline const * const lineLhs, fsl_dline const * const lineRhs) { #if 0 int rc = b->deletion(b, lineLhs); if(0==rc) rc = b->insertion(b, lineRhs); return rc; #else int rc = 0; DICOSTATE(sst); ++b->lnLHS; ++b->lnRHS; if(1==b->passNumber){ fdb__dico_update_maxlen(sst, DICO_TEXT1, lineLhs->z, lineLhs->n); fdb__dico_update_maxlen(sst, DICO_TEXT2, lineRhs->z, lineRhs->n); return 0; } rc = fdb__splittxt_color(b, 'e'); if(0==rc) rc = fdb__splittxt_side(b, sst, true, lineLhs); if(0==rc) rc = fdb__splittxt_mod(b, '|'); if(0==rc) rc = fdb__splittxt_side(b, sst, false, lineRhs); if(0==rc) rc = fdb__splittxt_color(b, 0); return rc; #endif } static int fdb__splittxt_finish(fsl_dibu * const b){ int rc = 0; if(1==b->passNumber){ DICOSTATE(sst); uint32_t ln = b->lnLHS; /* Calculate width of line number columns. */ sst->maxWidths[DICO_NUM1] = sst->maxWidths[DICO_NUM2] = 1; for(; ln>=10; ln/=10) ++sst->maxWidths[DICO_NUM1]; ln = b->lnRHS; for(; ln>=10; ln/=10) ++sst->maxWidths[DICO_NUM2]; } return rc; } static void fdb__splittxt_finalize(fsl_dibu * const b){ *b = fsl_dibu_empty; fsl_free(b); } static fsl_dibu * fsl__diff_builder_splittxt(void){ fsl_dibu * rc = fsl_dibu_alloc((fsl_size_t)sizeof(DiffCounter)); if(rc){ rc->twoPass = true; rc->chunkHeader = NULL; rc->start = fdb__splittxt_start; rc->skip = fdb__splittxt_skip; rc->common = fdb__splittxt_common; rc->insertion = fdb__splittxt_insertion; rc->deletion = fdb__splittxt_deletion; rc->replacement = fdb__splittxt_replacement; rc->edit = fdb__splittxt_replacement; rc->finish = fdb__splittxt_finish; rc->finalize = fdb__splittxt_finalize; assert(0!=rc->pimpl); DiffCounter * const sst = (DiffCounter*)rc->pimpl; *sst = DiffCounter_empty; } return rc; } int fsl_dibu_factory( fsl_dibu_e type, fsl_dibu **pOut ){ int rc = FSL_RC_TYPE; fsl_dibu * (*factory)(void) = NULL; switch(type){ case FSL_DIBU_DEBUG: factory = fsl__diff_builder_debug; break; case FSL_DIBU_JSON1: factory = fsl__diff_builder_json1; break; case FSL_DIBU_UNIFIED_TEXT: factory = fsl__diff_builder_utxt; break; case FSL_DIBU_TCL: factory = fsl__diff_builder_tcl; break; case FSL_DIBU_SPLIT_TEXT: factory = fsl__diff_builder_splittxt; break; case FSL_DIBU_INVALID: break; } if(NULL!=factory){ *pOut = factory(); rc = *pOut ? 0 : FSL_RC_OOM; } return rc; } #undef MARKER #undef DICOSTATE #undef DTCL_BUFFER