diff --git a/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetDialog.java b/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetDialog.java index b7d77701c..b4a33396b 100644 --- a/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetDialog.java +++ b/src/main/java/org/cyclops/integratedscripting/client/gui/component/input/WidgetDialog.java @@ -5,6 +5,7 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratedElementType; import net.minecraft.client.gui.narration.NarrationElementOutput; @@ -16,6 +17,8 @@ import org.cyclops.cyclopscore.helper.Helpers; import org.cyclops.integratedscripting.Reference; +import javax.annotation.Nullable; + /** * Shows a confirm/cancel dialog * @author rubensworks @@ -32,10 +35,21 @@ public class WidgetDialog extends AbstractWidget implements GuiEventListener { private final Component message; private final ButtonText buttonConfirm; private final ButtonText buttonCancel; + @Nullable + private final EditBox input; public WidgetDialog(Font font, int x, int y, Screen parent, Component title, Component message, Component confirm, Component cancel, Button.OnPress confirmCallback, Button.OnPress cancelCallback) { + this(font, x, y, parent, title, message, confirm, cancel, confirmCallback, cancelCallback, null); + } + + /** + * @param inputValue If not null, a text input is shown with this initial value. + */ + public WidgetDialog(Font font, int x, int y, Screen parent, + Component title, Component message, Component confirm, Component cancel, + Button.OnPress confirmCallback, Button.OnPress cancelCallback, @Nullable String inputValue) { super(x, y, WIDTH, HEIGHT, message); this.font = font; this.parent = parent; @@ -44,6 +58,14 @@ public WidgetDialog(Font font, int x, int y, Screen parent, this.buttonConfirm = new ButtonText(x + 50, y + HEIGHT - 15 - 5, 50, 15, confirm, confirm, confirmCallback, true); this.buttonCancel = new ButtonText(x + WIDTH - 50 - 50, y + HEIGHT - 15 - 5, 50, 15, cancel, cancel, cancelCallback, true); + if (inputValue != null) { + this.input = new EditBox(font, x + 10, y + 31, WIDTH - 20, 12, message); + this.input.setMaxLength(256); + this.input.setValue(inputValue); + this.input.setFocused(true); + } else { + this.input = null; + } } protected void drawBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { @@ -65,16 +87,33 @@ protected void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, flo guiGraphics.drawCenteredString(font, title, this.getX() + this.width / 2, this.getY() + 4, 16777215); FormattedCharSequence formattedcharsequence = message.getVisualOrderText(); - guiGraphics.drawString(font, message, (this.getX() + this.width / 2 - font.width(formattedcharsequence) / 2), (this.getY() + 25), 4210752, false); + guiGraphics.drawString(font, message, (this.getX() + this.width / 2 - font.width(formattedcharsequence) / 2), (this.getY() + (this.input != null ? 19 : 25)), 4210752, false); + if (this.input != null) { + this.input.render(guiGraphics, mouseX, mouseY, partialTicks); + } } @Override public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) { return this.buttonConfirm.mouseClicked(mouseX, mouseY, mouseButton) || this.buttonCancel.mouseClicked(mouseX, mouseY, mouseButton) + || (this.input != null && this.input.mouseClicked(mouseX, mouseY, mouseButton)) || super.mouseClicked(mouseX, mouseY, mouseButton); } + @Nullable + public EditBox getInput() { + return input; + } + + public void confirm() { + this.buttonConfirm.onPress(); + } + + public void cancel() { + this.buttonCancel.onPress(); + } + @Override protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { narrationElementOutput.add(NarratedElementType.TITLE, message); diff --git a/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java b/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java index a26f7264b..a6fde34b2 100644 --- a/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java +++ b/src/main/java/org/cyclops/integratedscripting/client/gui/container/ContainerScreenTerminalScripting.java @@ -11,7 +11,9 @@ import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; @@ -46,9 +48,11 @@ import org.cyclops.integratedscripting.inventory.container.ContainerTerminalScripting; import org.cyclops.integratedscripting.network.packet.TerminalScriptingDeleteScriptPacket; import org.cyclops.integratedscripting.network.packet.TerminalScriptingModifiedScriptPacket; +import org.cyclops.integratedscripting.network.packet.TerminalScriptingRenameScriptPacket; import org.lwjgl.glfw.GLFW; import javax.annotation.Nullable; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -74,6 +78,7 @@ public class ContainerScreenTerminalScripting extends ContainerScreenExtended { @@ -169,9 +182,15 @@ public int getTotalRows() { buttonCreateFile = new ButtonText(this.leftPos + 19, this.topPos + 222, 56, 10, Component.translatable("gui.integratedscripting.create_file"), Component.literal("+"), (button) -> getMenu().createNewFile(), true); - addRenderableWidget(buttonCreateFile); + if (!focusMode) { + addRenderableWidget(buttonCreateFile); + } - this.addToolbar(this.leftPos + SCRIPT_X + SCRIPT_WIDTH, this.topPos + 5); + if (focusMode) { + this.addToolbar(this.width - FOCUS_MARGIN, FOCUS_MARGIN - 12); + } else { + this.addToolbar(this.leftPos + SCRIPT_X + SCRIPT_WIDTH, this.topPos + 5); + } // Restore the open script in the new editor if (getMenu().getActiveScriptPath() != null) { @@ -187,6 +206,13 @@ public int getTotalRows() { * @param y The top of the toolbar. */ private void addToolbar(int right, int y) { + Component focusLabel = Component.translatable(focusMode ? "gui.integratedscripting.focus_mode.exit" : "gui.integratedscripting.focus_mode.enter"); + buttonFocus = new ButtonIcon(right - 10, y, 10, 10, focusLabel, (button) -> this.toggleFocusMode(), + focusMode ? ScriptImages.ACTION_FOCUS_EXIT : ScriptImages.ACTION_FOCUS_ENTER); + buttonFocus.setTooltip(Tooltip.create(focusLabel)); + addRenderableWidget(buttonFocus); + right -= 10; + this.toolbarLeft = right - 50; this.toolbarEditButtons.clear(); this.buttonUndo = this.addToolbarButton(right - 50, y, ScriptImages.ACTION_UNDO, Keys.UNDO, () -> this.textArea.undo()); @@ -281,8 +307,44 @@ protected int getErrorY() { return 139; } + private void toggleFocusMode() { + this.focusMode = !this.focusMode; + // Recreates the widgets, which keeps the open script + this.rebuildWidgets(); + } + + @Override + protected void drawCurrentScreen(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { + if (focusMode) { + // Only show the editor, without the container contents + guiGraphics.fill(textArea.getX() - 2, textArea.getY() - 2, textArea.getX() + textArea.getWidth() + 2, textArea.getY() + textArea.getHeight() + 2, Helpers.RGBAToInt(85, 85, 85, 255)); + guiGraphics.fill(textArea.getX() - 1, textArea.getY() - 1, textArea.getX() + textArea.getWidth() + 1, textArea.getY() + textArea.getHeight() + 1, Helpers.RGBAToInt(224, 224, 224, 255)); + Path path = getMenu().getActiveScriptPath(); + if (path != null) { + guiGraphics.drawString(font, path.toString(), FOCUS_MARGIN, FOCUS_MARGIN - 11, 16777215); + } + for (net.minecraft.client.gui.components.Renderable renderable : this.renderables) { + renderable.render(guiGraphics, mouseX, mouseY, partialTicks); + } + } else { + super.drawCurrentScreen(guiGraphics, mouseX, mouseY, partialTicks); + } + } + + @Override + protected void renderTooltip(GuiGraphics guiGraphics, int x, int y) { + // Slots are hidden in focus mode + if (!focusMode) { + super.renderTooltip(guiGraphics, x, y); + } + } + @Override protected void renderBg(GuiGraphics guiGraphics, float partialTicks, int mouseX, int mouseY) { + // Only the editor is shown in focus mode + if (focusMode) { + return; + } super.renderBg(guiGraphics, partialTicks, mouseX, mouseY); RenderHelpers.bindTexture(this.texture); fieldDisk.render(guiGraphics, mouseX, mouseY, partialTicks); @@ -364,8 +426,14 @@ protected void renderScriptPaths(GuiGraphics guiGraphics, int mouseX, int mouseY Font.DisplayMode.NORMAL ); - // If hovering, render removal button + // If hovering, show the full path, as it may be truncated, and render removal button if (hovering) { + // Don't draw over an open dialog + if (pendingDialog == null) { + this.setTooltipForNextRenderPass(Lists.newArrayList( + Component.literal(path.toString()).getVisualOrderText(), + Component.translatable("gui.integratedscripting.rename_hint").withStyle(ChatFormatting.GRAY).getVisualOrderText())); + } guiGraphics.pose().pushPose(); float scale = 0.4F; int size = (int) (Images.ERROR.getWidth() * scale); @@ -400,7 +468,7 @@ protected Path getHoveredScriptPath(double mouseX, double mouseY) { protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) { // Shorten the title if it would overlap with the toolbar, as translations can be long Component title = this.title; - int maxTitleWidth = this.toolbarLeft - this.leftPos - this.titleLabelX - 2; + int maxTitleWidth = this.toolbarLeft - this.leftPos - this.titleLabelX - 1; if (this.font.width(title) > maxTitleWidth) { title = Component.literal(this.font.plainSubstrByWidth(title.getString(), maxTitleWidth - this.font.width("\u2026")) + "\u2026"); } @@ -416,33 +484,49 @@ protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) { @Override public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) { // Make active dialog consume all input - if (pendingScriptRemovalDialog != null) { - return pendingScriptRemovalDialog.mouseClicked(mouseX, mouseY, mouseButton); + if (pendingDialog != null) { + return pendingDialog.mouseClicked(mouseX, mouseY, mouseButton); + } + + // Only the editor widgets are visible in focus mode, so don't let clicks reach the container + if (focusMode) { + for (GuiEventListener child : this.children()) { + if (child.mouseClicked(mouseX, mouseY, mouseButton)) { + this.setFocused(child); + return true; + } + } + return true; } // Handle script path clicks Path hoveredScriptPath = getHoveredScriptPath(mouseX, mouseY); + if (hoveredScriptPath != null && mouseButton == 1) { + this.fieldDisk.playDownSound(Minecraft.getInstance().getSoundManager()); + this.openRenameDialog(hoveredScriptPath); + return true; + } if (hoveredScriptPath != null) { // Handle script removal clicks if (mouseX >= this.leftPos + PATHS_X + PATHS_WIDTH - (int) (Images.ERROR.getWidth() * 0.4F) - 1) { this.fieldDisk.playDownSound(Minecraft.getInstance().getSoundManager()); // Show confirmation dialog - pendingScriptRemovalDialog = new WidgetDialog(font, leftPos + getBaseXSize() / 2 - WidgetDialog.WIDTH / 2, topPos + 50, this, + pendingDialog = new WidgetDialog(font, leftPos + getBaseXSize() / 2 - WidgetDialog.WIDTH / 2, topPos + 50, this, Component.translatable("gui.integratedscripting.removal_dialog.title"), Component.translatable("gui.integratedscripting.removal_dialog.message", hoveredScriptPath.toString()), Component.translatable("gui.integratedscripting.removal_dialog.delete"), Component.translatable("gui.integratedscripting.removal_dialog.keep"), (b) -> { - removeWidget(pendingScriptRemovalDialog); - pendingScriptRemovalDialog = null; + removeWidget(pendingDialog); + pendingDialog = null; this.removeScript(hoveredScriptPath); }, (b) -> { - removeWidget(pendingScriptRemovalDialog); - pendingScriptRemovalDialog = null; + removeWidget(pendingDialog); + pendingDialog = null; }); - addRenderableWidget(pendingScriptRemovalDialog); + addRenderableWidget(pendingDialog); return true; } else { @@ -592,6 +676,53 @@ private void onSelectionModified() { getMenu().setSelection(selected); } + private void openRenameDialog(Path path) { + pendingDialog = new WidgetDialog(font, leftPos + getBaseXSize() / 2 - WidgetDialog.WIDTH / 2, topPos + 50, this, + Component.translatable("gui.integratedscripting.rename_dialog.title"), + Component.translatable("gui.integratedscripting.rename_dialog.message"), + Component.translatable("gui.integratedscripting.rename_dialog.rename"), + Component.translatable("gui.integratedscripting.rename_dialog.cancel"), + (b) -> { + String name = pendingDialog.getInput().getValue(); + removeWidget(pendingDialog); + pendingDialog = null; + this.renameScript(path, name); + }, + (b) -> { + removeWidget(pendingDialog); + pendingDialog = null; + }, path.toString()); + addRenderableWidget(pendingDialog); + } + + private void renameScript(Path from, String name) { + int disk = getMenu().getActiveDisk(); + Map scripts = getActiveScripts(); + Path to; + try { + to = Path.of(name.trim()); + } catch (InvalidPathException e) { + return; + } + if (scripts == null || to.toString().isEmpty() || scripts.containsKey(to) || !scripts.containsKey(from)) { + return; + } + + // Apply the rename locally, so an open script stays open with its editor state + String script = scripts.get(from); + boolean active = from.equals(getMenu().getActiveScriptPath()); + getMenu().setLastScript(disk, to, script); + getMenu().setLastScript(disk, from, null); + TextHistory history = scriptHistories.remove(Pair.of(disk, from)); + if (history != null) { + scriptHistories.put(Pair.of(disk, to), history); + } + if (active) { + getMenu().setActiveScriptPath(to); + } + IntegratedScripting._instance.getPacketHandler().sendToServer(new TerminalScriptingRenameScriptPacket(disk, from, to)); + } + private void removeScript(Path path) { IntegratedScripting._instance.getPacketHandler() .sendToServer(new TerminalScriptingDeleteScriptPacket(getMenu().getActiveDisk(), path)); @@ -608,7 +739,7 @@ public boolean mouseScrolled(double mouseX, double mouseY, double mouseZ, double @Override public boolean mouseDragged(double mouseX, double mouseY, int mouseButton, double offsetX, double offsetY) { // Make active dialog consume all input - if (pendingScriptRemovalDialog != null) { + if (pendingDialog != null) { return false; } @@ -628,7 +759,17 @@ public boolean mouseReleased(double mouseX, double mouseY, int mouseButton) { @Override public boolean keyPressed(int typedChar, int keyCode, int modifiers) { // Make active dialog consume all input - if (typedChar != GLFW.GLFW_KEY_ESCAPE && pendingScriptRemovalDialog != null) { + if (pendingDialog != null && pendingDialog.getInput() != null) { + if (typedChar == GLFW.GLFW_KEY_ESCAPE) { + pendingDialog.cancel(); + } else if (typedChar == GLFW.GLFW_KEY_ENTER || typedChar == GLFW.GLFW_KEY_KP_ENTER) { + pendingDialog.confirm(); + } else { + pendingDialog.getInput().keyPressed(typedChar, keyCode, modifiers); + } + return true; + } + if (typedChar != GLFW.GLFW_KEY_ESCAPE && pendingDialog != null) { return false; } @@ -641,6 +782,12 @@ public boolean keyPressed(int typedChar, int keyCode, int modifiers) { return true; } + // Leave focus mode instead of closing the screen + if (focusMode && typedChar == GLFW.GLFW_KEY_ESCAPE && pendingDialog == null) { + this.toggleFocusMode(); + return true; + } + if (textArea.isFocused()) { boolean ret = textArea.keyPressed(typedChar, keyCode, modifiers); if (typedChar != GLFW.GLFW_KEY_ESCAPE) { @@ -757,8 +904,8 @@ private void navigate(int direction) { @Override public boolean charTyped(char p_94683_, int p_94684_) { // Make active dialog consume all input - if (pendingScriptRemovalDialog != null) { - return false; + if (pendingDialog != null) { + return pendingDialog.getInput() != null && pendingDialog.getInput().charTyped(p_94683_, p_94684_); } if (this.searchBar != null && this.searchBar.isFocused()) { return this.searchBar.charTyped(p_94683_, p_94684_); diff --git a/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java b/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java index 9d6678d8a..0796b72ef 100644 --- a/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java +++ b/src/main/java/org/cyclops/integratedscripting/client/gui/image/ScriptImages.java @@ -21,5 +21,7 @@ public class ScriptImages { public static final Image ACTION_FIND = new Image(ICONS, 16, 16, 8, 8); public static final Image ACTION_GOTO_LINE = new Image(ICONS, 24, 16, 8, 8); public static final Image ACTION_COMMENT = new Image(ICONS, 32, 16, 8, 8); + public static final Image ACTION_FOCUS_ENTER = new Image(ICONS, 48, 16, 8, 8); + public static final Image ACTION_FOCUS_EXIT = new Image(ICONS, 56, 16, 8, 8); } diff --git a/src/main/java/org/cyclops/integratedscripting/inventory/container/ContainerTerminalScripting.java b/src/main/java/org/cyclops/integratedscripting/inventory/container/ContainerTerminalScripting.java index 47e87e303..334c22dea 100644 --- a/src/main/java/org/cyclops/integratedscripting/inventory/container/ContainerTerminalScripting.java +++ b/src/main/java/org/cyclops/integratedscripting/inventory/container/ContainerTerminalScripting.java @@ -336,6 +336,35 @@ public void setServerScript(int disk, Path path, @Nullable String script) { ScriptingNetworkHelpers.getScriptingData().setScript(disk, path, script, IScriptingData.ChangeLocation.MEMORY); } + /** + * Rename a script on the server. + * The client already applied the rename to its own state, so it is corrected if the rename fails. + */ + public void renameServerScript(int disk, Path from, Path to) { + IScriptingData scriptingData = ScriptingNetworkHelpers.getScriptingData(); + String script = scriptingData.getScripts(disk).get(from); + if (script != null && !scriptingData.getScripts(disk).containsKey(to)) { + scriptingData.setScript(disk, to, script, IScriptingData.ChangeLocation.MEMORY); + // Only remove the original if the new path was accepted + if (script.equals(scriptingData.getScripts(disk).get(to))) { + scriptingData.setScript(disk, from, null, IScriptingData.ChangeLocation.MEMORY); + } + } + + // Send the actual state of both paths, which also corrects the client if the rename failed + for (Path path : new Path[]{from, to}) { + String value = scriptingData.getScripts(disk).get(path); + setLastScript(disk, path, value); + if (value != null) { + IntegratedScripting._instance.getPacketHandler() + .sendToPlayer(new TerminalScriptingModifiedScriptPacket(disk, path, value), (ServerPlayer) player); + } else { + IntegratedScripting._instance.getPacketHandler() + .sendToPlayer(new TerminalScriptingDeleteScriptPacket(disk, path), (ServerPlayer) player); + } + } + } + public void createNewServerScript(int disk) { // Determine unique script name Set existingScripts = ScriptingNetworkHelpers.getScriptingData().getScripts(disk).keySet(); diff --git a/src/main/java/org/cyclops/integratedscripting/network/packet/TerminalScriptingRenameScriptPacket.java b/src/main/java/org/cyclops/integratedscripting/network/packet/TerminalScriptingRenameScriptPacket.java new file mode 100644 index 000000000..4701fd614 --- /dev/null +++ b/src/main/java/org/cyclops/integratedscripting/network/packet/TerminalScriptingRenameScriptPacket.java @@ -0,0 +1,68 @@ +package org.cyclops.integratedscripting.network.packet; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import org.cyclops.cyclopscore.network.CodecField; +import org.cyclops.cyclopscore.network.PacketCodec; +import org.cyclops.integratedscripting.Reference; +import org.cyclops.integratedscripting.inventory.container.ContainerTerminalScripting; + +import java.nio.file.InvalidPathException; +import java.nio.file.Path; + +/** + * Packet for renaming a script from client to server. + * @author rubensworks + * + */ +public class TerminalScriptingRenameScriptPacket extends PacketCodec { + + public static final Type ID = new Type<>(ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "terminal_scripting_rename_script")); + public static final StreamCodec CODEC = getCodec(TerminalScriptingRenameScriptPacket::new); + + @CodecField + private int disk; + @CodecField + private String from; + @CodecField + private String to; + + public TerminalScriptingRenameScriptPacket() { + super(ID); + } + + public TerminalScriptingRenameScriptPacket(int disk, Path from, Path to) { + super(ID); + this.disk = disk; + this.from = from.toString(); + this.to = to.toString(); + } + + @Override + public boolean isAsync() { + return false; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void actionClient(Level world, Player player) { + + } + + @Override + public void actionServer(Level world, ServerPlayer player) { + if(player.containerMenu instanceof ContainerTerminalScripting container) { + try { + container.renameServerScript(disk, Path.of(from), Path.of(to)); + } catch (InvalidPathException e) { + // Ignore invalid paths + } + } + } +} diff --git a/src/main/java/org/cyclops/integratedscripting/proxy/CommonProxy.java b/src/main/java/org/cyclops/integratedscripting/proxy/CommonProxy.java index 9f579a612..64eed3f3d 100644 --- a/src/main/java/org/cyclops/integratedscripting/proxy/CommonProxy.java +++ b/src/main/java/org/cyclops/integratedscripting/proxy/CommonProxy.java @@ -7,6 +7,7 @@ import org.cyclops.integratedscripting.network.packet.TerminalScriptingCreateNewScriptPacket; import org.cyclops.integratedscripting.network.packet.TerminalScriptingDeleteScriptPacket; import org.cyclops.integratedscripting.network.packet.TerminalScriptingModifiedScriptPacket; +import org.cyclops.integratedscripting.network.packet.TerminalScriptingRenameScriptPacket; import org.cyclops.integratedscripting.network.packet.TerminalScriptingSyntaxErrorPacket; /** @@ -29,5 +30,6 @@ public void registerPacketHandlers(PacketHandler packetHandler) { packetHandler.register(TerminalScriptingCreateNewScriptPacket.ID, TerminalScriptingCreateNewScriptPacket.CODEC); packetHandler.register(TerminalScriptingDeleteScriptPacket.ID, TerminalScriptingDeleteScriptPacket.CODEC); packetHandler.register(TerminalScriptingSyntaxErrorPacket.ID, TerminalScriptingSyntaxErrorPacket.CODEC); + packetHandler.register(TerminalScriptingRenameScriptPacket.ID, TerminalScriptingRenameScriptPacket.CODEC); } } diff --git a/src/main/resources/assets/integratedscripting/lang/en_us.json b/src/main/resources/assets/integratedscripting/lang/en_us.json index fb5cc62ba..8b719866e 100644 --- a/src/main/resources/assets/integratedscripting/lang/en_us.json +++ b/src/main/resources/assets/integratedscripting/lang/en_us.json @@ -27,6 +27,8 @@ "gui.integratedscripting.script": "Script", "gui.integratedscripting.create_file": "Create File", "gui.integratedscripting.find": "Find", + "gui.integratedscripting.focus_mode.enter": "Enlarge editor", + "gui.integratedscripting.focus_mode.exit": "Back to normal size (Escape)", "gui.integratedscripting.goto_line": "Go to line", "gui.integratedscripting.find.label": "Find:", "gui.integratedscripting.goto_line.label": "Line:", @@ -37,6 +39,11 @@ "gui.integratedscripting.removal_dialog.message": "Do you want to delete \"%s\"?", "gui.integratedscripting.removal_dialog.delete": "Delete", "gui.integratedscripting.removal_dialog.keep": "Keep", + "gui.integratedscripting.rename_dialog.title": "Rename script", + "gui.integratedscripting.rename_dialog.message": "New path, such as dir/name.js:", + "gui.integratedscripting.rename_dialog.rename": "Rename", + "gui.integratedscripting.rename_dialog.cancel": "Cancel", + "gui.integratedscripting.rename_hint": "Right-click to rename", "gui.integratedscripting.error.invalid_member": "Select a valid function name or variable name inside the script editor to bind with.", "gui.integratedscripting.error.invalid_member.current": "Current selection: \"%s\"", @@ -139,6 +146,7 @@ "info_book.integratedscripting.terminal_shortcuts.text1": "The script editor of the &lScripting Terminal&r has the keyboard shortcuts shown below. You can change them in the &oControls&r settings, under &oIntegrated Scripting&r. Most of these actions are also available as buttons above the editor.", "info_book.integratedscripting.terminal_shortcuts.text2": "Some keys can not be changed: &oTab&r and &oShift+Tab&r indent and unindent the selected lines, &oPage Up&r and &oPage Down&r move by a page, &oCtrl+Left&r and &oCtrl+Right&r (&oOption&r on Mac) move by word, &oCmd+Left&r and &oCmd+Right&r move to the start or end of the line on Mac, and &oCtrl+Shift+Z&r (&oCmd+Shift+Z&r on Mac) also redoes.", "info_book.integratedscripting.terminal_shortcuts.text3": "While finding text or going to a line, &oEnter&r and &oShift+Enter&r go to the next and previous match, and &oEscape&r closes the search bar.", + "info_book.integratedscripting.terminal_shortcuts.text4": "In focus mode, &oEscape&r returns to the normal terminal. To rename a script, right-click it in the script list.", "info_book.integratedscripting.writing": "Writing Scripts", diff --git a/src/main/resources/assets/integratedscripting/textures/gui/icons.png b/src/main/resources/assets/integratedscripting/textures/gui/icons.png index c896e55b9..26a3a8cae 100644 Binary files a/src/main/resources/assets/integratedscripting/textures/gui/icons.png and b/src/main/resources/assets/integratedscripting/textures/gui/icons.png differ diff --git a/src/main/resources/data/integratedscripting/info/scripting_info.xml b/src/main/resources/data/integratedscripting/info/scripting_info.xml index 84b7818ef..94d6191ad 100644 --- a/src/main/resources/data/integratedscripting/info/scripting_info.xml +++ b/src/main/resources/data/integratedscripting/info/scripting_info.xml @@ -59,6 +59,7 @@ info_book.integratedscripting.terminal_shortcuts.text1 info_book.integratedscripting.terminal_shortcuts.text2 info_book.integratedscripting.terminal_shortcuts.text3 + info_book.integratedscripting.terminal_shortcuts.text4 key.integratedscripting.undo key.integratedscripting.redo key.integratedscripting.find diff --git a/src/test/java/org/cyclops/integratedscripting/core/network/ScriptingDataTest.java b/src/test/java/org/cyclops/integratedscripting/core/network/ScriptingDataTest.java index 5ab3cf714..4f062a42b 100644 --- a/src/test/java/org/cyclops/integratedscripting/core/network/ScriptingDataTest.java +++ b/src/test/java/org/cyclops/integratedscripting/core/network/ScriptingDataTest.java @@ -227,6 +227,38 @@ public void testFlushKeepsFilePermissions() throws IOException { } } + @Test + public void testDiskReloadKeepsUnflushedChanges() throws IOException { + Path rootPath = Files.createTempDirectory("integratedscripting-test"); + ScriptingData scriptingData = new ScriptingData(rootPath); + try { + Path diskPath = rootPath.resolve("scripting-disks").resolve("654"); + Files.createDirectories(diskPath); + Files.writeString(diskPath.resolve("old.js"), "old"); + Files.writeString(diskPath.resolve("other.js"), "other"); + scriptingData.initialize(); + + // Rename old.js to new.js in memory, without flushing it to disk yet + scriptingData.setScript(654, Path.of("new.js"), "old", IScriptingData.ChangeLocation.MEMORY); + scriptingData.setScript(654, Path.of("old.js"), null, IScriptingData.ChangeLocation.MEMORY); + + // A file watcher reload, for example caused by another file, must not undo these changes + scriptingData.initializeDiskScripts(654); + assertThat(scriptingData.getScripts(654).get(Path.of("new.js")), equalTo("old")); + assertThat(scriptingData.getScripts(654).get(Path.of("old.js")), nullValue()); + assertThat(scriptingData.getScripts(654).get(Path.of("other.js")), equalTo("other")); + + // After flushing, the disk contents are used again + scriptingData.tick(); + scriptingData.initializeDiskScripts(654); + assertThat(scriptingData.getScripts(654).get(Path.of("new.js")), equalTo("old")); + assertFalse(Files.exists(diskPath.resolve("old.js"))); + } finally { + scriptingData.close(); + FileUtils.deleteDirectory(rootPath.toFile()); + } + } + @Test public void testDiskReloadDuringFirstFlushKeepsScript() throws IOException { Path rootPath = Files.createTempDirectory("integratedscripting-test");