2 * @file MergeDocDiffSync.cpp
4 * @brief Code to layout diff blocks, to find matching lines, and insert ghost lines
14 #include "stringdiffs.h"
19 static char THIS_FILE[] = __FILE__;
25 * @brief Divide diff blocks to match lines in diff blocks.
27 void CMergeDoc::AdjustDiffBlocks()
30 int nDiffCount = m_diffList.GetSize();
32 // Go through and do our best to line up lines within each diff block
33 // between left side and right side
36 for (nDiff = 0; nDiff < nDiffCount; nDiff++)
38 const DIFFRANGE & diffrange = *m_diffList.DiffRangeAt(nDiff);
39 // size map correctly (it will hold one entry for each left-side line
40 int nlines0 = diffrange.end[0] - diffrange.begin[0] + 1;
41 int nlines1 = diffrange.end[1] - diffrange.begin[1] + 1;
42 if (nlines0>0 && nlines1>0)
44 // Call worker to do all lines in block
45 int lo0 = 0, hi0 = nlines0-1;
46 int lo1 = 0, hi1 = nlines1-1;
48 diffmap.InitDiffMap(nlines0);
49 AdjustDiffBlock(diffmap, diffrange, lo0, hi0, lo1, hi1);
53 int line0, line1, lineend0;
54 for (line0 = 0, line1 = 0; line0 < nlines0;)
56 const int map_line0 = diffmap.m_map[line0];
57 if (map_line0 == DiffMap::GHOST_MAP_ENTRY ||
58 map_line0 == DiffMap::BAD_MAP_ENTRY)
60 for (lineend0 = line0; lineend0 < nlines0; lineend0++)
62 if (map_line0 != DiffMap::GHOST_MAP_ENTRY &&
63 map_line0 != DiffMap::BAD_MAP_ENTRY)
66 dr.begin[0] = diffrange.begin[0] + line0;
67 dr.begin[1] = diffrange.begin[1] + line1;
68 dr.end[0] = diffrange.begin[0] + lineend0 - 1;
69 dr.end[1] = dr.begin[1] - 1;
70 dr.blank[0] = dr.blank[1] = -1;
72 newDiffList.AddDiff(dr);
77 if (map_line0 > line1)
79 dr.begin[0] = diffrange.begin[0] + line0;
80 dr.begin[1] = diffrange.begin[1] + line1;
81 dr.end[0] = dr.begin[0] - 1;
82 dr.end[1] = diffrange.begin[1] + map_line0 - 1;
83 dr.blank[0] = dr.blank[1] = -1;
85 newDiffList.AddDiff(dr);
89 for (lineend0 = line0 + 1; lineend0 < nlines0; lineend0++)
91 if (map_line0 != diffmap.m_map[lineend0 - 1] + 1)
94 dr.begin[0] = diffrange.begin[0] + line0;
95 dr.begin[1] = diffrange.begin[1] + line1;
96 dr.end[0] = diffrange.begin[0] + lineend0 - 1;
97 dr.end[1] = diffrange.begin[1] + diffmap.m_map[lineend0 - 1];
98 dr.blank[0] = dr.blank[1] = -1;
99 dr.op = diffrange.op == OP_TRIVIAL ? OP_TRIVIAL : OP_DIFF;
100 newDiffList.AddDiff(dr);
102 line1 = diffmap.m_map[lineend0 - 1] + 1;
107 dr.begin[0] = diffrange.begin[0] + line0;
108 dr.begin[1] = diffrange.begin[1] + line1;
109 dr.end[0] = dr.begin[0] - 1;
110 dr.end[1] = diffrange.begin[1] + hi1;
111 dr.blank[0] = dr.blank[1] = -1;
112 dr.op = diffrange.op == OP_TRIVIAL ? OP_TRIVIAL : OP_DIFF;
113 newDiffList.AddDiff(dr);
118 newDiffList.AddDiff(diffrange);
122 // recreate m_diffList
124 nDiffCount = newDiffList.GetSize();
125 for (nDiff = 0; nDiff < nDiffCount; nDiff++)
126 m_diffList.AddDiff(*newDiffList.DiffRangeAt(nDiff));
130 * @brief Return cost of making strings equal
132 * The cost of making them equal is the measure of their dissimilarity
133 * which is their Levenshtein distance.
135 int CMergeDoc::GetMatchCost(const String &sLine0, const String &sLine1)
137 DIFFOPTIONS diffOptions = {0};
138 m_diffWrapper.GetOptions(&diffOptions);
144 // Options that affect comparison
145 bool casitive = !diffOptions.bIgnoreCase;
146 int xwhite = diffOptions.nIgnoreWhitespace;
147 int breakType = GetBreakType(); // whitespace only or include punctuation
148 bool byteColoring = GetByteColoringOption();
150 std::vector<wdiff> worddiffs;
151 sd_ComputeWordDiffs(2, str, casitive, xwhite, breakType, byteColoring, &worddiffs);
154 for (std::vector<wdiff>::const_iterator it = worddiffs.begin(); it != worddiffs.end(); ++it)
156 nDiffLenSum += it->end[0] - it->begin[0] + 1;
159 return -static_cast<int>(sLine0.length() - nDiffLenSum);
163 * @brief Map lines from left to right for specified range in diff block, as best we can
164 * Map left side range [lo0;hi0] to right side range [lo1;hi1]
165 * (ranges include ends)
168 * Find best match, and use that to split problem into two parts (above & below match)
169 * and call ourselves recursively to solve each smaller problem
171 void CMergeDoc::AdjustDiffBlock(DiffMap & diffMap, const DIFFRANGE & diffrange, int lo0, int hi0, int lo1, int hi1)
173 // Map & lo & hi numbers are all relative to this block
174 // We need to know offsets to find actual line strings from buffer
175 int offset0 = diffrange.begin[0];
176 int offset1 = diffrange.begin[1];
178 // # of lines on left and right
179 int lines0 = hi0 - lo0 + 1;
180 int lines1 = hi1 - lo1 + 1;
183 ASSERT(lines0 > 0 && lines1 > 0);
185 // shortcut special case
186 if (lines0 == 1 && lines1 == 1)
188 diffMap.m_map[lo0] = hi1;
192 // Bail out if range is large
193 if (lines0 > 15 || lines1 > 15)
195 // Do simple 1:1 mapping
196 // but try to stay within original block
197 // Cannot be outside both sides, so just test for outside one side
201 // right side is off the bottom
202 // so put it as close to bottom as possible
203 tlo = hi1 - (hi0-lo0);
208 // put it as close to top as possible
210 thi = lo1 + (hi0-lo0);
213 for (int w=0; w<lines0; ++w)
216 diffMap.m_map[w] = w + tlo;
218 diffMap.m_map[w] = DiffMap::GHOST_MAP_ENTRY;
224 int ibest=-1, isavings=0x7fffffff, itarget=-1;
225 CString sLine0, sLine1;
226 for (int i=lo0; i<=hi0; ++i)
228 m_ptBuf[0]->GetLine(offset0 + i, sLine0);
229 for (int j=lo1; j<=hi1; ++j)
231 m_ptBuf[1]->GetLine(offset1 + j, sLine1);
232 int savings = GetMatchCost((LPCTSTR)sLine0, (LPCTSTR)sLine1);
234 // Need to penalize assignments that push us outside the box
235 // further than is required
236 if (savings < isavings)
244 // I think we have to find at least one
245 // because cost (Levenshtein distance) is less than or equal to maxlen
246 // so cost is always nonnegative
249 ASSERT(diffMap.m_map[ibest] == DiffMap::BAD_MAP_ENTRY);
251 diffMap.m_map[ibest] = itarget;
253 // Half of the remaining problem is below our match
258 AdjustDiffBlock(diffMap, diffrange, lo0, ibest-1, lo1, itarget-1);
262 // No target space for the ones below our match
263 for (int x = lo0; x < ibest; ++x)
265 diffMap.m_map[x] = DiffMap::GHOST_MAP_ENTRY;
270 // Half of the remaining problem is above our match
275 AdjustDiffBlock(diffMap, diffrange, ibest + 1, hi0,
280 // No target space for the ones above our match
281 for (int x = ibest + 1; x <= hi0; ++x)
283 diffMap.m_map[x] = DiffMap::GHOST_MAP_ENTRY;