1/*
2 SPDX-FileCopyrightText: 2011 Dominik Haumann <dhaumann@kde.org>
3 SPDX-FileCopyrightText: 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
4 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
5 SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
6 SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
7 SPDX-FileCopyrightText: 2023 Waqar Ahmed <waqar.17a@gmail.com>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
12#include "kateundo.h"
13
14#include "katebuffer.h"
15#include "katedocument.h"
16#include "kateundomanager.h"
17#include "kateview.h"
18
19#include <ktexteditor/cursor.h>
20#include <ktexteditor/view.h>
21
22KateUndoGroup::KateUndoGroup(const KTextEditor::Cursor cursorPosition,
23 KTextEditor::Range selection,
24 const QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> &secondary)
25 : m_undoSelection(selection)
26 , m_redoSelection(-1, -1, -1, -1)
27 , m_undoCursor(cursorPosition)
28 , m_undoSecondaryCursors(secondary)
29 , m_redoCursor(-1, -1)
30{
31}
32
33void KateUndoGroup::undo(KateUndoManager *manager, KTextEditor::ViewPrivate *view)
34{
35 if (m_items.empty()) {
36 return;
37 }
38
39 manager->startUndo();
40
41 auto doc = manager->document();
42 auto updateDocLine = [doc](const UndoItem &item) {
43 Kate::TextLine tl = doc->plainKateTextLine(i: item.line);
44 tl.markAsModified(modified: item.lineModFlags.testFlag(flag: UndoItem::UndoLine1Modified));
45 tl.markAsSavedOnDisk(savedOnDisk: item.lineModFlags.testFlag(flag: UndoItem::UndoLine1Saved));
46 doc->buffer().setLineMetaData(line: item.line, textLine: tl);
47 };
48
49 for (auto rit = m_items.rbegin(); rit != m_items.rend(); ++rit) {
50 auto &item = *rit;
51 switch (item.type) {
52 case UndoItem::editInsertText:
53 doc->editRemoveText(line: item.line, col: item.col, len: item.text.size());
54 updateDocLine(item);
55 break;
56 case UndoItem::editRemoveText:
57 doc->editInsertText(line: item.line, col: item.col, s: item.text);
58 updateDocLine(item);
59 break;
60 case UndoItem::editWrapLine:
61 doc->editUnWrapLine(line: item.line, removeLine: item.newLine, length: item.len);
62 updateDocLine(item);
63 break;
64 case UndoItem::editUnWrapLine: {
65 doc->editWrapLine(line: item.line, col: item.col, newLine: item.removeLine);
66 updateDocLine(item);
67
68 auto next = doc->plainKateTextLine(i: item.line + 1);
69 next.markAsModified(modified: item.lineModFlags.testFlag(flag: UndoItem::UndoLine2Modified));
70 next.markAsSavedOnDisk(savedOnDisk: item.lineModFlags.testFlag(flag: UndoItem::UndoLine2Saved));
71 doc->buffer().setLineMetaData(line: item.line + 1, textLine: next);
72 } break;
73 case UndoItem::editInsertLine:
74 doc->editRemoveLine(line: item.line);
75 break;
76 case UndoItem::editRemoveLine:
77 doc->editInsertLine(line: item.line, s: item.text);
78 updateDocLine(item);
79 break;
80 case UndoItem::editMarkLineAutoWrapped:
81 doc->editMarkLineAutoWrapped(line: item.line, autowrapped: item.autowrapped);
82 break;
83 case UndoItem::editInvalid:
84 break;
85 }
86 }
87
88 if (view != nullptr) {
89 if (m_undoSelection.isValid()) {
90 view->setSelection(m_undoSelection);
91 } else {
92 view->removeSelection();
93 }
94 view->clearSecondaryCursors();
95 view->addSecondaryCursorsWithSelection(cursorsWithSelection: m_undoSecondaryCursors);
96
97 if (m_undoCursor.isValid()) {
98 view->setCursorPosition(m_undoCursor);
99 }
100 }
101
102 manager->endUndo();
103}
104
105void KateUndoGroup::redo(KateUndoManager *manager, KTextEditor::ViewPrivate *view)
106{
107 if (m_items.empty()) {
108 return;
109 }
110
111 manager->startUndo();
112
113 auto doc = manager->document();
114 auto updateDocLine = [doc](const UndoItem &item) {
115 Kate::TextLine tl = doc->plainKateTextLine(i: item.line);
116 tl.markAsModified(modified: item.lineModFlags.testFlag(flag: UndoItem::RedoLine1Modified));
117 tl.markAsSavedOnDisk(savedOnDisk: item.lineModFlags.testFlag(flag: UndoItem::RedoLine1Saved));
118 doc->buffer().setLineMetaData(line: item.line, textLine: tl);
119 };
120
121 for (auto &item : m_items) {
122 switch (item.type) {
123 case UndoItem::editInsertText:
124 doc->editInsertText(line: item.line, col: item.col, s: item.text);
125 updateDocLine(item);
126 break;
127 case UndoItem::editRemoveText:
128 doc->editRemoveText(line: item.line, col: item.col, len: item.text.size());
129 updateDocLine(item);
130 break;
131 case UndoItem::editWrapLine: {
132 doc->editWrapLine(line: item.line, col: item.col, newLine: item.newLine);
133 updateDocLine(item);
134
135 Kate::TextLine next = doc->plainKateTextLine(i: item.line + 1);
136 next.markAsModified(modified: item.lineModFlags.testFlag(flag: UndoItem::RedoLine2Modified));
137 next.markAsSavedOnDisk(savedOnDisk: item.lineModFlags.testFlag(flag: UndoItem::RedoLine2Saved));
138 doc->buffer().setLineMetaData(line: item.line + 1, textLine: next);
139 } break;
140 case UndoItem::editUnWrapLine:
141 doc->editUnWrapLine(line: item.line, removeLine: item.removeLine, length: item.len);
142 updateDocLine(item);
143 break;
144 case UndoItem::editInsertLine:
145 doc->editInsertLine(line: item.line, s: item.text);
146 updateDocLine(item);
147 break;
148 case UndoItem::editRemoveLine:
149 doc->editRemoveLine(line: item.line);
150 break;
151 case UndoItem::editMarkLineAutoWrapped:
152 doc->editMarkLineAutoWrapped(line: item.line, autowrapped: item.autowrapped);
153 break;
154 case UndoItem::editInvalid:
155 break;
156 }
157 }
158
159 if (view != nullptr) {
160 if (m_redoSelection.isValid()) {
161 view->setSelection(m_redoSelection);
162 } else {
163 view->removeSelection();
164 }
165 view->clearSecondaryCursors();
166 view->addSecondaryCursorsWithSelection(cursorsWithSelection: m_redoSecondaryCursors);
167
168 if (m_redoCursor.isValid()) {
169 view->setCursorPosition(m_redoCursor);
170 }
171 }
172
173 manager->endUndo();
174}
175
176void KateUndoGroup::editEnd(const KTextEditor::Cursor cursorPosition,
177 KTextEditor::Range selectionRange,
178 const QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> &secondaryCursors)
179{
180 m_redoCursor = cursorPosition;
181 m_redoSecondaryCursors = secondaryCursors;
182 m_redoSelection = selectionRange;
183}
184
185static bool mergeUndoItems(UndoItem &base, const UndoItem &u)
186{
187 if (base.type == UndoItem::editInsertText && u.type == UndoItem::editWrapLine) {
188 // merge insert text full line + wrap line
189 if (base.col == 0 && base.line == u.line && base.col + base.text.length() == u.col && u.newLine) {
190 base.type = UndoItem::editInsertLine;
191 base.lineModFlags.setFlag(flag: UndoItem::RedoLine1Modified);
192 return true;
193 }
194 }
195
196 if (base.type == UndoItem::editRemoveText && base.type == u.type) {
197 if (base.line == u.line && base.col == (u.col + u.text.size())) {
198 base.text.prepend(s: u.text);
199 base.col = u.col;
200 return true;
201 }
202 }
203
204 if (base.type == UndoItem::editInsertText && base.type == u.type) {
205 if (base.line == u.line && (base.col + base.text.size()) == u.col) {
206 base.text += u.text;
207 return true;
208 }
209 }
210
211 return false;
212}
213
214void KateUndoGroup::addItem(UndoItem u)
215{
216 // try to merge, do that only for equal types, inside mergeWith we do hard casts
217 if (!m_items.empty() && mergeUndoItems(base&: m_items.back(), u)) {
218 return;
219 }
220
221 // default: just add new item unchanged
222 m_items.push_back(x: std::move(u));
223}
224
225bool KateUndoGroup::merge(KateUndoGroup *newGroup, bool complex)
226{
227 if (m_safePoint) {
228 return false;
229 }
230
231 if (newGroup->isOnlyType(type: singleType()) || complex) {
232 // Take all of its items first -> last
233 for (auto &item : newGroup->m_items) {
234 addItem(u: item);
235 }
236 newGroup->m_items.clear();
237
238 if (newGroup->m_safePoint) {
239 safePoint();
240 }
241
242 m_redoCursor = newGroup->m_redoCursor;
243 m_redoSecondaryCursors = newGroup->m_redoSecondaryCursors;
244 m_redoSelection = newGroup->m_redoSelection;
245 // m_redoSelections = newGroup->m_redoSelections;
246
247 return true;
248 }
249
250 return false;
251}
252
253void KateUndoGroup::safePoint(bool safePoint)
254{
255 m_safePoint = safePoint;
256}
257
258void KateUndoGroup::flagSavedAsModified()
259{
260 for (UndoItem &item : m_items) {
261 if (item.lineModFlags.testFlag(flag: UndoItem::UndoLine1Saved)) {
262 item.lineModFlags.setFlag(flag: UndoItem::UndoLine1Saved, on: false);
263 item.lineModFlags.setFlag(flag: UndoItem::UndoLine1Modified, on: true);
264 }
265
266 if (item.lineModFlags.testFlag(flag: UndoItem::UndoLine2Saved)) {
267 item.lineModFlags.setFlag(flag: UndoItem::UndoLine2Saved, on: false);
268 item.lineModFlags.setFlag(flag: UndoItem::UndoLine2Modified, on: true);
269 }
270
271 if (item.lineModFlags.testFlag(flag: UndoItem::RedoLine1Saved)) {
272 item.lineModFlags.setFlag(flag: UndoItem::RedoLine1Saved, on: false);
273 item.lineModFlags.setFlag(flag: UndoItem::RedoLine1Modified, on: true);
274 }
275
276 if (item.lineModFlags.testFlag(flag: UndoItem::RedoLine2Saved)) {
277 item.lineModFlags.setFlag(flag: UndoItem::RedoLine2Saved, on: false);
278 item.lineModFlags.setFlag(flag: UndoItem::RedoLine2Modified, on: true);
279 }
280 }
281}
282
283static void updateUndoSavedOnDiskFlag(UndoItem &item, QBitArray &lines)
284{
285 const int line = item.line;
286 if (line >= lines.size()) {
287 lines.resize(size: line + 1);
288 }
289
290 const bool wasBitSet = lines.testBit(i: line);
291 if (!wasBitSet) {
292 lines.setBit(line);
293 }
294
295 auto &lineFlags = item.lineModFlags;
296
297 switch (item.type) {
298 case UndoItem::editInsertText:
299 case UndoItem::editRemoveText:
300 case UndoItem::editRemoveLine:
301 if (!wasBitSet) {
302 lineFlags.setFlag(flag: UndoItem::UndoLine1Modified, on: false);
303 lineFlags.setFlag(flag: UndoItem::UndoLine1Saved, on: true);
304 }
305 break;
306 case UndoItem::editWrapLine:
307 if (lineFlags.testFlag(flag: UndoItem::UndoLine1Modified) && !wasBitSet) {
308 lineFlags.setFlag(flag: UndoItem::UndoLine1Modified, on: false);
309 lineFlags.setFlag(flag: UndoItem::UndoLine1Saved, on: true);
310 }
311 break;
312 case UndoItem::editUnWrapLine:
313 if (line + 1 >= lines.size()) {
314 lines.resize(size: line + 2);
315 }
316 if (lineFlags.testFlag(flag: UndoItem::UndoLine1Modified) && !wasBitSet) {
317 lineFlags.setFlag(flag: UndoItem::UndoLine1Modified, on: false);
318 lineFlags.setFlag(flag: UndoItem::UndoLine1Saved, on: true);
319 }
320
321 if (lineFlags.testFlag(flag: UndoItem::UndoLine2Modified) && !lines.testBit(i: line + 1)) {
322 lines.setBit(line + 1);
323
324 lineFlags.setFlag(flag: UndoItem::UndoLine2Modified, on: false);
325 lineFlags.setFlag(flag: UndoItem::UndoLine2Saved, on: true);
326 }
327 break;
328 case UndoItem::editInsertLine:
329 case UndoItem::editMarkLineAutoWrapped:
330 case UndoItem::editInvalid:
331 break;
332 }
333}
334
335void KateUndoGroup::markUndoAsSaved(QBitArray &lines)
336{
337 for (auto rit = m_items.rbegin(); rit != m_items.rend(); ++rit) {
338 updateUndoSavedOnDiskFlag(item&: *rit, lines);
339 }
340}
341
342static void updateRedoSavedOnDiskFlag(UndoItem &item, QBitArray &lines)
343{
344 const int line = item.line;
345 if (line >= lines.size()) {
346 lines.resize(size: line + 1);
347 }
348
349 const bool wasBitSet = lines.testBit(i: line);
350 if (!wasBitSet) {
351 lines.setBit(line);
352 }
353 auto &lineFlags = item.lineModFlags;
354
355 switch (item.type) {
356 case UndoItem::editInsertText:
357 case UndoItem::editRemoveText:
358 case UndoItem::editInsertLine:
359 lineFlags.setFlag(flag: UndoItem::RedoLine1Modified, on: false);
360 lineFlags.setFlag(flag: UndoItem::RedoLine1Saved, on: true);
361 break;
362 case UndoItem::editUnWrapLine:
363 if (lineFlags.testFlag(flag: UndoItem::RedoLine1Modified) && !wasBitSet) {
364 lineFlags.setFlag(flag: UndoItem::RedoLine1Modified, on: false);
365 lineFlags.setFlag(flag: UndoItem::RedoLine1Saved, on: true);
366 }
367 break;
368 case UndoItem::editWrapLine:
369 if (line + 1 >= lines.size()) {
370 lines.resize(size: line + 2);
371 }
372
373 if (lineFlags.testFlag(flag: UndoItem::RedoLine1Modified) && !wasBitSet) {
374 lineFlags.setFlag(flag: UndoItem::RedoLine1Modified, on: false);
375 lineFlags.setFlag(flag: UndoItem::RedoLine1Saved, on: true);
376 }
377
378 if (lineFlags.testFlag(flag: UndoItem::RedoLine2Modified) && !lines.testBit(i: line + 1)) {
379 lineFlags.setFlag(flag: UndoItem::RedoLine2Modified, on: false);
380 lineFlags.setFlag(flag: UndoItem::RedoLine2Saved, on: true);
381 }
382 break;
383 case UndoItem::editRemoveLine:
384 case UndoItem::editMarkLineAutoWrapped:
385 case UndoItem::editInvalid:
386 break;
387 }
388}
389
390void KateUndoGroup::markRedoAsSaved(QBitArray &lines)
391{
392 for (auto rit = m_items.rbegin(); rit != m_items.rend(); ++rit) {
393 updateRedoSavedOnDiskFlag(item&: *rit, lines);
394 }
395}
396
397UndoItem::UndoType KateUndoGroup::singleType() const
398{
399 UndoItem::UndoType ret = UndoItem::editInvalid;
400
401 for (const auto &item : m_items) {
402 if (ret == UndoItem::editInvalid) {
403 ret = item.type;
404 } else if (ret != item.type) {
405 return UndoItem::editInvalid;
406 }
407 }
408
409 return ret;
410}
411
412bool KateUndoGroup::isOnlyType(UndoItem::UndoType type) const
413{
414 if (type == UndoItem::editInvalid) {
415 return false;
416 }
417 for (const auto &item : m_items) {
418 if (item.type != type)
419 return false;
420 }
421 return true;
422}
423

source code of ktexteditor/src/undo/kateundo.cpp