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 | |
22 | KateUndoGroup::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 | |
33 | void 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 | |
105 | void 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 | |
176 | void 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 | |
185 | static 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 | |
214 | void 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 | |
225 | bool 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 | |
253 | void KateUndoGroup::safePoint(bool safePoint) |
254 | { |
255 | m_safePoint = safePoint; |
256 | } |
257 | |
258 | void 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 | |
283 | static 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 | |
335 | void 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 | |
342 | static 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 | |
390 | void 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 | |
397 | UndoItem::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 | |
412 | bool 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 | |