Merge remote-tracking branch 'runelite/master'

This commit is contained in:
Owain van Brakel
2021-03-02 13:16:12 +01:00
51 changed files with 2748 additions and 414 deletions

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019 Owain van Brakel <https://github.com/Owain94>
* 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.
*/
description = "RuneLite JShell"
dependencies {
annotationProcessor(group = "org.projectlombok", name = "lombok", version = "1.18.4")
compileOnly(group = "org.projectlombok", name = "lombok", version = "1.18.4")
implementation(group = "com.google.code.findbugs", name = "jsr305", version = "3.0.2")
implementation(group = "com.google.inject", name = "guice", version = "4.1.0", classifier = "no_aop")
implementation(group = "com.fifesoft", name = "rsyntaxtextarea", version = "3.1.2")
implementation(group = "com.fifesoft", name = "autocomplete", version = "3.1.1")
implementation(group = "org.slf4j", name = "slf4j-api", version = "1.7.12")
}

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