Skip to content

Commit 113c23e

Browse files
committed
feat: Show external modifications in /sign cut output
When another plugin modifies sign text curing a cut operation, `/sign cut` now displays a localized `after_section` showing the lines that weren't cleared as expected. This brings `/sign cut` in line with `/sign set` and `/sign clear`, both of which indicate when a sign text edit is overridden.
1 parent 46f6c11 commit 113c23e

3 files changed

Lines changed: 136 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## v1.14.7 (UNRELEASED)
1111

12+
### Added
13+
14+
* `/sign cut` now shows the unexpected differences when another plugin overrides the line removals.
15+
16+
![`/sign cut` with SignEdit for Bukkit v1.14.7](https://i.imgur.com/kilZyoY.png)
17+
1218
### Fixed
1319

1420
* When using `/sign set` or `/sign clear`, sign text modifications were not applied due to a state tracking bug. This bug did not affect native sign text edits with `/sign ui`.

src/net/deltik/mc/signedit/interactions/CutSignEditInteraction.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ public void interact(Player player, SignShim sign, SideShim side) {
6565

6666
comms.tell(comms.t("lines_cut_section"));
6767
comms.dumpLines(clipboard.getLines());
68+
69+
// Check if any cut lines were externally modified (not cleared as expected)
70+
String[] afterLines = signText().getAfterLines();
71+
boolean hasExternalMod = false;
72+
for (int selectedLine : selectedLines) {
73+
if (!afterLines[selectedLine].isEmpty()) {
74+
hasExternalMod = true;
75+
break;
76+
}
77+
}
78+
79+
if (hasExternalMod) {
80+
comms.tell(comms.t("after_section", comms.t("section_decorator", comms.t("modified_by_another_plugin"))));
81+
for (int selectedLine : selectedLines) {
82+
String afterLine = afterLines[selectedLine];
83+
if (!afterLine.isEmpty()) {
84+
int displayLine = config().getLineStartsAt() + selectedLine;
85+
comms.tell(comms.t("print_line", comms.t("secondary"), displayLine, afterLine));
86+
}
87+
}
88+
}
6889
}
6990

7091
@Override

test/net/deltik/mc/signedit/interactions/CutSignEditInteractionTest.java

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929
import org.bukkit.block.Sign;
3030
import org.bukkit.block.data.BlockData;
3131
import org.bukkit.entity.Player;
32+
import org.bukkit.event.block.SignChangeEvent;
3233
import org.junit.jupiter.api.BeforeEach;
3334
import org.junit.jupiter.api.Test;
35+
import org.mockito.ArgumentCaptor;
3436
import org.mockito.stubbing.Answer;
3537

3638
import java.util.Locale;
@@ -120,8 +122,8 @@ private SubcommandContext createContext(SignText signText, ArgParser argParser,
120122
}
121123

122124
@Test
123-
public void cutAllLinesShowsClipboardDump() {
124-
// Setup: normal cut with NoopValidator
125+
public void cutAllLinesShowsClipboardDumpOnly() {
126+
// Setup: normal cut with NoopValidator (no external modification)
125127
Sign sign = createSign(defaultSignLines.clone());
126128
SignShim signShim = new SignShim(sign);
127129
SignEditValidator validator = new NoopSignEditValidator();
@@ -137,6 +139,110 @@ public void cutAllLinesShowsClipboardDump() {
137139
// Verify clipboard dump is shown
138140
verify(comms).tell(comms.t("lines_cut_section"));
139141
verify(comms).dumpLines(any());
142+
143+
// Verify NO sign result section is shown (no external modification)
144+
verify(comms, never()).tell(contains("after_section"));
145+
verify(comms, never()).tell(argThat(arg ->
146+
arg != null && arg.contains("Modified by another plugin")));
147+
}
148+
149+
@Test
150+
public void cutWithExternalModificationShowsSignResultSection() {
151+
// Setup: validator that restores line 1 when we try to clear it
152+
SignEditValidator modifyingValidator = new SignEditValidator() {
153+
@Override
154+
public String[] validate(SignShim proposedSign, SideShim side, Player player) {
155+
String[] lines = proposedSign.getSide(side).getLines().clone();
156+
// Simulate another plugin keeping line 1 non-empty
157+
if (lines[1].isEmpty()) {
158+
lines[1] = "KEPT_BY_PLUGIN";
159+
}
160+
return lines;
161+
}
162+
163+
@Override
164+
public void validate(SignChangeEvent signChangeEvent) {
165+
}
166+
};
167+
168+
Sign sign = createSign(defaultSignLines.clone());
169+
SignShim signShim = new SignShim(sign);
170+
SignText signText = new SignText(modifyingValidator);
171+
ArgParser argParser = createArgParser(new int[]{0, 1, 2, 3});
172+
173+
SubcommandContext context = createContext(signText, argParser, modifyingValidator);
174+
CutSignEditInteraction interaction = new CutSignEditInteraction(context);
175+
176+
// Execute the cut
177+
interaction.interact(player, signShim, SideShim.FRONT);
178+
179+
// Verify clipboard dump is shown first
180+
verify(comms).tell(comms.t("lines_cut_section"));
181+
verify(comms).dumpLines(any());
182+
183+
// Verify sign result section IS shown (external modification occurred)
184+
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
185+
verify(comms, atLeast(2)).tell(messageCaptor.capture());
186+
187+
// Check that one of the messages contains the external modification notice
188+
boolean hasExternalModMessage = messageCaptor.getAllValues().stream()
189+
.anyMatch(msg -> msg != null && msg.contains("Modified by another plugin"));
190+
assertTrue(hasExternalModMessage, "Expected external modification message to be shown");
191+
192+
// Verify the modified line is shown
193+
boolean hasModifiedLine = messageCaptor.getAllValues().stream()
194+
.anyMatch(msg -> msg != null && msg.contains("KEPT_BY_PLUGIN"));
195+
assertTrue(hasModifiedLine, "Expected modified line content to be shown");
196+
}
197+
198+
@Test
199+
public void cutPartialLinesWithExternalModificationShowsOnlyAffectedLines() {
200+
// Setup: validator that modifies line 2 when we try to clear it
201+
SignEditValidator modifyingValidator = new SignEditValidator() {
202+
@Override
203+
public String[] validate(SignShim proposedSign, SideShim side, Player player) {
204+
String[] lines = proposedSign.getSide(side).getLines().clone();
205+
// Simulate another plugin modifying line 2 to something else
206+
if (lines[2].isEmpty()) {
207+
lines[2] = "MODIFIED_LINE_3";
208+
}
209+
return lines;
210+
}
211+
212+
@Override
213+
public void validate(SignChangeEvent signChangeEvent) {
214+
}
215+
};
216+
217+
Sign sign = createSign(defaultSignLines.clone());
218+
SignShim signShim = new SignShim(sign);
219+
SignText signText = new SignText(modifyingValidator);
220+
// Only cut lines 1, 2, 3 (0-indexed: 0, 1, 2)
221+
ArgParser argParser = createArgParser(new int[]{0, 1, 2});
222+
223+
SubcommandContext context = createContext(signText, argParser, modifyingValidator);
224+
CutSignEditInteraction interaction = new CutSignEditInteraction(context);
225+
226+
// Execute the cut
227+
interaction.interact(player, signShim, SideShim.FRONT);
228+
229+
// Verify clipboard dump is shown
230+
verify(comms).tell(comms.t("lines_cut_section"));
231+
verify(comms).dumpLines(any());
232+
233+
// Capture all messages
234+
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass(String.class);
235+
verify(comms, atLeast(2)).tell(messageCaptor.capture());
236+
237+
// Verify external modification message is shown
238+
boolean hasExternalModMessage = messageCaptor.getAllValues().stream()
239+
.anyMatch(msg -> msg != null && msg.contains("Modified by another plugin"));
240+
assertTrue(hasExternalModMessage, "Expected external modification message");
241+
242+
// Verify the modified line content is shown
243+
boolean hasModifiedContent = messageCaptor.getAllValues().stream()
244+
.anyMatch(msg -> msg != null && msg.contains("MODIFIED_LINE_3"));
245+
assertTrue(hasModifiedContent, "Expected modified line content to be shown");
140246
}
141247

142248
@Test
@@ -160,7 +266,7 @@ public void cutCopiesOriginalLinesToClipboard() {
160266
}
161267

162268
@Test
163-
public void cutClearsSignLines() {
269+
public void cutWithNoExternalModificationClearsSignLines() {
164270
Sign sign = createSign(defaultSignLines.clone());
165271
SignShim signShim = new SignShim(sign);
166272
SignEditValidator validator = new NoopSignEditValidator();

0 commit comments

Comments
 (0)