Fixed issue #4126: Capitalize the first letter in the Push dialog
[TortoiseGit.git] / src / TortoiseProc / DiffLinesForStaging.cpp
blob34c874beb5c93d31a64f841d3409ae3c5f556af5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2016, 2019-2021 - TortoiseGit
4 // Copyright (C) 2007, 2009-2013 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "DiffLinesForStaging.h"
22 #include <regex>
24 CDiffLinesForStaging::CDiffLinesForStaging(const char* text, int numLines, int firstLineSelected, int lastLineSelected)
26 const char* ptr = text;
27 size_t last_i = 0; // beginning of the last line processed
28 int state = 0;
29 int oldCount = 0, newCount = 0;
30 bool fileWasAdded = false, fileWasDeleted = false;
31 DiffLineTypes lastType = DiffLineTypes::DEFAULT; // type of the last line processed, necessary to handle "No newline at end of file" correctly
33 for (size_t i = 0; ;)
35 int eol_len = 0;
36 if (!ptr[i])
37 break;
38 if (ptr[i] == '\r' && ptr[i + 1] == '\n' ||
39 ptr[i] == '\n' && ptr[i + 1] == '\r')
40 eol_len = 2;
41 else if (ptr[i] == '\r' || ptr[i] == '\n')
42 eol_len = 1;
43 else
45 ++i;
46 continue;
49 size_t linebuflen = i - last_i + eol_len + 1;
50 auto line = std::string_view(text + last_i, linebuflen - 1);
51 last_i = i + eol_len;
52 i += eol_len;
54 DiffLineTypes type = DiffLineTypes::DEFAULT;
55 bool exitLoop = false;
56 // This algorithm is based off CPatch::ParsePatchFile
57 while (!exitLoop)
59 exitLoop = true;
60 switch (state)
62 case 0:
63 if (strncmp(line.data(), "diff ", 5) == 0)
65 type = DiffLineTypes::COMMAND;
66 state = 1;
68 break;
69 case 1:
70 if (strncmp(line.data(), "--- ", 4) == 0)
72 type = DiffLineTypes::HEADER;
73 state = 2;
75 else
76 type = DiffLineTypes::COMMENT;
77 break;
78 case 2:
79 if (strncmp(line.data(), "+++ ", 4) == 0)
81 type = DiffLineTypes::HEADER;
82 state = 3;
84 break;
85 case 3:
86 if (strncmp(line.data(), "@@ ", 3) == 0)
88 if (GetOldAndNewLinesCountFromHunk(line, &oldCount, &newCount, true))
90 type = DiffLineTypes::POSITION;
91 fileWasAdded = oldCount == 0;
92 fileWasDeleted = newCount == 0;
93 state = 4;
96 break;
97 case 4:
98 if (strncmp(line.data(), "+", 1) == 0)
100 --newCount;
101 type = DiffLineTypes::ADDED;
103 else if (strncmp(line.data(), "-", 1) == 0)
105 --oldCount;
106 type = DiffLineTypes::DELETED;
108 else if (strncmp(line.data(), " ", 1) == 0)
110 --oldCount;
111 --newCount;
112 type = DiffLineTypes::DEFAULT;
114 // Regardless of locales, a "\ No newline at end of file" will always begin with "\ " and 10 is a sane minimum length to look for
115 else if (linebuflen - 1 >= 10 && strncmp(line.data(), "\\ ", 2) == 0)
117 if (fileWasAdded)
118 type = DiffLineTypes::NO_NEWLINE_NEWFILE;
119 else if (fileWasDeleted)
120 type = DiffLineTypes::NO_NEWLINE_OLDFILE;
121 else if (oldCount == 0 && newCount > 0)
122 type = DiffLineTypes::NO_NEWLINE_OLDFILE;
123 else if (newCount == 0 && oldCount > 0)
124 type = DiffLineTypes::NO_NEWLINE_NEWFILE;
125 else if (oldCount == 0 && newCount == 0)
127 if (lastType == DiffLineTypes::ADDED)
128 type = DiffLineTypes::NO_NEWLINE_NEWFILE;
129 else if (lastType == DiffLineTypes::DELETED)
130 type = DiffLineTypes::NO_NEWLINE_OLDFILE;
131 else if (lastType == DiffLineTypes::DEFAULT)
132 type = DiffLineTypes::NO_NEWLINE_BOTHFILES;
135 else if (strncmp(line.data(), "@@ ", 3) == 0)
137 state = 3;
138 exitLoop = false;
140 else
142 state = 0;
143 exitLoop = false;
145 } // switch (state)
146 } // while (!exitLoop)
147 lastType = type;
148 m_linevec.emplace_back(line, type);
149 } // for (int i = 0; ;)
150 m_linevec.emplace_back("", DiffLineTypes::DEFAULT); // Scintilla considers an empty document to have 1 line, so add an extra line here
151 VERIFY(m_linevec.size() == static_cast<size_t>(numLines));
152 m_firstLineSelected = firstLineSelected;
153 m_lastLineSelected = lastLineSelected;
156 int CDiffLinesForStaging::GetFirstLineNumberSelected() const
158 return m_firstLineSelected;
161 int CDiffLinesForStaging::GetLastLineNumberSelected() const
163 return m_lastLineSelected;
166 // Includes EOL characters of all lines
167 std::string CDiffLinesForStaging::GetFullTextOfSelectedLines() const
169 return GetFullTextOfLineRange(GetFirstLineNumberSelected(), GetLastLineNumberSelected());
172 // Includes EOL characters of all lines
173 std::string CDiffLinesForStaging::GetFullTextOfLineRange(int startline, int endline) const
175 if (endline < startline)
176 return {};
177 std::string ret;
178 for (int i = startline; i <= endline; ++i)
179 ret.append(m_linevec.at(i).sLine);
180 return ret;
183 DiffLineTypes CDiffLinesForStaging::GetLineType(int line) const
185 return m_linevec.at(line).type;
188 bool CDiffLinesForStaging::IsNoNewlineComment(int line) const
190 auto type = m_linevec.at(line).type;
191 return type == DiffLineTypes::NO_NEWLINE_OLDFILE || type == DiffLineTypes::NO_NEWLINE_NEWFILE || type == DiffLineTypes::NO_NEWLINE_BOTHFILES;
194 // Includes EOL characters
195 std::string_view CDiffLinesForStaging::GetFullLineByLineNumber(int line) const
197 return m_linevec.at(line).sLine;
200 int CDiffLinesForStaging::GetLastDocumentLine() const
202 return static_cast<int>(m_linevec.size() - 1);
205 // Takes a buffer containing the first line of a hunk (@@xxxxxx@@)
206 // Parses it to extract its old lines count and new lines count and passes them back to the given oldCount and newCount.
207 // Returns true if the line matches the expected format, false otherwise (what should never happen).
208 // If allowSingleLine is false, returns false for hunks missing the start line number in one or both sides (e.g. @@ -x +y,z @@)
209 bool CDiffLinesForStaging::GetOldAndNewLinesCountFromHunk(std::string_view hunk, int* oldCount, int* newCount, bool allowSingleLine)
211 std::string pattern = allowSingleLine ? "^@@ -(?:\\d+?,)?(\\d+?) \\+(?:\\d+?,)?(\\d+?) @@" : "^@@ -\\d+?,(\\d+?) \\+\\d+?,(\\d+?) @@";
212 std::regex rx(pattern, std::regex_constants::ECMAScript);
213 std::match_results<std::string_view::const_iterator> match;
215 if (!std::regex_search(hunk.begin(), hunk.end(), match, rx) || match.size() != 3) // this should never happen
216 return false;
217 *oldCount = StrToIntA(match[1].str().c_str());
218 *newCount = StrToIntA(match[2].str().c_str());
219 return true;