From 11f32eb5ce26fe02b03fd5bdbc8a793162be6e36 Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Wed, 24 Feb 2021 21:17:32 -0800 Subject: [PATCH 01/23] HotColdLocation: Center some location spots Center some hot-cold locations as reported and verified from the mega\ issue. Ref: runelite/runelite#9601 --- .../plugins/cluescrolls/clues/hotcold/HotColdLocation.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java index 287ca3844a..3baeee926c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java @@ -62,7 +62,7 @@ public enum HotColdLocation ASGARNIA_CRAFT_GUILD(new WorldPoint(2917, 3295, 0), ASGARNIA, "Outside the Crafting Guild cow pen.", BRASSICAN_MAGE), ASGARNIA_RIMMINGTON(new WorldPoint(2976, 3239, 0), ASGARNIA, "In the centre of the Rimmington mine.", BRASSICAN_MAGE), ASGARNIA_MUDSKIPPER(new WorldPoint(2987, 3110, 0), ASGARNIA, "Mudskipper Point, near the starfish in the south-west corner.", BRASSICAN_MAGE), - ASGARNIA_TROLL(new WorldPoint(2910, 3616, 0), ASGARNIA, "The Troll arena, where the player fights Dad during the Troll Stronghold quest. Bring climbing boots if travelling from Burthorpe.", BRASSICAN_MAGE), + ASGARNIA_TROLL(new WorldPoint(2910, 3615, 0), ASGARNIA, "The Troll arena, where the player fights Dad during the Troll Stronghold quest. Bring climbing boots if travelling from Burthorpe.", BRASSICAN_MAGE), DESERT_GENIE(new WorldPoint(3359, 2912, 0), DESERT, "West of Nardah genie cave.", BRASSICAN_MAGE), DESERT_ALKHARID_MINE(new WorldPoint(3279, 3263, 0), DESERT, "West of Al Kharid mine.", BRASSICAN_MAGE), DESERT_MENAPHOS_GATE(new WorldPoint(3223, 2820, 0), DESERT, "North of Menaphos gate.", BRASSICAN_MAGE), @@ -117,7 +117,7 @@ public enum HotColdLocation KARAMJA_MUSA_POINT(new WorldPoint(2913, 3169, 0), KARAMJA, "Musa Point, banana plantation.", BRASSICAN_MAGE), KARAMJA_BRIMHAVEN_FRUIT_TREE(new WorldPoint(2782, 3215, 0), KARAMJA, "Brimhaven, east of the fruit tree patch.", BRASSICAN_MAGE), KARAMJA_WEST_BRIMHAVEN(new WorldPoint(2718, 3167, 0), KARAMJA, "West of Brimhaven.", BRASSICAN_MAGE), - KARAMJA_GLIDER(new WorldPoint(2966, 2975, 0), KARAMJA, "West of the gnome glider.", BRASSICAN_MAGE), + KARAMJA_GLIDER(new WorldPoint(2966, 2976, 0), KARAMJA, "West of the gnome glider.", BRASSICAN_MAGE), KARAMJA_KHARAZI_NE(new WorldPoint(2904, 2925, 0), KARAMJA, "North-eastern part of Kharazi Jungle.", BRASSICAN_MAGE), KARAMJA_KHARAZI_SW(new WorldPoint(2786, 2899, 0), KARAMJA, "South-western part of Kharazi Jungle.", BRASSICAN_MAGE), KARAMJA_CRASH_ISLAND(new WorldPoint(2909, 2737, 0), KARAMJA, "Northern part of Crash Island.", BRASSICAN_MAGE), @@ -146,7 +146,7 @@ public enum HotColdLocation WESTERN_PROVINCE_PISCATORIS_HUNTER_AREA(new WorldPoint(2359, 3564, 0), WESTERN_PROVINCE, "Eastern part of Piscatoris Hunter area, south-west of the Falconry.", BRASSICAN_MAGE), WESTERN_PROVINCE_ARANDAR(new WorldPoint(2370, 3319, 0), WESTERN_PROVINCE, "South-west of the crystal gate to Arandar.", ANCIENT_WIZARDS), WESTERN_PROVINCE_ELF_CAMP_EAST(new WorldPoint(2268, 3242, 0), WESTERN_PROVINCE, "East of Iorwerth Camp.", BRASSICAN_MAGE), - WESTERN_PROVINCE_ELF_CAMP_NW(new WorldPoint(2174, 3280, 0), WESTERN_PROVINCE, "North-west of Iorwerth Camp.", BRASSICAN_MAGE), + WESTERN_PROVINCE_ELF_CAMP_NW(new WorldPoint(2177, 3282, 0), WESTERN_PROVINCE, "North-west of Iorwerth Camp.", BRASSICAN_MAGE), WESTERN_PROVINCE_LLETYA(new WorldPoint(2337, 3166, 0), WESTERN_PROVINCE, "In Lletya.", BRASSICAN_MAGE), WESTERN_PROVINCE_TYRAS(new WorldPoint(2206, 3158, 0), WESTERN_PROVINCE, "Near Tyras Camp.", BRASSICAN_MAGE), WESTERN_PROVINCE_ZULANDRA(new WorldPoint(2196, 3057, 0), WESTERN_PROVINCE, "The northern house at Zul-Andra.", BRASSICAN_MAGE), From 70b3a987c9abeab6564a9512f0a46664fc461ce7 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Fri, 26 Feb 2021 10:22:21 -0700 Subject: [PATCH 02/23] Require Java 9 or higher for building We still support Java 8 at runtime since older launchers shipped with it, and there are still many installs that use those older launchers. --- pom.xml | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index c3ccba7052..0443ee0b52 100644 --- a/pom.xml +++ b/pom.xml @@ -152,34 +152,15 @@ - - - java9 - - [1.9,) - - - - - maven-compiler-plugin - - ${java.release} - - - - - - - org.apache.maven.plugins maven-compiler-plugin - 3.6.1 ${java.version} ${java.version} + ${java.release} From 2c4f3a895e7d0dca2f5ec74164a1749d53088725 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Tue, 9 Feb 2021 22:01:00 -0700 Subject: [PATCH 03/23] devtools: factor frame handling out --- .../plugins/devtools/DevToolsButton.java | 18 ++++- .../plugins/devtools/DevToolsFrame.java | 66 +++++++++++++++++++ .../plugins/devtools/DevToolsPanel.java | 48 ++------------ .../plugins/devtools/InventoryInspector.java | 27 ++------ .../plugins/devtools/ScriptInspector.java | 28 ++------ .../client/plugins/devtools/VarInspector.java | 28 ++------ .../plugins/devtools/WidgetInspector.java | 27 ++------ 7 files changed, 110 insertions(+), 132 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsFrame.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsButton.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsButton.java index e21af75f5a..e2a2f6c7ff 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsButton.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsButton.java @@ -28,7 +28,7 @@ import java.awt.Color; import javax.swing.JButton; import lombok.Getter; -class DevToolsButton extends JButton +public class DevToolsButton extends JButton { @Getter private boolean active; @@ -53,4 +53,20 @@ class DevToolsButton extends JButton setBackground(null); } } + + void addFrame(DevToolsFrame frame) + { + frame.setDevToolsButton(this); + addActionListener(ev -> + { + if (isActive()) + { + frame.close(); + } + else + { + frame.open(); + } + }); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsFrame.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsFrame.java new file mode 100644 index 0000000000..94554e9863 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsFrame.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.devtools; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JFrame; +import lombok.AccessLevel; +import lombok.Setter; +import net.runelite.client.ui.ClientUI; + +public class DevToolsFrame extends JFrame +{ + @Setter(AccessLevel.PACKAGE) + protected DevToolsButton devToolsButton; + + public DevToolsFrame() + { + setIconImage(ClientUI.ICON); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() + { + @Override + public void windowClosing(WindowEvent e) + { + close(); + devToolsButton.setActive(false); + } + }); + } + + public void open() + { + setVisible(true); + toFront(); + repaint(); + } + + public void close() + { + setVisible(false); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java index 54f0037639..7651a5ad69 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java @@ -129,30 +129,10 @@ class DevToolsPanel extends PluginPanel }); container.add(plugin.getWidgetInspector()); - plugin.getWidgetInspector().addActionListener((ev) -> - { - if (plugin.getWidgetInspector().isActive()) - { - widgetInspector.close(); - } - else - { - widgetInspector.open(); - } - }); + plugin.getWidgetInspector().addFrame(widgetInspector); container.add(plugin.getVarInspector()); - plugin.getVarInspector().addActionListener((ev) -> - { - if (plugin.getVarInspector().isActive()) - { - varInspector.close(); - } - else - { - varInspector.open(); - } - }); + plugin.getVarInspector().addFrame(varInspector); container.add(plugin.getSoundEffects()); @@ -164,17 +144,7 @@ class DevToolsPanel extends PluginPanel container.add(notificationBtn); container.add(plugin.getScriptInspector()); - plugin.getScriptInspector().addActionListener((ev) -> - { - if (plugin.getScriptInspector().isActive()) - { - scriptInspector.close(); - } - else - { - scriptInspector.open(); - } - }); + plugin.getScriptInspector().addFrame(scriptInspector); final JButton newInfoboxBtn = new JButton("Infobox"); newInfoboxBtn.addActionListener(e -> @@ -198,17 +168,7 @@ class DevToolsPanel extends PluginPanel container.add(clearInfoboxBtn); container.add(plugin.getInventoryInspector()); - plugin.getInventoryInspector().addActionListener((ev) -> - { - if (plugin.getInventoryInspector().isActive()) - { - inventoryInspector.close(); - } - else - { - inventoryInspector.open(); - } - }); + plugin.getInventoryInspector().addFrame(inventoryInspector); final JButton disconnectBtn = new JButton("Disconnect"); disconnectBtn.addActionListener(e -> clientThread.invoke(() -> client.setGameState(GameState.CONNECTION_LOST))); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/InventoryInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/InventoryInspector.java index 44b7f141dd..61428e2f37 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/InventoryInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/InventoryInspector.java @@ -28,8 +28,6 @@ import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -39,7 +37,6 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import javax.swing.JButton; -import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; @@ -65,7 +62,7 @@ import net.runelite.client.ui.ColorScheme; @Slf4j @Singleton -class InventoryInspector extends JFrame +class InventoryInspector extends DevToolsFrame { private static final int MAX_LOG_ENTRIES = 25; @@ -80,7 +77,7 @@ class InventoryInspector extends JFrame private final InventoryDeltaPanel deltaPanel; @Inject - InventoryInspector(Client client, EventBus eventBus, DevToolsPlugin plugin, ItemManager itemManager, ClientThread clientThread) + InventoryInspector(Client client, EventBus eventBus, ItemManager itemManager, ClientThread clientThread) { this.client = client; this.eventBus = eventBus; @@ -92,18 +89,6 @@ class InventoryInspector extends JFrame setTitle("RuneLite Inventory Inspector"); setIconImage(ClientUI.ICON); - setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - // Reset highlight on close - addWindowListener(new WindowAdapter() - { - @Override - public void windowClosing(WindowEvent e) - { - close(); - plugin.getInventoryInspector().setActive(false); - } - }); - tree.setBorder(new EmptyBorder(2, 2, 2, 2)); tree.setRootVisible(false); tree.setShowsRootHandles(true); @@ -175,19 +160,19 @@ class InventoryInspector extends JFrame pack(); } + @Override public void open() { eventBus.register(this); - setVisible(true); - toFront(); - repaint(); + super.open(); } + @Override public void close() { eventBus.unregister(this); clearTracker(); - setVisible(false); + super.close(); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java index 5956baed72..58146acef7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java @@ -31,8 +31,6 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; @@ -42,7 +40,6 @@ import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JFormattedTextField; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; @@ -68,14 +65,13 @@ import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.FontManager; import net.runelite.client.util.Text; @Slf4j -public class ScriptInspector extends JFrame +public class ScriptInspector extends DevToolsFrame { // These scripts are the only ones that fire every client tick regardless of location. private final static String DEFAULT_BLACKLIST = "3174,1004"; @@ -139,28 +135,16 @@ public class ScriptInspector extends JFrame } @Inject - ScriptInspector(Client client, EventBus eventBus, DevToolsPlugin plugin, ConfigManager configManager) + ScriptInspector(Client client, EventBus eventBus, ConfigManager configManager) { this.eventBus = eventBus; this.client = client; this.configManager = configManager; setTitle("RuneLite Script Inspector"); - setIconImage(ClientUI.ICON); setLayout(new BorderLayout()); - setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() - { - @Override - public void windowClosing(WindowEvent e) - { - close(); - plugin.getScriptInspector().setActive(false); - } - }); - tracker.setLayout(new DynamicGridLayout(0, 1, 0, 3)); final JPanel leftSide = new JPanel(); @@ -344,14 +328,14 @@ public class ScriptInspector extends JFrame } } + @Override public void open() { eventBus.register(this); - setVisible(true); - toFront(); - repaint(); + super.open(); } + @Override public void close() { configManager.setConfiguration("devtools", "highlights", @@ -360,7 +344,7 @@ public class ScriptInspector extends JFrame Text.toCSV(Lists.transform(new ArrayList<>(blacklist), String::valueOf))); currentNode = null; eventBus.unregister(this); - setVisible(false); + super.close(); } private void addScriptLog(ScriptTreeNode treeNode) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/VarInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/VarInspector.java index 089a7461d0..0fd19ae027 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/VarInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/VarInspector.java @@ -32,15 +32,12 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.util.HashMap; import java.util.Map; import java.util.Objects; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollBar; @@ -62,13 +59,12 @@ import net.runelite.api.events.VarbitChanged; import net.runelite.client.callback.ClientThread; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.FontManager; @Slf4j -class VarInspector extends JFrame +class VarInspector extends DevToolsFrame { @Getter private enum VarType @@ -106,28 +102,16 @@ class VarInspector extends JFrame private Map varcs = null; @Inject - VarInspector(Client client, ClientThread clientThread, EventBus eventBus, DevToolsPlugin plugin) + VarInspector(Client client, ClientThread clientThread, EventBus eventBus) { this.client = client; this.clientThread = clientThread; this.eventBus = eventBus; setTitle("RuneLite Var Inspector"); - setIconImage(ClientUI.ICON); setLayout(new BorderLayout()); - setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() - { - @Override - public void windowClosing(WindowEvent e) - { - close(); - plugin.getVarInspector().setActive(false); - } - }); - tracker.setLayout(new DynamicGridLayout(0, 1, 0, 3)); final JPanel trackerWrapper = new JPanel(); @@ -332,6 +316,7 @@ class VarInspector extends JFrame } } + @Override public void open() { if (oldVarps == null) @@ -361,16 +346,15 @@ class VarInspector extends JFrame }); eventBus.register(this); - setVisible(true); - toFront(); - repaint(); + super.open(); } + @Override public void close() { + super.close(); tracker.removeAll(); eventBus.unregister(this); - setVisible(false); varcs = null; varbits = null; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java index 986ebb996a..d137ea3053 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInspector.java @@ -32,8 +32,6 @@ import com.google.inject.Singleton; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.util.Collection; import java.util.Comparator; import java.util.Enumeration; @@ -43,7 +41,6 @@ import java.util.Stack; import java.util.stream.Stream; import javax.swing.JButton; import javax.swing.JCheckBox; -import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; @@ -73,13 +70,12 @@ import net.runelite.api.widgets.WidgetType; import net.runelite.client.callback.ClientThread; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.util.ColorUtil; @Slf4j @Singleton -class WidgetInspector extends JFrame +class WidgetInspector extends DevToolsFrame { private static final Map widgetIdMap = new HashMap<>(); @@ -123,7 +119,6 @@ class WidgetInspector extends JFrame ClientThread clientThread, WidgetInfoTableModel infoTableModel, DevToolsConfig config, - DevToolsPlugin plugin, EventBus eventBus, Provider overlay, OverlayManager overlayManager) @@ -138,18 +133,6 @@ class WidgetInspector extends JFrame eventBus.register(this); setTitle("RuneLite Widget Inspector"); - setIconImage(ClientUI.ICON); - - // Reset highlight on close - addWindowListener(new WindowAdapter() - { - @Override - public void windowClosing(WindowEvent e) - { - close(); - plugin.getWidgetInspector().setActive(false); - } - }); setLayout(new BorderLayout()); @@ -402,21 +385,21 @@ class WidgetInspector extends JFrame return widgetIdMap.get(packedId); } + @Override public void open() { - setVisible(true); - toFront(); - repaint(); + super.open(); overlayManager.add(this.overlay.get()); clientThread.invokeLater(this::addPickerWidget); } + @Override public void close() { overlayManager.remove(this.overlay.get()); clientThread.invokeLater(this::removePickerWidget); setSelectedWidget(null, -1, false); - setVisible(false); + super.close(); } private void removePickerWidget() From 7480c74750a5bd9f68b0aa8d946c0432860550dc Mon Sep 17 00:00:00 2001 From: Max Weber Date: Mon, 22 Feb 2021 10:40:05 -0700 Subject: [PATCH 04/23] devtools: add shell window --- pom.xml | 13 + runelite-api/pom.xml | 1 - runelite-client/pom.xml | 14 +- .../runelite/client/eventbus/EventBus.java | 72 ++- .../plugins/devtools/DevToolsPanel.java | 18 + .../plugins/devtools/DevToolsPlugin.java | 2 + .../client/plugins/devtools/ShellFrame.java | 70 +++ runelite-jshell/pom.xml | 83 +++ .../jshell/JShellAutocompleteProvider.java | 137 +++++ .../jshell/RLShellExecutionControl.java | 50 ++ .../runelite/jshell/RemappingThrowable.java | 142 +++++ .../java/net/runelite/jshell/ShellPanel.java | 447 ++++++++++++++++ .../java/net/runelite/jshell/TeeLogger.java | 483 ++++++++++++++++++ .../resources/net/runelite/jshell/darcula.xml | 73 +++ .../resources/net/runelite/jshell/default.jsh | 8 + .../resources/net/runelite/jshell/prelude.jsh | 68 +++ 16 files changed, 1645 insertions(+), 36 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/devtools/ShellFrame.java create mode 100644 runelite-jshell/pom.xml create mode 100644 runelite-jshell/src/main/java/net/runelite/jshell/JShellAutocompleteProvider.java create mode 100644 runelite-jshell/src/main/java/net/runelite/jshell/RLShellExecutionControl.java create mode 100644 runelite-jshell/src/main/java/net/runelite/jshell/RemappingThrowable.java create mode 100644 runelite-jshell/src/main/java/net/runelite/jshell/ShellPanel.java create mode 100644 runelite-jshell/src/main/java/net/runelite/jshell/TeeLogger.java create mode 100644 runelite-jshell/src/main/resources/net/runelite/jshell/darcula.xml create mode 100644 runelite-jshell/src/main/resources/net/runelite/jshell/default.jsh create mode 100644 runelite-jshell/src/main/resources/net/runelite/jshell/prelude.jsh diff --git a/pom.xml b/pom.xml index 0443ee0b52..5f1f37e418 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,7 @@ cache-updater runelite-api runelite-client + runelite-jshell runelite-script-assembler-plugin http-api http-service @@ -142,6 +143,11 @@ gson 2.8.5 + + com.google.code.findbugs + jsr305 + 3.0.2 + ch.qos.logback logback-parent @@ -149,6 +155,13 @@ pom import + + com.google.inject + guice-bom + 4.1.0 + pom + import + diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml index 8634829df5..94a578e767 100644 --- a/runelite-api/pom.xml +++ b/runelite-api/pom.xml @@ -48,7 +48,6 @@ com.google.code.findbugs jsr305 - 3.0.2 provided diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 37364a3f5b..ab36e6df91 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -36,8 +36,6 @@ RuneLite Client - 4.1.0 - true true @@ -85,7 +83,6 @@ com.google.inject guice - ${guice.version} no_aop @@ -215,7 +212,6 @@ com.google.code.findbugs jsr305 - 3.0.2 @@ -223,6 +219,12 @@ runelite-api ${project.version} + + net.runelite + jshell + ${project.version} + true + net.runelite client-patch @@ -267,13 +269,11 @@ com.google.inject.extensions guice-testlib - ${guice.version} test com.google.inject.extensions guice-grapher - ${guice.version} test @@ -318,7 +318,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.0.0 + 3.2.1 package diff --git a/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java b/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java index 2340e306dd..a6d123a974 100644 --- a/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java +++ b/runelite-client/src/main/java/net/runelite/client/eventbus/EventBus.java @@ -51,26 +51,20 @@ import net.runelite.client.util.ReflectUtil; @ThreadSafe public class EventBus { - @FunctionalInterface - public interface SubscriberMethod - { - void invoke(Object event); - } - @Value - private static class Subscriber + public static class Subscriber { private final Object object; private final Method method; private final float priority; @EqualsAndHashCode.Exclude - private final SubscriberMethod lamda; + private final Consumer lambda; void invoke(final Object arg) throws Exception { - if (lamda != null) + if (lambda != null) { - lamda.invoke(arg); + lambda.accept(arg); } else { @@ -80,7 +74,9 @@ public class EventBus } private final Consumer exceptionHandler; - private ImmutableMultimap subscribers = ImmutableMultimap.of(); + + @Nonnull + private ImmutableMultimap, Subscriber> subscribers = ImmutableMultimap.of(); /** * Instantiates EventBus with default exception handler @@ -99,13 +95,8 @@ public class EventBus */ public synchronized void register(@Nonnull final Object object) { - final ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); - - if (subscribers != null) - { - builder.putAll(subscribers); - } - + final ImmutableMultimap.Builder, Subscriber> builder = ImmutableMultimap.builder(); + builder.putAll(subscribers); builder.orderValuesBy(Comparator.comparing(Subscriber::getPriority).reversed() .thenComparing(s -> s.object.getClass().getName())); @@ -141,7 +132,7 @@ public class EventBus Preconditions.checkArgument(method.getName().equals(preferredName), "Subscribed method " + method + " should be named " + preferredName); method.setAccessible(true); - SubscriberMethod lambda = null; + Consumer lambda = null; try { @@ -150,14 +141,14 @@ public class EventBus final MethodHandle target = caller.findVirtual(clazz, method.getName(), subscription); final CallSite site = LambdaMetafactory.metafactory( caller, - "invoke", - MethodType.methodType(SubscriberMethod.class, clazz), + "accept", + MethodType.methodType(Consumer.class, clazz), subscription.changeParameterType(0, Object.class), target, subscription); final MethodHandle factory = site.getTarget(); - lambda = (SubscriberMethod) factory.bindTo(object).invokeExact(); + lambda = (Consumer) factory.bindTo(object).invokeExact(); } catch (Throwable e) { @@ -173,6 +164,21 @@ public class EventBus subscribers = builder.build(); } + public synchronized Subscriber register(Class clazz, Consumer subFn, float priority) + { + final ImmutableMultimap.Builder, Subscriber> builder = ImmutableMultimap.builder(); + builder.putAll(subscribers); + builder.orderValuesBy(Comparator.comparing(Subscriber::getPriority).reversed() + .thenComparing(s -> s.object.getClass().getName())); + + Subscriber sub = new Subscriber(subFn, null, priority, (Consumer) subFn); + builder.put(clazz, sub); + + subscribers = builder.build(); + + return sub; + } + /** * Unregisters all subscribed methods from provided subscriber object. * @@ -180,12 +186,7 @@ public class EventBus */ public synchronized void unregister(@Nonnull final Object object) { - if (subscribers == null) - { - return; - } - - final Multimap map = HashMultimap.create(); + final Multimap, Subscriber> map = HashMultimap.create(); map.putAll(subscribers); for (Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass()) @@ -207,6 +208,21 @@ public class EventBus subscribers = ImmutableMultimap.copyOf(map); } + public synchronized void unregister(Subscriber sub) + { + if (sub == null) + { + return; + } + + final Multimap, Subscriber> map = HashMultimap.create(); + map.putAll(subscribers); + + map.values().remove(sub); + + subscribers = ImmutableMultimap.copyOf(map); + } + /** * Posts provided event to all registered subscribers. Subscriber calls are invoked immediately, * ordered by priority then their declaring class' name. diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java index 7651a5ad69..9ba1efd092 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java @@ -25,6 +25,7 @@ */ package net.runelite.client.plugins.devtools; +import com.google.inject.ProvisionException; import java.awt.GridLayout; import java.awt.TrayIcon; import java.util.concurrent.ScheduledExecutorService; @@ -32,6 +33,7 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.swing.JButton; import javax.swing.JPanel; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.MenuAction; @@ -44,6 +46,7 @@ import net.runelite.client.ui.overlay.infobox.Counter; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.util.ImageUtil; +@Slf4j class DevToolsPanel extends PluginPanel { private final Client client; @@ -174,6 +177,21 @@ class DevToolsPanel extends PluginPanel disconnectBtn.addActionListener(e -> clientThread.invoke(() -> client.setGameState(GameState.CONNECTION_LOST))); container.add(disconnectBtn); + try + { + ShellFrame sf = plugin.getInjector().getInstance(ShellFrame.class); + container.add(plugin.getShell()); + plugin.getShell().addFrame(sf); + } + catch (LinkageError | ProvisionException e) + { + log.debug("Shell is not supported", e); + } + catch (Exception e) + { + log.info("Shell couldn't be loaded", e); + } + return container; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java index 424c26e4c3..b690f54e71 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java @@ -143,6 +143,7 @@ public class DevToolsPlugin extends Plugin private DevToolsButton soundEffects; private DevToolsButton scriptInspector; private DevToolsButton inventoryInspector; + private DevToolsButton shell; private NavigationButton navButton; @Provides @@ -187,6 +188,7 @@ public class DevToolsPlugin extends Plugin soundEffects = new DevToolsButton("Sound Effects"); scriptInspector = new DevToolsButton("Script Inspector"); inventoryInspector = new DevToolsButton("Inventory Inspector"); + shell = new DevToolsButton("Shell"); overlayManager.add(overlay); overlayManager.add(locationOverlay); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ShellFrame.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ShellFrame.java new file mode 100644 index 0000000000..f6472575ad --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ShellFrame.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.devtools; + +import java.util.concurrent.ScheduledExecutorService; +import javax.inject.Inject; +import javax.inject.Singleton; +import net.runelite.client.RuneLite; +import net.runelite.client.callback.ClientThread; +import net.runelite.jshell.ShellPanel; + +@Singleton +class ShellFrame extends DevToolsFrame +{ + private final ShellPanel shellPanel; + + @Inject + ShellFrame(ClientThread clientThread, ScheduledExecutorService executor) + { + this.shellPanel = new ShellPanel(executor) + { + @Override + protected void invokeOnClientThread(Runnable r) + { + clientThread.invoke(r); + } + }; + setContentPane(shellPanel); + + setTitle("RuneLite Shell"); + + pack(); + } + + @Override + public void open() + { + shellPanel.switchContext(RuneLite.getInjector()); + super.open(); + } + + @Override + public void close() + { + super.close(); + shellPanel.freeContext(); + } +} diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml new file mode 100644 index 0000000000..85536adbe3 --- /dev/null +++ b/runelite-jshell/pom.xml @@ -0,0 +1,83 @@ + + + + 4.0.0 + + + net.runelite + runelite-parent + 1.7.2-SNAPSHOT + + + jshell + RuneLite JShell + + + + org.slf4j + slf4j-api + + + com.google.inject + guice + no_aop + + + org.projectlombok + lombok + provided + + + com.google.code.findbugs + jsr305 + + + com.fifesoft + rsyntaxtextarea + 3.1.2 + + + com.fifesoft + autocomplete + 3.1.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + 11 + + + + + diff --git a/runelite-jshell/src/main/java/net/runelite/jshell/JShellAutocompleteProvider.java b/runelite-jshell/src/main/java/net/runelite/jshell/JShellAutocompleteProvider.java new file mode 100644 index 0000000000..2a0a2d8418 --- /dev/null +++ b/runelite-jshell/src/main/java/net/runelite/jshell/JShellAutocompleteProvider.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.jshell; + +import java.awt.Point; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import javax.swing.text.JTextComponent; +import jdk.jshell.JShell; +import jdk.jshell.SourceCodeAnalysis; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.fife.ui.autocomplete.BasicCompletion; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.CompletionProviderBase; +import org.fife.ui.autocomplete.ParameterizedCompletion; + +@Slf4j +@RequiredArgsConstructor +public class JShellAutocompleteProvider extends CompletionProviderBase +{ + private final JShell shell; + private String anchorText; + private List completions; + + @Override + protected List getCompletionsImpl(JTextComponent comp) + { + return completions; + } + + @Override + public String getAlreadyEnteredText(JTextComponent comp) + { + complete(comp); + return anchorText; + } + + private void complete(JTextComponent comp) + { + completions = Collections.emptyList(); + + String src = comp.getText(); + int cursor = comp.getCaretPosition(); + + for (int offset = 0; offset < src.length() && cursor >= offset; ) + { + var snipSrc = src.substring(offset); + int thisOffset = offset; + var ci = shell.sourceCodeAnalysis().analyzeCompletion(snipSrc); + offset = src.length() - ci.remaining().length(); + boolean mayHaveMore = ci.completeness() == SourceCodeAnalysis.Completeness.COMPLETE_WITH_SEMI + || ci.completeness() == SourceCodeAnalysis.Completeness.COMPLETE; + + if (cursor <= offset || !mayHaveMore) + { + var anchor = new int[1]; + + completions = shell.sourceCodeAnalysis() + .completionSuggestions(snipSrc, cursor - thisOffset, anchor) + .stream() + .filter(v -> !v.continuation().startsWith("$")) + .map(s -> + { + return new BasicCompletion(this, s.continuation()); + }) + .collect(Collectors.toList()); + anchorText = snipSrc.substring(anchor[0], cursor - thisOffset); + break; + } + } + + if (completions.isEmpty()) + { + anchorText = null; + } + } + + @Override + public List getCompletionsAt(JTextComponent comp, Point p) + { + return Collections.emptyList(); + } + + @Override + public boolean isAutoActivateOkay(JTextComponent comp) + { + // try not to start autocomplete when it has no useful context + String text = comp.getText(); + for (int i = comp.getCaretPosition(); i >= 0; i--) + { + char c = text.charAt(i); + if (Character.isJavaIdentifierPart(c) || c == '.' || c == '(') + { + return true; + } + if (Character.isWhitespace(c)) + { + continue; + } + + return false; + } + + return false; + } + + @Override + public List getParameterizedCompletions(JTextComponent tc) + { + return Collections.emptyList(); + } +} + diff --git a/runelite-jshell/src/main/java/net/runelite/jshell/RLShellExecutionControl.java b/runelite-jshell/src/main/java/net/runelite/jshell/RLShellExecutionControl.java new file mode 100644 index 0000000000..76a32c6aac --- /dev/null +++ b/runelite-jshell/src/main/java/net/runelite/jshell/RLShellExecutionControl.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.jshell; + +import java.util.Map; +import jdk.jshell.execution.DirectExecutionControl; +import jdk.jshell.spi.ExecutionControl; +import jdk.jshell.spi.ExecutionControlProvider; +import jdk.jshell.spi.ExecutionEnv; + +public class RLShellExecutionControl extends DirectExecutionControl implements ExecutionControlProvider +{ + public RLShellExecutionControl() + { + } + + @Override + public String name() + { + return getClass().getName(); + } + + @Override + public ExecutionControl generate(ExecutionEnv env, Map parameters) throws Throwable + { + return this; + } +} diff --git a/runelite-jshell/src/main/java/net/runelite/jshell/RemappingThrowable.java b/runelite-jshell/src/main/java/net/runelite/jshell/RemappingThrowable.java new file mode 100644 index 0000000000..7fc5e6342d --- /dev/null +++ b/runelite-jshell/src/main/java/net/runelite/jshell/RemappingThrowable.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2021 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.jshell; + +import com.google.common.base.Strings; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import jdk.jshell.EvalException; + +class RemappingThrowable extends Throwable +{ + private final String source; + private final Map offsets; + private final Throwable wrapped; + private final Map dejaVu; + + public RemappingThrowable(String source, Map offsets, Throwable other) + { + this(source, offsets, other, new HashMap<>()); + } + + private RemappingThrowable(String source, Map offsets, Throwable other, Map dejaVu) + { + super(); + + this.source = source; + this.offsets = offsets; + this.wrapped = other; + this.dejaVu = dejaVu; + + dejaVu.put(wrapped, this); + + setStackTrace(Stream.of(wrapped.getStackTrace()) + .map(e -> + { + Integer boxOffset = offsets.get(e.getFileName()); + if (boxOffset == null) + { + return e; + } + + int offset = boxOffset; + int line = e.getLineNumber(); + for (int i = 0; i <= offset && i < source.length(); i++) + { + if (source.charAt(i) == '\n') + { + line++; + } + } + return new StackTraceElement( + Strings.isNullOrEmpty(e.getClassName()) ? "Shell" : e.getClassName(), + Strings.isNullOrEmpty(e.getMethodName()) ? "global" : e.getMethodName(), + "", + line); + }) + .toArray(StackTraceElement[]::new)); + + if (wrapped.getCause() != null) + { + initCause(remap(wrapped.getCause())); + } + + for (Throwable suppressed : wrapped.getSuppressed()) + { + addSuppressed(remap(suppressed)); + } + } + + private Throwable remap(Throwable other) + { + Throwable remap = dejaVu.get(other); + if (remap == null) + { + remap = new RemappingThrowable(source, offsets, other, dejaVu); + // ctor inserts into the map + } + return remap; + } + + @Override + public String getMessage() + { + return wrapped.getMessage(); + } + + @Override + public String getLocalizedMessage() + { + return wrapped.getLocalizedMessage(); + } + + @Override + public synchronized Throwable fillInStackTrace() + { + return this; + } + + @Override + public String toString() + { + String className; + if (wrapped instanceof EvalException) + { + className = ((EvalException) wrapped).getExceptionClassName(); + } + else + { + className = wrapped.getClass().getName(); + } + + String message = wrapped.getLocalizedMessage(); + if (message == null) + { + return className; + } + return className + ": " + message; + } +} diff --git a/runelite-jshell/src/main/java/net/runelite/jshell/ShellPanel.java b/runelite-jshell/src/main/java/net/runelite/jshell/ShellPanel.java new file mode 100644 index 0000000000..1fd17a5711 --- /dev/null +++ b/runelite-jshell/src/main/java/net/runelite/jshell/ShellPanel.java @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2021 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.jshell; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Injector; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.RenderingHints; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.text.BadLocationException; +import javax.swing.text.Segment; +import jdk.jshell.Diag; +import jdk.jshell.JShell; +import jdk.jshell.Snippet; +import jdk.jshell.SnippetEvent; +import jdk.jshell.SourceCodeAnalysis; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.fife.ui.autocomplete.AutoCompletion; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rtextarea.RTextScrollPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Slf4j +public abstract class ShellPanel extends JPanel +{ + private final ScheduledExecutorService executor; + + private final RSyntaxTextArea textArea; + private final JTextArea console = new JTextArea(); + + @Getter + private final Logger shellLogger; + + private final List cleanup = new ArrayList<>(); + + private RLShellExecutionControl exec; + private JShell shell; + private Set prelude; + private Injector injector; + private AutoCompletion autoCompletion; + + public static ShellPanel INSTANCE; + + public ShellPanel(ScheduledExecutorService executor) + { + this.executor = executor; + + Font codeFont = Stream.of( + "Source code pro", + "DejaVu Sans Code", + "Consolas", + Font.MONOSPACED) + .map(name -> new Font(name, Font.PLAIN, 12)) + .filter(f -> !"Dialog.plain".equals(f.getFontName())) + .findFirst() + .get(); + + setLayout(new BorderLayout()); + + JPanel topPanel = new JPanel(); + topPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + + JButton run = new JButton("⯈"); + run.setToolTipText("Run"); + run.addActionListener(ev -> run()); + topPanel.add(run); + + JButton clear = new JButton("🗑"); + run.setToolTipText("Clear console"); + clear.addActionListener(ev -> console.setText("")); + topPanel.add(clear); + + add(topPanel, BorderLayout.NORTH); + + textArea = new RSyntaxTextArea(); + + try + { + // RSyntaxTextArea::setAntiAliasingEnabled actually forces it to match the platform's + // default, which is pointless + var map = new HashMap(); + map.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + var f = RSyntaxTextArea.class.getDeclaredField("aaHints"); + f.setAccessible(true); + f.set(textArea, map); + } + catch (ReflectiveOperationException e) + { + throw new RuntimeException(e); + } + + textArea.setFont(codeFont); + textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + textArea.setAutoIndentEnabled(true); + textArea.setPaintTabLines(true); + textArea.setShowMatchedBracketPopup(true); + textArea.setCloseCurlyBraces(false); + textArea.setTabSize(2); + textArea.setMarkOccurrences(true); + textArea.setMarkOccurrencesDelay(200); + textArea.addKeyListener(new KeyAdapter() + { + @Override + public void keyPressed(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_R && (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) + { + run(); + e.consume(); + } + if (e.getKeyCode() == KeyEvent.VK_F10) + { + run(); + e.consume(); + } + } + }); + + var textScrollArea = new RTextScrollPane(textArea); + + try + { + Theme.load(ShellPanel.class.getResourceAsStream("darcula.xml"), codeFont) + .apply(textArea); + + try (var is = ShellPanel.class.getResourceAsStream("default.jsh")) + { + textArea.setText(new String(is.readAllBytes(), StandardCharsets.UTF_8)); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + console.setFont(codeFont); + console.setFocusable(false); + console.setEditable(false); + console.setOpaque(false); // this turns off the hover effect for some reason + + var split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, textScrollArea, new JScrollPane(console)); + split.setResizeWeight(.8); + split.setPreferredSize(new Dimension(800, 800)); + add(split, BorderLayout.CENTER); + + shellLogger = new TeeLogger(LoggerFactory.getLogger("Shell"), this::logToConsole); + INSTANCE = this; + + // make sure jshell is on the classpath + JShell.builder(); + } + + public void switchContext(Injector injector) + { + freeContext(); + + this.injector = injector; + + exec = new RLShellExecutionControl() + { + @Override + protected String invoke(Method doitMethod) throws Exception + { + var result = new AtomicReference<>(); + var sema = new Semaphore(0); + invokeOnClientThread(() -> + { + try + { + result.set(super.invoke(doitMethod)); + } + catch (Exception e) + { + result.set(e); + } + finally + { + sema.release(); + } + }); + sema.acquire(); + if (result.get() instanceof String) + { + return (String) result.get(); + } + throw (Exception) result.get(); + } + }; + + shell = JShell.builder() + .executionEngine(exec, null) + .build(); + + String preludeStr; + try (var is = ShellPanel.class.getResourceAsStream("prelude.jsh")) + { + preludeStr = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + prelude = ImmutableSet.copyOf(eval(preludeStr, false)); + + var cp = new JShellAutocompleteProvider(shell); + autoCompletion = new AutoCompletion(cp); + autoCompletion.setAutoActivationDelay(200); + autoCompletion.setAutoActivationEnabled(true); + autoCompletion.setAutoCompleteSingleChoices(false); + autoCompletion.install(this.textArea); + } + + public void logToConsole(String message) + { + SwingUtilities.invokeLater(() -> + { + try + { + var doc = console.getDocument(); + if (doc.getLength() > 100_000) + { + Segment seg = new Segment(); + int i = doc.getLength() - 75_000; + for (; i < doc.getLength(); i++) + { + doc.getText(i, 1, seg); + if (seg.array[0] == '\n') + { + break; + } + } + doc.remove(0, i); + } + doc.insertString(doc.getLength(), message + "\n", null); + console.setCaretPosition(doc.getLength()); + } + catch (BadLocationException e) + { + throw new RuntimeException(e); + } + }); + } + + private List eval(String src, boolean isUserCode) + { + var out = new ArrayList(); + var offsets = new HashMap(); + String output = null; + evaluation: + for (int offset = 0; offset < src.length(); ) + { + // Workaround a jdk bug + for (; src.charAt(offset) == '\n'; offset++); + + var ci = shell.sourceCodeAnalysis().analyzeCompletion(src.substring(offset)); + int thisOffset = offset; + offset = src.length() - ci.remaining().length(); + if (ci.completeness() == SourceCodeAnalysis.Completeness.EMPTY) + { + break; + } + + List evs = shell.eval(ci.source()); + for (var ev : evs) + { + Snippet snip = ev.snippet(); + offsets.put("#" + snip.id(), thisOffset); + if (ev.status() != Snippet.Status.VALID && ev.status() != Snippet.Status.RECOVERABLE_DEFINED) + { + var diags = shell.diagnostics(snip).collect(Collectors.toList()); + for (var diag : diags) + { + String msg = toStringDiagnostic(src, thisOffset, diag); + if (isUserCode) + { + logToConsole(msg); + // It might be nice to highlight stuff here + } + else + { + throw new RuntimeException("prelude error: " + msg); + } + } + if (diags.isEmpty()) + { + logToConsole("bad snippet" + ev.status()); + } + break evaluation; + } + if (ev.exception() != null) + { + if (isUserCode) + { + shellLogger.error("", new RemappingThrowable(src, offsets, ev.exception())); + } + else + { + throw new RuntimeException("prelude error", ev.exception()); + } + } + output = ev.value(); + + out.add(snip); + } + } + + if (isUserCode && !Strings.isNullOrEmpty(output)) + { + logToConsole("[OUTPUT] " + output); + } + + return out; + } + + private String toStringDiagnostic(String source, int offset, Diag diag) + { + int line = 1; + int column = 1; + offset += (int) diag.getPosition(); + for (int i = 0; i < offset && i < source.length(); i++) + { + if (source.charAt(i) == '\n') + { + line++; + column = 1; + } + else + { + column++; + } + } + + return line + ":" + column + ": " + diag.getMessage(Locale.getDefault()); + } + + protected void run() + { + String text = textArea.getText(); + executor.submit(() -> + { + shell.snippets() + .filter(v -> !prelude.contains(v)) + .forEach(shell::drop); + + cleanup(); + + eval(text, true); + }); + } + + public void freeContext() + { + cleanup(); + + exec = null; + shell = null; + prelude = null; + injector = null; + + if (autoCompletion != null) + { + autoCompletion.uninstall(); + } + autoCompletion = null; + + console.setText(""); + } + + private void cleanup() + { + for (var c : cleanup) + { + try + { + c.run(); + } + catch (Exception e) + { + shellLogger.error("Cleanup threw:", e); + } + } + cleanup.clear(); + } + + protected abstract void invokeOnClientThread(Runnable r); + + public T inject(Class clazz) + { + return injector.getInstance(clazz); + } + + public void cleanup(Runnable r) + { + cleanup.add(r); + } +} diff --git a/runelite-jshell/src/main/java/net/runelite/jshell/TeeLogger.java b/runelite-jshell/src/main/java/net/runelite/jshell/TeeLogger.java new file mode 100644 index 0000000000..9893842243 --- /dev/null +++ b/runelite-jshell/src/main/java/net/runelite/jshell/TeeLogger.java @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2021 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.jshell; + +import java.io.CharArrayWriter; +import java.io.PrintWriter; +import java.util.function.Consumer; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; + +@SuppressWarnings("PlaceholderCountMatchesArgumentCount") +@RequiredArgsConstructor +public class TeeLogger implements Logger +{ + private static final String TRACE = "[TRACE] "; + private static final String DEBUG = "[DEBUG] "; + private static final String INFO = "[INFO] "; + private static final String WARN = "[WARN] "; + private static final String ERROR = "[ERROR] "; + + private final Logger delegate; + private final Consumer messageConsumer; + + @Override + public String getName() + { + return "RLShell"; + } + + @Override + public boolean isTraceEnabled() + { + return true; + } + + private void log(String level, String message, Object... format) + { + FormattingTuple fmt = MessageFormatter.arrayFormat(message, format); + StringBuilder msg = new StringBuilder(); + msg.append(level).append(fmt.getMessage()); + Throwable throwable = fmt.getThrowable(); + if (throwable != null) + { + msg.append("\n"); + var caw = new CharArrayWriter(); + try (PrintWriter pw = new PrintWriter(caw)) + { + throwable.printStackTrace(pw); + } + msg.append(caw.toString()); + } + messageConsumer.accept(msg.toString()); + } + + @Override + public void trace(String msg) + { + delegate.trace(msg); + log(TRACE, msg); + } + + @Override + public void trace(String format, Object arg) + { + delegate.trace(format, arg); + log(TRACE, format, arg); + } + + @Override + public void trace(String format, Object arg1, Object arg2) + { + delegate.trace(format, arg1, arg2); + log(TRACE, format, arg1, arg2); + } + + @Override + public void trace(String format, Object... arguments) + { + delegate.trace(format, arguments); + log(TRACE, format, arguments); + } + + @Override + public void trace(String msg, Throwable t) + { + delegate.trace(msg, t); + log(TRACE, msg, t); + } + + @Override + public boolean isTraceEnabled(Marker marker) + { + return true; + } + + @Override + public void trace(Marker marker, String msg) + { + delegate.trace(marker, msg); + log(TRACE, msg); + } + + @Override + public void trace(Marker marker, String format, Object arg) + { + delegate.trace(marker, format, arg); + log(TRACE, format, arg); + } + + @Override + public void trace(Marker marker, String format, Object arg1, Object arg2) + { + delegate.trace(marker, format, arg1, arg2); + log(TRACE, format, arg1, arg2); + } + + @Override + public void trace(Marker marker, String format, Object... argArray) + { + delegate.trace(marker, format, argArray); + log(TRACE, format, argArray); + } + + @Override + public void trace(Marker marker, String msg, Throwable t) + { + delegate.trace(marker, msg, t); + log(TRACE, msg, t); + } + + @Override + public boolean isDebugEnabled() + { + return true; + } + + @Override + public void debug(String msg) + { + delegate.debug(msg); + log(DEBUG, msg); + } + + @Override + public void debug(String format, Object arg) + { + delegate.debug(format, arg); + log(DEBUG, format, arg); + } + + @Override + public void debug(String format, Object arg1, Object arg2) + { + delegate.debug(format, arg1, arg2); + log(DEBUG, format, arg1, arg2); + } + + @Override + public void debug(String format, Object... arguments) + { + delegate.debug(format, arguments); + log(DEBUG, format, arguments); + } + + @Override + public void debug(String msg, Throwable t) + { + delegate.debug(msg, t); + log(DEBUG, msg, t); + } + + @Override + public boolean isDebugEnabled(Marker marker) + { + return true; + } + + @Override + public void debug(Marker marker, String msg) + { + delegate.debug(marker, msg); + log(DEBUG, msg); + } + + @Override + public void debug(Marker marker, String format, Object arg) + { + delegate.debug(marker, format, arg); + log(DEBUG, format, arg); + } + + @Override + public void debug(Marker marker, String format, Object arg1, Object arg2) + { + delegate.debug(marker, format, arg1, arg2); + log(DEBUG, format, arg1, arg2); + } + + @Override + public void debug(Marker marker, String format, Object... arguments) + { + delegate.debug(marker, format, arguments); + log(DEBUG, format, arguments); + } + + @Override + public void debug(Marker marker, String msg, Throwable t) + { + delegate.debug(marker, msg, t); + log(DEBUG, msg, t); + } + + @Override + public boolean isInfoEnabled() + { + return true; + } + + @Override + public void info(String msg) + { + delegate.info(msg); + log(INFO, msg); + } + + @Override + public void info(String format, Object arg) + { + delegate.info(format, arg); + log(INFO, format, arg); + } + + @Override + public void info(String format, Object arg1, Object arg2) + { + delegate.info(format, arg1, arg2); + log(INFO, format, arg1, arg2); + } + + @Override + public void info(String format, Object... arguments) + { + delegate.info(format, arguments); + log(INFO, format, arguments); + } + + @Override + public void info(String msg, Throwable t) + { + delegate.info(msg, t); + log(INFO, msg, t); + } + + @Override + public boolean isInfoEnabled(Marker marker) + { + return true; + } + + @Override + public void info(Marker marker, String msg) + { + delegate.info(marker, msg); + log(INFO, msg); + } + + @Override + public void info(Marker marker, String format, Object arg) + { + delegate.info(marker, format, arg); + log(INFO, format, arg); + } + + @Override + public void info(Marker marker, String format, Object arg1, Object arg2) + { + delegate.info(marker, format, arg1, arg2); + log(INFO, format, arg1, arg2); + } + + @Override + public void info(Marker marker, String format, Object... arguments) + { + delegate.info(marker, format, arguments); + log(INFO, format, arguments); + } + + @Override + public void info(Marker marker, String msg, Throwable t) + { + delegate.info(marker, msg, t); + log(INFO, msg, t); + } + + @Override + public boolean isWarnEnabled() + { + return true; + } + + @Override + public void warn(String msg) + { + delegate.warn(msg); + log(WARN, msg); + } + + @Override + public void warn(String format, Object arg) + { + delegate.warn(format, arg); + log(WARN, format, arg); + } + + @Override + public void warn(String format, Object... arguments) + { + delegate.warn(format, arguments); + log(WARN, format, arguments); + } + + @Override + public void warn(String format, Object arg1, Object arg2) + { + delegate.warn(format, arg1, arg2); + log(WARN, format, arg1, arg2); + } + + @Override + public void warn(String msg, Throwable t) + { + delegate.warn(msg, t); + log(WARN, msg, t); + } + + @Override + public boolean isWarnEnabled(Marker marker) + { + return true; + } + + @Override + public void warn(Marker marker, String msg) + { + delegate.warn(marker, msg); + log(WARN, msg); + } + + @Override + public void warn(Marker marker, String format, Object arg) + { + delegate.warn(marker, format, arg); + log(WARN, format, arg); + } + + @Override + public void warn(Marker marker, String format, Object arg1, Object arg2) + { + delegate.warn(marker, format, arg1, arg2); + log(WARN, format, arg1, arg2); + } + + @Override + public void warn(Marker marker, String format, Object... arguments) + { + delegate.warn(marker, format, arguments); + log(WARN, format, arguments); + } + + @Override + public void warn(Marker marker, String msg, Throwable t) + { + delegate.warn(marker, msg, t); + log(WARN, msg, t); + } + + @Override + public boolean isErrorEnabled() + { + return true; + } + + @Override + public void error(String msg) + { + delegate.error(msg); + log(ERROR, msg); + } + + @Override + public void error(String format, Object arg) + { + delegate.error(format, arg); + log(ERROR, format, arg); + } + + @Override + public void error(String format, Object arg1, Object arg2) + { + delegate.error(format, arg1, arg2); + log(ERROR, format, arg1, arg2); + } + + @Override + public void error(String format, Object... arguments) + { + delegate.error(format, arguments); + log(ERROR, format, arguments); + } + + @Override + public void error(String msg, Throwable t) + { + delegate.error(msg, t); + log(ERROR, msg, t); + } + + @Override + public boolean isErrorEnabled(Marker marker) + { + return true; + } + + @Override + public void error(Marker marker, String msg) + { + delegate.error(marker, msg); + log(ERROR, msg); + } + + @Override + public void error(Marker marker, String format, Object arg) + { + delegate.error(marker, format, arg); + log(ERROR, format, arg); + } + + @Override + public void error(Marker marker, String format, Object arg1, Object arg2) + { + delegate.error(marker, format, arg1, arg2); + log(ERROR, format, arg1, arg2); + } + + @Override + public void error(Marker marker, String format, Object... arguments) + { + delegate.error(marker, format, arguments); + log(ERROR, format, arguments); + } + + @Override + public void error(Marker marker, String msg, Throwable t) + { + delegate.error(marker, msg, t); + log(ERROR, msg, t); + } +} diff --git a/runelite-jshell/src/main/resources/net/runelite/jshell/darcula.xml b/runelite-jshell/src/main/resources/net/runelite/jshell/darcula.xml new file mode 100644 index 0000000000..43729730ad --- /dev/null +++ b/runelite-jshell/src/main/resources/net/runelite/jshell/darcula.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +