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,&param);
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,&param);
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,&param);
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