Skip to content

Commit 13cea5f

Browse files
committed
refactor Undo/Redo: Extract logic into UndoRedoManager
1 parent be4ff52 commit 13cea5f

File tree

6 files changed

+183
-61
lines changed

6 files changed

+183
-61
lines changed

src/MultiReplacePanel.cpp

Lines changed: 15 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "Encoding.h"
2929
#include "LanguageManager.h"
3030
#include "ConfigManager.h"
31+
#include "UndoRedoManager.h"
3132

3233
#include <algorithm>
3334
#include <bitset>
@@ -86,6 +87,7 @@ HWND MultiReplace::hDebugWnd = NULL;
8687
bool MultiReplace::_isShuttingDown = false;
8788
static LanguageManager& LM = LanguageManager::instance();
8889
static ConfigManager& CFG = ConfigManager::instance();
90+
static UndoRedoManager& URM = UndoRedoManager::instance();
8991

9092
#pragma warning(disable: 6262)
9193

@@ -816,34 +818,6 @@ void MultiReplace::updateUseListState(bool isUpdate)
816818

817819
#pragma region Undo stack
818820

819-
void MultiReplace::undo() {
820-
if (!undoStack.empty()) {
821-
// Get the last action
822-
UndoRedoAction action = undoStack.back();
823-
undoStack.pop_back();
824-
825-
// Execute the undo action
826-
action.undoAction();
827-
828-
// Push the action onto the redoStack
829-
redoStack.push_back(action);
830-
}
831-
}
832-
833-
void MultiReplace::redo() {
834-
if (!redoStack.empty()) {
835-
// Get the last action
836-
UndoRedoAction action = redoStack.back();
837-
redoStack.pop_back();
838-
839-
// Execute the redo action
840-
action.redoAction();
841-
842-
// Push the action back onto the undoStack
843-
undoStack.push_back(action);
844-
}
845-
}
846-
847821
void MultiReplace::addItemsToReplaceList(const std::vector<ReplaceItemData>& items, size_t insertPosition = std::numeric_limits<size_t>::max()) {
848822
// Determine the insert position
849823
if (insertPosition > replaceListData.size()) {
@@ -860,9 +834,6 @@ void MultiReplace::addItemsToReplaceList(const std::vector<ReplaceItemData>& ite
860834
ListView_SetItemCountEx(_replaceListView, static_cast<int>(replaceListData.size()), LVSICF_NOINVALIDATEALL);
861835
InvalidateRect(_replaceListView, NULL, TRUE);
862836

863-
// Clear the redoStack since a new action invalidates the redo history
864-
redoStack.clear();
865-
866837
// Create undo and redo actions
867838
UndoRedoAction action;
868839

@@ -904,7 +875,7 @@ void MultiReplace::addItemsToReplaceList(const std::vector<ReplaceItemData>& ite
904875
};
905876

906877
// Push the action onto the undoStack
907-
undoStack.push_back(action);
878+
URM.push(action.undoAction, action.redoAction, L"Add items");
908879
}
909880

910881
void MultiReplace::removeItemsFromReplaceList(const std::vector<size_t>& indicesToRemove) {
@@ -925,9 +896,6 @@ void MultiReplace::removeItemsFromReplaceList(const std::vector<size_t>& indices
925896
ListView_SetItemCountEx(_replaceListView, static_cast<int>(replaceListData.size()), LVSICF_NOINVALIDATEALL);
926897
InvalidateRect(_replaceListView, NULL, TRUE);
927898

928-
// Clear redoStack since a new action invalidates redo history
929-
redoStack.clear();
930-
931899
// Create undo and redo actions
932900
UndoRedoAction action;
933901

@@ -994,7 +962,7 @@ void MultiReplace::removeItemsFromReplaceList(const std::vector<size_t>& indices
994962
};
995963

996964
// Push the action to the undoStack
997-
undoStack.push_back(action);
965+
URM.push(action.undoAction, action.redoAction, L"Remove items");
998966
}
999967

1000968
void MultiReplace::modifyItemInReplaceList(size_t index, const ReplaceItemData& newData) {
@@ -1007,9 +975,6 @@ void MultiReplace::modifyItemInReplaceList(size_t index, const ReplaceItemData&
1007975
// Update the ListView item
1008976
updateListViewItem(index);
1009977

1010-
// Clear the redoStack
1011-
redoStack.clear();
1012-
1013978
// Create Undo/Redo actions
1014979
UndoRedoAction action;
1015980

@@ -1046,7 +1011,7 @@ void MultiReplace::modifyItemInReplaceList(size_t index, const ReplaceItemData&
10461011
};
10471012

10481013
// Push the action onto the undoStack
1049-
undoStack.push_back(action);
1014+
URM.push(action.undoAction, action.redoAction, L"Modify item");
10501015
}
10511016

10521017
bool MultiReplace::moveItemsInReplaceList(std::vector<size_t>& indices, Direction direction) {
@@ -1084,9 +1049,6 @@ bool MultiReplace::moveItemsInReplaceList(std::vector<size_t>& indices, Directio
10841049
ListView_SetItemCountEx(_replaceListView, static_cast<int>(replaceListData.size()), LVSICF_NOINVALIDATEALL);
10851050
InvalidateRect(_replaceListView, NULL, TRUE);
10861051

1087-
// Clear the redoStack to invalidate future actions
1088-
redoStack.clear();
1089-
10901052
// Create Undo/Redo actions
10911053
UndoRedoAction action;
10921054

@@ -1139,7 +1101,7 @@ bool MultiReplace::moveItemsInReplaceList(std::vector<size_t>& indices, Directio
11391101
};
11401102

11411103
// Push the action onto the undoStack
1142-
undoStack.push_back(action);
1104+
URM.push(action.undoAction, action.redoAction, L"Move items");
11431105

11441106
// Deselect all items
11451107
ListView_SetItemState(_replaceListView, -1, 0, LVIS_SELECTED);
@@ -1206,7 +1168,7 @@ void MultiReplace::sortItemsInReplaceList(const std::vector<size_t>& originalOrd
12061168
};
12071169

12081170
// Push the action onto the undo stack
1209-
undoStack.push_back(action);
1171+
URM.push(action.undoAction, action.redoAction, L"Sort items");
12101172
}
12111173

12121174
void MultiReplace::scrollToIndices(size_t firstIndex, size_t lastIndex) {
@@ -2689,8 +2651,8 @@ MenuState MultiReplace::checkMenuConditions(POINT ptScreen) {
26892651
state.allEnabled = (enabledCount == ListView_GetSelectedCount(_replaceListView));
26902652
state.allDisabled = (disabledCount == ListView_GetSelectedCount(_replaceListView));
26912653

2692-
state.canUndo = !undoStack.empty();
2693-
state.canRedo = !redoStack.empty();
2654+
state.canUndo = URM.canUndo();
2655+
state.canRedo = URM.canRedo();
26942656

26952657
return state;
26962658
}
@@ -2730,11 +2692,11 @@ void MultiReplace::performItemAction(POINT pt, ItemAction action) {
27302692

27312693
switch (action) {
27322694
case ItemAction::Undo:
2733-
undo();
2695+
URM.undo();
27342696
showListFilePath();
27352697
break;
27362698
case ItemAction::Redo:
2737-
redo();
2699+
URM.redo();
27382700
showListFilePath();
27392701
break;
27402702
case ItemAction::Search:
@@ -3451,10 +3413,10 @@ INT_PTR CALLBACK MultiReplace::run_dlgProc(UINT message, WPARAM wParam, LPARAM l
34513413
if (GetKeyState(VK_CONTROL) & 0x8000) { // If Ctrl is pressed
34523414
switch (pnkd->wVKey) {
34533415
case 'Z': // Ctrl+Z for Undo
3454-
undo();
3416+
URM.undo();
34553417
break;
34563418
case 'Y': // Ctrl+Y for Redo
3457-
redo();
3419+
URM.redo();
34583420
break;
34593421
case 'F': // Ctrl+F for Search in List
34603422
performItemAction(_contextMenuClickPoint, ItemAction::Search);
@@ -8641,9 +8603,6 @@ void MultiReplace::setSelections(bool select, bool onlySelected) {
86418603
updateListViewItem(index);
86428604
}
86438605

8644-
// Clear the redoStack
8645-
redoStack.clear();
8646-
86478606
// Create Undo/Redo actions
86488607
UndoRedoAction action;
86498608

@@ -8666,7 +8625,7 @@ void MultiReplace::setSelections(bool select, bool onlySelected) {
86668625
};
86678626

86688627
// Push the action onto the undoStack
8669-
undoStack.push_back(action);
8628+
URM.push(action.undoAction, action.redoAction, L"Set enabled");
86708629

86718630
// Show Select Statisics
86728631
showListFilePath();
@@ -9528,8 +9487,7 @@ void MultiReplace::loadListFromCsv(const std::wstring& filePath) {
95289487
originalListHash = computeListHash(replaceListData);
95299488

95309489
// Clear Undo and Redo stacks
9531-
undoStack.clear();
9532-
redoStack.clear();
9490+
URM.clear();
95339491

95349492
// Update the ListView
95359493
ListView_SetItemCountEx(_replaceListView, static_cast<int>(replaceListData.size()), LVSICF_NOINVALIDATEALL);

src/MultiReplacePanel.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,6 @@ struct EditControlContext
335335

336336
// each new Vector has to be delared outside of the class due to unresolved memory behaviours,
337337
// possible initial limited stack size in N++ for Plugins
338-
inline std::vector<UndoRedoAction> undoStack;
339-
inline std::vector<UndoRedoAction> redoStack;
340338
inline HWND hwndExpandBtn = nullptr;
341339
inline HFONT _hBoldFont2;
342340
inline lua_State* _luaState = nullptr; // Reused Lua state
@@ -678,8 +676,6 @@ class MultiReplace : public StaticDialog
678676
void updateUseListState(bool isUpdate);
679677

680678
// Undo
681-
void undo();
682-
void redo();
683679
void addItemsToReplaceList(const std::vector<ReplaceItemData>& items, size_t insertPosition);
684680
void removeItemsFromReplaceList(const std::vector<size_t>& indicesToRemove);
685681
void modifyItemInReplaceList(size_t index, const ReplaceItemData& newData);

src/UndoRedoManager.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// This file is part of the MultiReplace plugin for Notepad++.
2+
// Copyright (C) 2025 Thomas Knoefel
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
// -----------------------------------------------------------------------------
17+
// Centralised undo / redo stack – no plugin‑specific code in here
18+
// -----------------------------------------------------------------------------
19+
20+
#include "UndoRedoManager.h"
21+
22+
// -----------------------------------------------------------------------------
23+
// Singleton
24+
// -----------------------------------------------------------------------------
25+
UndoRedoManager& UndoRedoManager::instance()
26+
{
27+
static UndoRedoManager mgr;
28+
return mgr;
29+
}
30+
31+
// -----------------------------------------------------------------------------
32+
// push – store new command and invalidate redo history
33+
// -----------------------------------------------------------------------------
34+
void UndoRedoManager::push(Action undoAction,
35+
Action redoAction,
36+
const std::wstring& label)
37+
{
38+
_undo.push_back({ std::move(undoAction),
39+
std::move(redoAction),
40+
label });
41+
_redo.clear();
42+
}
43+
44+
// -----------------------------------------------------------------------------
45+
// undo – run last undo lambda and move it to redo stack
46+
// -----------------------------------------------------------------------------
47+
bool UndoRedoManager::undo()
48+
{
49+
if (_undo.empty())
50+
return false;
51+
52+
Item cmd = std::move(_undo.back());
53+
_undo.pop_back();
54+
55+
if (cmd.undo)
56+
cmd.undo();
57+
58+
_redo.push_back(std::move(cmd));
59+
return true;
60+
}
61+
62+
// -----------------------------------------------------------------------------
63+
// redo – run last redo lambda and move it back to undo stack
64+
// -----------------------------------------------------------------------------
65+
bool UndoRedoManager::redo()
66+
{
67+
if (_redo.empty())
68+
return false;
69+
70+
Item cmd = std::move(_redo.back());
71+
_redo.pop_back();
72+
73+
if (cmd.redo)
74+
cmd.redo();
75+
76+
_undo.push_back(std::move(cmd));
77+
return true;
78+
}
79+
80+
// -----------------------------------------------------------------------------
81+
// Helpers
82+
// -----------------------------------------------------------------------------
83+
void UndoRedoManager::clear()
84+
{
85+
_undo.clear();
86+
_redo.clear();
87+
}
88+
89+
std::wstring UndoRedoManager::peekUndoLabel() const
90+
{
91+
return _undo.empty() ? L"" : _undo.back().label;
92+
}
93+
94+
std::wstring UndoRedoManager::peekRedoLabel() const
95+
{
96+
return _redo.empty() ? L"" : _redo.back().label;
97+
}

src/UndoRedoManager.h

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// This file is part of the MultiReplace plugin for Notepad++.
2+
// Copyright (C) 2025 Thomas Knoefel
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
#pragma once
17+
// --------------------------------------------------------------
18+
// UndoRedoManager (singleton)
19+
// • Keeps two stacks of lambda pairs (undo / redo)
20+
// • Totally framework‑agnostic – STL only
21+
// • Optional label field enables later history UI
22+
// --------------------------------------------------------------
23+
24+
#include <functional>
25+
#include <string>
26+
#include <vector>
27+
28+
class UndoRedoManager
29+
{
30+
public:
31+
/// Global accessor – same idiom as ConfigManager / LanguageManager
32+
static UndoRedoManager& instance();
33+
34+
using Action = std::function<void()>;
35+
36+
void push(Action undoAction,
37+
Action redoAction,
38+
const std::wstring& label = L"");
39+
40+
bool undo(); // returns false if nothing to undo
41+
bool redo(); // returns false if nothing to redo
42+
43+
void clear();
44+
45+
[[nodiscard]] bool canUndo() const { return !_undo.empty(); }
46+
[[nodiscard]] bool canRedo() const { return !_redo.empty(); }
47+
[[nodiscard]] size_t undoCount() const { return _undo.size(); }
48+
[[nodiscard]] size_t redoCount() const { return _redo.size(); }
49+
50+
[[nodiscard]] std::wstring peekUndoLabel() const;
51+
[[nodiscard]] std::wstring peekRedoLabel() const;
52+
53+
private:
54+
UndoRedoManager() = default;
55+
~UndoRedoManager() = default;
56+
UndoRedoManager(const UndoRedoManager&) = delete;
57+
UndoRedoManager& operator=(const UndoRedoManager&) = delete;
58+
59+
struct Item {
60+
Action undo;
61+
Action redo;
62+
std::wstring label;
63+
};
64+
65+
std::vector<Item> _undo;
66+
std::vector<Item> _redo;
67+
};

0 commit comments

Comments
 (0)