KiCad PCB EDA Suite
Loading...
Searching...
No Matches
text_diff.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <git/text_diff.h>
21
22#include <algorithm>
23#include <cstring>
24
25namespace KIGIT
26{
27
28std::vector<wxString> SplitTextLines( const std::string& aContent )
29{
30 std::vector<wxString> lines;
31
32 auto appendLine = [&lines]( std::string aLine )
33 {
34 if( !aLine.empty() && aLine.back() == '\r' )
35 aLine.pop_back();
36
37 wxString text = wxString::FromUTF8( aLine.c_str(), aLine.size() );
38
39 if( text.IsEmpty() && !aLine.empty() )
40 text = wxString::From8BitData( aLine.c_str(), aLine.size() );
41
42 lines.push_back( text );
43 };
44
45 size_t start = 0;
46
47 while( start <= aContent.size() )
48 {
49 size_t end = aContent.find( '\n', start );
50
51 if( end == std::string::npos )
52 {
53 // A trailing newline does not add an empty last line.
54 if( start < aContent.size() )
55 appendLine( aContent.substr( start ) );
56
57 break;
58 }
59
60 appendLine( aContent.substr( start, end - start ) );
61 start = end + 1;
62 }
63
64 return lines;
65}
66
67
68bool TextLooksBinary( const std::string& aContent )
69{
70 // Bounded deliberately. std::string::find would scan the whole file first.
71 size_t probe = std::min<size_t>( aContent.size(), 8000 );
72
73 return std::memchr( aContent.data(), '\0', probe ) != nullptr;
74}
75
76
77std::vector<TEXT_DIFF_LINE> BuildTextDiff( const std::vector<wxString>& aOld, const std::vector<wxString>& aNew )
78{
79 std::vector<TEXT_DIFF_LINE> result;
80
81 size_t prefix = 0;
82
83 while( prefix < aOld.size() && prefix < aNew.size() && aOld[prefix] == aNew[prefix] )
84 prefix++;
85
86 size_t suffix = 0;
87
88 while( suffix < aOld.size() - prefix && suffix < aNew.size() - prefix
89 && aOld[aOld.size() - 1 - suffix] == aNew[aNew.size() - 1 - suffix] )
90 {
91 suffix++;
92 }
93
94 for( size_t i = 0; i < prefix; i++ )
95 result.push_back( { TEXT_DIFF_KIND::CONTEXT, aOld[i] } );
96
97 size_t n = aOld.size() - prefix - suffix;
98 size_t m = aNew.size() - prefix - suffix;
99
100 // A pair too big for an O(n*m) table takes the cruder path below instead. Dividing rather
101 // than multiplying keeps the check from overflowing.
102 const size_t maxCells = 4000000;
103 const size_t rows = n + 1;
104 const size_t stride = m + 1;
105
106 if( n > 0 && m > 0 && rows <= maxCells / stride )
107 {
108 std::vector<unsigned> lcs( rows * stride, 0 );
109
110 auto cell = [&lcs, stride]( size_t aRow, size_t aCol ) -> unsigned&
111 {
112 return lcs[aRow * stride + aCol];
113 };
114
115 for( size_t i = n; i-- > 0; )
116 {
117 for( size_t j = m; j-- > 0; )
118 {
119 if( aOld[prefix + i] == aNew[prefix + j] )
120 cell( i, j ) = cell( i + 1, j + 1 ) + 1;
121 else
122 cell( i, j ) = std::max( cell( i + 1, j ), cell( i, j + 1 ) );
123 }
124 }
125
126 size_t i = 0, j = 0;
127
128 while( i < n && j < m )
129 {
130 if( aOld[prefix + i] == aNew[prefix + j] )
131 {
132 result.push_back( { TEXT_DIFF_KIND::CONTEXT, aOld[prefix + i] } );
133 i++;
134 j++;
135 }
136 else if( cell( i + 1, j ) >= cell( i, j + 1 ) )
137 {
138 result.push_back( { TEXT_DIFF_KIND::REMOVED, aOld[prefix + i] } );
139 i++;
140 }
141 else
142 {
143 result.push_back( { TEXT_DIFF_KIND::ADDED, aNew[prefix + j] } );
144 j++;
145 }
146 }
147
148 for( ; i < n; i++ )
149 result.push_back( { TEXT_DIFF_KIND::REMOVED, aOld[prefix + i] } );
150
151 for( ; j < m; j++ )
152 result.push_back( { TEXT_DIFF_KIND::ADDED, aNew[prefix + j] } );
153 }
154 else
155 {
156 for( size_t i = 0; i < n; i++ )
157 result.push_back( { TEXT_DIFF_KIND::REMOVED, aOld[prefix + i] } );
158
159 for( size_t j = 0; j < m; j++ )
160 result.push_back( { TEXT_DIFF_KIND::ADDED, aNew[prefix + j] } );
161 }
162
163 for( size_t i = aOld.size() - suffix; i < aOld.size(); i++ )
164 result.push_back( { TEXT_DIFF_KIND::CONTEXT, aOld[i] } );
165
166 return result;
167}
168
169} // namespace KIGIT
std::vector< wxString > SplitTextLines(const std::string &aContent)
Tolerates CRLF endings and content that is not valid UTF-8.
Definition text_diff.cpp:28
bool TextLooksBinary(const std::string &aContent)
Definition text_diff.cpp:68
std::vector< TEXT_DIFF_LINE > BuildTextDiff(const std::vector< wxString > &aOld, const std::vector< wxString > &aNew)
Every line of both sides appears once. There are no hunks and nothing is elided.
Definition text_diff.cpp:77
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.