Merge pull request #13277 from abextm/jshell

devtools: add shell window
This commit is contained in:
Adam
2021-02-26 14:17:33 -05:00
committed by GitHub
22 changed files with 1756 additions and 188 deletions

34
pom.xml
View File

@@ -119,6 +119,7 @@
<module>cache-updater</module>
<module>runelite-api</module>
<module>runelite-client</module>
<module>runelite-jshell</module>
<module>runelite-script-assembler-plugin</module>
<module>http-api</module>
<module>http-service</module>
@@ -142,6 +143,11 @@
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-parent</artifactId>
@@ -149,37 +155,25 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice-bom</artifactId>
<version>4.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
<profile>
<id>java9</id>
<activation>
<jdk>[1.9,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${java.release}</release>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<release>${java.release}</release>
</configuration>
</plugin>
<plugin>

View File

@@ -48,7 +48,6 @@
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<scope>provided</scope>
</dependency>

View File

@@ -36,8 +36,6 @@
<name>RuneLite Client</name>
<properties>
<guice.version>4.1.0</guice.version>
<jarsigner.skip>true</jarsigner.skip>
<pmd.skip>true</pmd.skip>
</properties>
@@ -85,7 +83,6 @@
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>${guice.version}</version>
<classifier>no_aop</classifier>
</dependency>
<dependency>
@@ -215,7 +212,6 @@
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
@@ -223,6 +219,12 @@
<artifactId>runelite-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>jshell</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>client-patch</artifactId>
@@ -267,13 +269,11 @@
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-testlib</artifactId>
<version>${guice.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-grapher</artifactId>
<version>${guice.version}</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -318,7 +318,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>

View File

@@ -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<Object> 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<Throwable> exceptionHandler;
private ImmutableMultimap<Class, Subscriber> subscribers = ImmutableMultimap.of();
@Nonnull
private ImmutableMultimap<Class<?>, 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<Class, Subscriber> builder = ImmutableMultimap.builder();
if (subscribers != null)
{
builder.putAll(subscribers);
}
final ImmutableMultimap.Builder<Class<?>, 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<Object> 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<Object>) factory.bindTo(object).invokeExact();
}
catch (Throwable e)
{
@@ -173,6 +164,21 @@ public class EventBus
subscribers = builder.build();
}
public synchronized <T> Subscriber register(Class<T> clazz, Consumer<T> subFn, float priority)
{
final ImmutableMultimap.Builder<Class<?>, 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<Object>) 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<Class, Subscriber> map = HashMultimap.create();
final Multimap<Class<?>, 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<Class<?>, 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.

View File

@@ -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();
}
});
}
}

View File

@@ -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);
}
}

View File

@@ -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;
@@ -129,30 +132,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 +147,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,22 +171,27 @@ 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)));
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;
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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)

View File

@@ -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();
}
}

View File

@@ -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<Integer, Object> 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;
}

View File

@@ -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<Integer, WidgetInfo> widgetIdMap = new HashMap<>();
@@ -123,7 +119,6 @@ class WidgetInspector extends JFrame
ClientThread clientThread,
WidgetInfoTableModel infoTableModel,
DevToolsConfig config,
DevToolsPlugin plugin,
EventBus eventBus,
Provider<WidgetInspectorOverlay> 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()

83
runelite-jshell/pom.xml Normal file
View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId>
<version>1.7.2-SNAPSHOT</version>
</parent>
<artifactId>jshell</artifactId>
<name>RuneLite JShell</name>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<classifier>no_aop</classifier>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>com.fifesoft</groupId>
<artifactId>rsyntaxtextarea</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>com.fifesoft</groupId>
<artifactId>autocomplete</artifactId>
<version>3.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<release>11</release>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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<Completion> completions;
@Override
protected List<Completion> 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<Completion> 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<ParameterizedCompletion> getParameterizedCompletions(JTextComponent tc)
{
return Collections.emptyList();
}
}

View File

@@ -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<String, String> parameters) throws Throwable
{
return this;
}
}

View File

@@ -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<String, Integer> offsets;
private final Throwable wrapped;
private final Map<Throwable, Throwable> dejaVu;
public RemappingThrowable(String source, Map<String, Integer> offsets, Throwable other)
{
this(source, offsets, other, new HashMap<>());
}
private RemappingThrowable(String source, Map<String, Integer> offsets, Throwable other, Map<Throwable, Throwable> 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;
}
}

View File

@@ -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<Runnable> cleanup = new ArrayList<>();
private RLShellExecutionControl exec;
private JShell shell;
private Set<Snippet> 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<RenderingHints.Key, Object>();
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<Snippet> eval(String src, boolean isUserCode)
{
var out = new ArrayList<Snippet>();
var offsets = new HashMap<String, Integer>();
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<SnippetEvent> 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> T inject(Class<T> clazz)
{
return injector.getInstance(clazz);
}
public void cleanup(Runnable r)
{
cleanup.add(r);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE RSyntaxTheme SYSTEM "theme.dtd">
<RSyntaxTheme version="1.0">
<!-- General editor colors. -->
<background color="2B2B2B"/>
<caret color="BBBBBB"/>
<selection useFG="false" bg="214283" roundedEdges="false"/>
<currentLineHighlight color="323232" fade="false"/>
<marginLine fg="394448"/>
<markAllHighlight color="155221"/>
<markOccurrencesHighlight color="32593D" border="true"/>
<matchedBracket fg="A9B7C6" bg="2E2E2E" highlightBoth="true" animate="true"/><!--ij draws a border-->
<hyperlinks fg="287BDE"/>
<secondaryLanguages>
<language index="1" bg="333344"/>
<language index="2" bg="223322"/>
<language index="3" bg="332222"/>
</secondaryLanguages>
<!-- Gutter styling. -->
<gutterBorder color="606366"/>
<lineNumbers fg="606366"/>
<foldIndicator fg="6A8088" iconBg="2f383c" iconArmedBg="3f484c"/>
<iconRowHeader activeLineRange="878787"/>
<!-- Syntax tokens. -->
<tokenStyles>
<style token="IDENTIFIER" fg="A9B7C6"/>
<style token="RESERVED_WORD" fg="CC7832" bold="false"/>
<style token="RESERVED_WORD_2" fg="CC7832" bold="false"/>
<style token="ANNOTATION" fg="BBB529"/>
<style token="COMMENT_DOCUMENTATION" fg="629755"/>
<style token="COMMENT_EOL" fg="808080"/>
<style token="COMMENT_MULTILINE" fg="808080"/>
<style token="COMMENT_KEYWORD" fg="629755"/>
<style token="COMMENT_MARKUP" fg="77B767"/>
<style token="FUNCTION" fg="A9B7C6"/><!-- any identifier magically known -->
<style token="DATA_TYPE" fg="CC7832" bold="false"/>
<style token="LITERAL_BOOLEAN" fg="CC7832" bold="false"/>
<style token="LITERAL_NUMBER_DECIMAL_INT" fg="6897BB"/>
<style token="LITERAL_NUMBER_FLOAT" fg="6897BB"/>
<style token="LITERAL_NUMBER_HEXADECIMAL" fg="6897BB"/>
<style token="LITERAL_STRING_DOUBLE_QUOTE" fg="6A8759"/>
<style token="LITERAL_CHAR" fg="6A8759"/>
<style token="LITERAL_BACKQUOTE" fg="6A8759"/>
<!-- all wrong but nobody will write xml in this -->
<style token="MARKUP_TAG_DELIMITER" fg="F92672"/>
<style token="MARKUP_TAG_NAME" fg="ABBFD3" bold="true"/>
<style token="MARKUP_TAG_ATTRIBUTE" fg="B3B689"/>
<style token="MARKUP_TAG_ATTRIBUTE_VALUE" fg="e1e2cf"/>
<style token="MARKUP_COMMENT" fg="878787"/>
<style token="MARKUP_DTD" fg="A082BD"/>
<style token="MARKUP_PROCESSING_INSTRUCTION" fg="A082BD"/>
<style token="MARKUP_CDATA" fg="d5e6f0"/>
<style token="MARKUP_CDATA_DELIMITER" fg="FD971F"/>
<style token="MARKUP_ENTITY_REFERENCE" fg="F92672"/>
<style token="OPERATOR" fg="A9B7C6"/>
<style token="PREPROCESSOR" fg="A082BD"/>
<style token="REGEX" fg="6A8759"/>
<style token="SEPARATOR" fg="A9B7C6"/>
<style token="VARIABLE" fg="A9B7C6" bold="false"/>
<style token="WHITESPACE" fg="606060"/>
<style token="ERROR_IDENTIFIER" fg="F9F9F9" bg="d82323"/>
<style token="ERROR_NUMBER_FORMAT" fg="F9F9F9" bg="d82323"/>
<style token="ERROR_STRING_DOUBLE" fg="F9F9F9" bg="d82323"/>
<style token="ERROR_CHAR" fg="F9F9F9" bg="d82323"/>
</tokenStyles>
</RSyntaxTheme>

View File

@@ -0,0 +1,8 @@
// Welcome to the RuneLite Development Shell
// Everything executed here runs on the client thread by default.
// By default client, clientThread, configManager and log are in scope
// You can subscribe to the Event Bus by using subscribe(Event.class, ev -> handler);
// and you can access things in the global injector module with var thing = inject(Thing.class);
// Press Ctrl+R or F10 to execute the contents of this editor
log.info("Hello {}", client.getGameState());

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
import java.util.function.Consumer;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import org.slf4j.Logger;
import java.util.*;
import java.util.stream.*;
import net.runelite.api.*;
import net.runelite.api.coords.*;
import net.runelite.api.events.*;
import net.runelite.api.widgets.*;
import net.runelite.client.events.*;
import net.runelite.client.game.*;
var $PANEL = net.runelite.jshell.ShellPanel.INSTANCE;
Logger log = $PANEL.getShellLogger();
static <T> T inject(Class<T> clazz)
{
return $PANEL.inject(clazz);
}
static void cleanup(Runnable r)
{
$PANEL.cleanup(r);
}
var $EVENT_BUS = inject(net.runelite.client.eventbus.EventBus.class);
static <T> void subscribe(Class<T> eventType, Consumer<T> subscriber, float priority)
{
var sub = $EVENT_BUS.register(eventType, subscriber, priority);
cleanup(() -> $EVENT_BUS.unregister(sub));
}
static <T> void subscribe(Class<T> eventType, Consumer<T> subscriber)
{
var sub = $EVENT_BUS.register(eventType, subscriber, 0.f);
cleanup(() -> $EVENT_BUS.unregister(sub));
}
var client = inject(Client.class);
var clientThread = inject(ClientThread.class);
var configManager = inject(ConfigManager.class);