1 /**************************************************************************
2 ** This file is part of LiteIDE
3 **
4 ** Copyright (c) 2011-2019 LiteIDE. All rights reserved.
5 **
6 ** This library is free software; you can redistribute it and/or
7 ** modify it under the terms of the GNU Lesser General Public
8 ** License as published by the Free Software Foundation; either
9 ** version 2.1 of the License, or (at your option) any later version.
10 **
11 ** This library 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 GNU
14 ** Lesser General Public License for more details.
15 **
16 ** In addition, as a special exception, that plugins developed for LiteIDE,
17 ** are allowed to remain closed sourced and can be distributed under any license .
18 ** These rights are included in the file LGPL_EXCEPTION.txt in this package.
19 **
20 **************************************************************************/
21 // Module: editorutil.cpp
22 // Creator: visualfc <visualfc@gmail.com>
23
24 #include "editorutil.h"
25 #include "difflib.h"
26 #include "diff_match_patch/diff_match_patch.h"
27
28 #include <QTextBlock>
29 #include <QTextCursor>
30 #include <QPlainTextEdit>
31 //lite_memory_check_begin
32 #if defined(WIN32) && defined(_MSC_VER) && defined(_DEBUG)
33 #define _CRTDBG_MAP_ALLOC
34 #include <stdlib.h>
35 #include <crtdbg.h>
36 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
37 #define new DEBUG_NEW
38 #endif
39 //lite_memory_check_end
40
41 typedef void (*EnumEditorProc)(QTextCursor &cursor, QTextBlock &block, void *param);
42
EnumEditor(QPlainTextEdit * ed,EnumEditorProc proc,void * param)43 void EditorUtil::EnumEditor(QPlainTextEdit *ed, EnumEditorProc proc, void *param)
44 {
45 if (!ed) {
46 return;
47 }
48 QTextCursor cur = ed->textCursor();
49 cur.beginEditBlock();
50 if (cur.hasSelection()) {
51 QTextBlock begin = ed->document()->findBlock(cur.selectionStart());
52 QTextBlock end = ed->document()->findBlock(cur.selectionEnd());
53 if (end.position() == cur.selectionEnd()) {
54 end = end.previous();
55 }
56 QTextBlock block = begin;
57 do {
58 if (block.text().length() > 0) {
59 proc(cur,block,param);
60 }
61 block = block.next();
62 } while(block.isValid() && block.position() <= end.position());
63 } else {
64 QTextBlock block = cur.block();
65 proc(cur,block,param);
66 }
67 cur.endEditBlock();
68 ed->setTextCursor(cur);
69 }
70
71 struct InsertHeadParam
72 {
73 QString tag;
74 bool blockStart;
75 };
76
insertHead(QTextCursor & cur,QTextBlock & block,void * param)77 static void insertHead(QTextCursor &cur, QTextBlock &block, void *param)
78 {
79 InsertHeadParam *ip = (InsertHeadParam*)param;
80 if (ip->blockStart) {
81 cur.setPosition(block.position());
82 } else {
83 QString text = block.text();
84 foreach(QChar c, text) {
85 if (!c.isSpace()) {
86 cur.setPosition(block.position()+text.indexOf(c));
87 break;
88 }
89 }
90 }
91 cur.insertText(ip->tag);
92 }
93
94 struct RemoveHeadParam
95 {
96 QStringList tags;
97 bool blockStart;
98 };
99
100 struct SwitchHeadParam
101 {
102 QString tagAdd;
103 QStringList tagRemove;
104 bool blockStart;
105 };
106
removeHead(QTextCursor & cur,QTextBlock & block,void * param)107 static void removeHead(QTextCursor &cur, QTextBlock &block, void *param)
108 {
109 RemoveHeadParam *ip = (RemoveHeadParam*)param;
110 if (ip->blockStart) {
111 cur.setPosition(block.position());
112 } else {
113 QString text = block.text();
114 foreach(QChar c, text) {
115 if (!c.isSpace()) {
116 cur.setPosition(block.position()+text.indexOf(c));
117 break;
118 }
119 }
120 }
121 foreach (QString tag, ip->tags) {
122 if (cur.block().text().startsWith(tag)) {
123 cur.setPosition(block.position());
124 cur.movePosition(QTextCursor::NextCharacter,
125 QTextCursor::KeepAnchor,
126 tag.length());
127 cur.removeSelectedText();
128 break;
129 }
130 }
131 }
132
switchHead(QTextCursor & cur,QTextBlock & block,void * param)133 static void switchHead(QTextCursor &cur, QTextBlock &block, void *param)
134 {
135 SwitchHeadParam *ip = (SwitchHeadParam*)param;
136 if (ip->blockStart) {
137 cur.setPosition(block.position());
138 } else {
139 QString text = block.text();
140 foreach(QChar c, text) {
141 if (!c.isSpace()) {
142 cur.setPosition(block.position()+text.indexOf(c));
143 break;
144 }
145 }
146 }
147 foreach (QString tag, ip->tagRemove) {
148 if (cur.block().text().startsWith(tag)) {
149 cur.setPosition(block.position());
150 cur.movePosition(QTextCursor::NextCharacter,
151 QTextCursor::KeepAnchor,
152 tag.length());
153 cur.removeSelectedText();
154 return;
155 }
156 }
157 cur.insertText(ip->tagAdd);
158 }
159
160
InsertHead(QPlainTextEdit * ed,const QString & tag,bool blockStart)161 void EditorUtil::InsertHead(QPlainTextEdit *ed, const QString &tag, bool blockStart)
162 {
163 InsertHeadParam param = {tag,blockStart};
164 EnumEditor(ed,&insertHead,¶m);
165 }
166
RemoveHead(QPlainTextEdit * ed,const QStringList & tags,bool blockStart)167 void EditorUtil::RemoveHead(QPlainTextEdit *ed, const QStringList &tags, bool blockStart)
168 {
169 RemoveHeadParam param = {tags,blockStart};
170 EnumEditor(ed,&removeHead,¶m);
171 }
172
SwitchHead(QPlainTextEdit * ed,const QString & tagAdd,const QStringList & tagRemove,bool blockStart)173 void EditorUtil::SwitchHead(QPlainTextEdit *ed, const QString &tagAdd, const QStringList &tagRemove, bool blockStart)
174 {
175 SwitchHeadParam param = {tagAdd,tagRemove,blockStart};
176 EnumEditor(ed,&switchHead,¶m);
177 }
178
MarkSelection(QPlainTextEdit * ed,const QString & mark1,const QString & mark2)179 void EditorUtil::MarkSelection(QPlainTextEdit *ed, const QString &mark1, const QString &mark2)
180 {
181 if (!ed) {
182 return;
183 }
184 QTextCursor cur = ed->textCursor();
185 cur.beginEditBlock();
186 if (cur.hasSelection()) {
187 QTextBlock begin = ed->document()->findBlock(cur.selectionStart());
188 QTextBlock end = ed->document()->findBlock(cur.selectionEnd());
189 if (end.position() == cur.selectionEnd()) {
190 end = end.previous();
191 }
192 int n1 = cur.selectionStart();
193 int n2 = cur.selectionEnd();
194 QTextBlock block = begin;
195 do {
196 int c1 = block.position();
197 int c2 = c1+block.text().length();
198 if (block.position() == begin.position() && c1 < n1) {
199 c1 = n1;
200 }
201 if (c2 > n2) {
202 c2 = n2;
203 }
204 if (c2 > c1) {
205 if (!mark1.isEmpty()) {
206 cur.setPosition(c1);
207 cur.insertText(mark1);
208 n2 += mark1.length();
209 }
210 if (!mark2.isEmpty()) {
211 cur.setPosition(c2+mark1.length());
212 cur.insertText(mark2);
213 n2 += mark2.length();
214 }
215 }
216 block = block.next();
217 } while(block.isValid() && block.position() <= end.position());
218 } else {
219 int pos = cur.position();
220 cur.insertText(mark1+mark2);
221 cur.setPosition(pos+mark1.length());
222 }
223 cur.endEditBlock();
224 ed->setTextCursor(cur);
225 }
226
MarkSelection(QPlainTextEdit * ed,const QString & mark)227 void EditorUtil::MarkSelection(QPlainTextEdit *ed, const QString &mark)
228 {
229 EditorUtil::MarkSelection(ed,mark,mark);
230 }
231
232 // use diff_match_patch
findBlockPos(const QString & orgText,const QString & newText,int pos)233 static int findBlockPos(const QString &orgText, const QString &newText, int pos )
234 {
235 diff_match_patch dmp;
236 QList<Diff> diffs = dmp.diff_main(orgText,newText,false);
237 return dmp.diff_xIndex(diffs,pos);
238 }
239
checkTowStringHead(const QString & s1,const QString & s2,int & nSameOfHead)240 static bool checkTowStringHead(const QString &s1, const QString &s2, int &nSameOfHead)
241 {
242 int size1 = s1.size();
243 int size2 = s2.size();
244 int size = qMin(size1,size2);
245 for(nSameOfHead = 0; nSameOfHead < size; nSameOfHead++) {
246 if (s1[nSameOfHead] != s2[nSameOfHead]) {
247 return false;
248 }
249 }
250 return true;
251 }
252
findBlockNumber(const QList<int> & offsetList,int offsetBase,int blockNumber)253 static int findBlockNumber(const QList<int> &offsetList, int offsetBase, int blockNumber)
254 {
255 for (int i = offsetList.size()-1; i>=0; i--) {
256 int iv = offsetList[i];
257 if (iv == -1) {
258 continue;
259 }
260 if (blockNumber >= iv) {
261 if (blockNumber == iv) {
262 return offsetBase+i;
263 } else {
264 if (i == offsetList.size()-1) {
265 return offsetBase+i+blockNumber-iv;
266 }
267 int offset = i;
268 int v0 = iv;
269 for (int j = i+1; j < offsetList.size(); j++) {
270 if (offsetList[j] != -1) {
271 break;
272 }
273 offset++;
274 v0++;
275 if (v0 == blockNumber) {
276 break;
277 }
278 }
279 return offsetBase+offset;
280 }
281 }
282 }
283 return blockNumber;
284 }
285
loadDiff(QTextCursor & cursor,const QString & diff)286 void EditorUtil::loadDiff(QTextCursor &cursor, const QString &diff)
287 {
288 //save org block
289 int orgBlockNumber = cursor.blockNumber();
290 int orgPosInBlock = cursor.positionInBlock();
291 QString orgBlockText = cursor.block().text();
292 int curBlockNumber = orgBlockNumber;
293
294 //load diff
295 QRegExp reg("@@\\s+\\-(\\d+),?(\\d+)?\\s+\\+(\\d+),?(\\d+)?\\s+@@");
296 QTextBlock block;
297 int line = -1;
298 int line_add = 0;
299 int block_number = 0;
300
301 QList<int> offsetList;
302 int offsetBase = 0;
303
304 QStringList diffList = diff.split("\n");
305 QString s;
306 int size = diffList.size();
307
308 for (int i = 0; i < size; i++) {
309 s = diffList[i];
310 if (s.length() == 0) {
311 continue;
312 }
313
314 QChar ch = s.at(0);
315 if (ch == '@') {
316 if (reg.indexIn(s) == 0) {
317 int s1 = reg.cap(1).toInt();
318 int s2 = reg.cap(2).toInt();
319 //int n1 = reg.cap(3).toInt();
320 int n2 = reg.cap(4).toInt();
321 line = line_add+s1;
322 //block = cursor.document()->findBlockByNumber(line-1);
323 line_add += n2-s2;//n2+n1-(s2+s1);
324 block_number = line-1;
325
326 //find block number
327 curBlockNumber = findBlockNumber(offsetList,offsetBase,curBlockNumber);
328 offsetBase = block_number;
329 offsetList.clear();
330 for (int i = 0; i <= s2; i++) {
331 offsetList.append(offsetBase+i);
332 }
333 continue;
334 }
335 }
336 if (line == -1) {
337 continue;
338 }
339 if (ch == '+') {
340 offsetList.insert(block_number-offsetBase,-1);
341 block = cursor.document()->findBlockByNumber(block_number);
342 if (!block.isValid()) {
343 cursor.movePosition(QTextCursor::End);
344 cursor.insertBlock();
345 cursor.insertText(s.mid(1));
346 } else {
347 cursor.setPosition(block.position());
348 cursor.insertText(s.mid(1));
349 cursor.insertBlock();
350 }
351 block_number++;
352 } else if (ch == '-') {
353 //check modify current block text
354 if ((i < (size-1)) && diffList[i+1].startsWith("+")) {
355 block = cursor.document()->findBlockByNumber(block_number);
356 QString nextText = diffList[i+1].mid(1);
357 int nSameOfHead = 0;
358 bool checkSame = checkTowStringHead(nextText.simplified(),block.text().simplified(),nSameOfHead);
359 if (checkSame || (nSameOfHead >= 4) ) {
360 cursor.setPosition(block.position());
361 cursor.insertText(nextText);
362 cursor.setPosition(block.position()+nextText.length());
363 cursor.setPosition(block.position()+block.text().length(), QTextCursor::KeepAnchor);
364 cursor.removeSelectedText();
365 i++;
366 block_number++;
367 continue;
368 }
369 }
370
371 offsetList.removeAt(block_number-offsetBase);
372 block = cursor.document()->findBlockByNumber(block_number);
373 cursor.setPosition(block.position());
374 if (block.next().isValid()) {
375 cursor.setPosition(block.next().position(), QTextCursor::KeepAnchor);
376 cursor.removeSelectedText();
377 } else {
378 cursor.movePosition(QTextCursor::EndOfBlock);
379 cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
380 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
381 cursor.removeSelectedText();
382 }
383 } else if (ch == ' ') {
384 block_number++;
385 } else if (ch == '\\') {
386 //skip comment
387 }
388 }
389 //find block number
390 curBlockNumber = findBlockNumber(offsetList,offsetBase,curBlockNumber);
391 //load cur block
392 block = cursor.document()->findBlockByNumber(curBlockNumber);
393 if (block.isValid()) {
394 cursor.setPosition(block.position());
395 int column = findBlockPos(orgBlockText,block.text(),orgPosInBlock);
396 if (column > 0) {
397 cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
398 }
399 }
400 }
401
unifiedDiffText(const QString & text1,const QString & text2)402 QString EditorUtil::unifiedDiffText(const QString &text1, const QString &text2)
403 {
404 return UnifiedDiffLines(text1,text2);
405 }
406
407
408