Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ void removeNavigation(NavigationButton navBtn)
: selectedTabHistory.removeLast();

openPanel(entry.navBtn, entry.sidebarOpen);
saveSidebarTab();
}
}
finally
Expand Down Expand Up @@ -452,15 +453,7 @@ public void componentMoved(ComponentEvent e)

if (!updatingNavigation)
{
sidebarTabRestored = true;
if (newSelectedTab == null)
{
configManager.unsetConfiguration(CONFIG_GROUP, CONFIG_CLIENT_SIDEBAR_TAB);
}
else
{
configManager.setConfiguration(CONFIG_GROUP, CONFIG_CLIENT_SIDEBAR_TAB, newSelectedTab.getTooltip());
}
saveSidebarTab();
}

if (sidebar.isVisible())
Expand Down Expand Up @@ -764,6 +757,7 @@ public void show()
frame.revalidateMinimumSize();
// this must run after the native window border is installed on the window
frame.updateContainsInScreen();
sidebarTabRestored = true;
});

// Show out of date dialog if needed
Expand Down Expand Up @@ -1125,6 +1119,19 @@ public GraphicsConfiguration getGraphicsConfiguration()
return frame.getGraphicsConfiguration();
}

private void saveSidebarTab()
{
sidebarTabRestored = true;
if (selectedTab == null)
{
configManager.unsetConfiguration(CONFIG_GROUP, CONFIG_CLIENT_SIDEBAR_TAB);
}
else
{
configManager.setConfiguration(CONFIG_GROUP, CONFIG_CLIENT_SIDEBAR_TAB, selectedTab.getTooltip());
}
}

void openPanel(NavigationButton navBtn, boolean showSidebar)
{
if (navBtn != null && !sidebarEntries.contains(navBtn))
Expand Down Expand Up @@ -1294,6 +1301,7 @@ private void initializeConsoleLogging()
System.setOut(consolePrintStream);

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
ConsoleLogAppender.bindStdoutAppenders(loggerContext, originalOut);
consoleLogAppender = new ConsoleLogAppender(consolePanel::append);
consoleLogAppender.setContext(loggerContext);
consoleLogAppender.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@
*/
package net.runelite.client.ui;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.joran.spi.ConsoleTarget;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.function.Consumer;

final class ConsoleLogAppender extends AppenderBase<ILoggingEvent>
Expand All @@ -19,6 +26,23 @@ final class ConsoleLogAppender extends AppenderBase<ILoggingEvent>
this.logConsumer = logConsumer;
}

static void bindStdoutAppenders(LoggerContext context, OutputStream stdout)
{
for (Logger logger : context.getLoggerList())
{
Iterator<Appender<ILoggingEvent>> appenders = logger.iteratorForAppenders();
while (appenders.hasNext())
{
Appender<ILoggingEvent> appender = appenders.next();
if (appender instanceof ConsoleAppender
&& ConsoleTarget.SystemOut.getName().equals(((ConsoleAppender<ILoggingEvent>) appender).getTarget()))
{
((ConsoleAppender<ILoggingEvent>) appender).setOutputStream(stdout);
}
}
}
}

@Override
public void start()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import java.awt.*;
import java.awt.event.HierarchyEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
Expand Down Expand Up @@ -45,6 +47,7 @@ final class LogConsolePanel extends JPanel
private boolean followTail = true;
private boolean snapQueued;
private int snapAttempts;
private int tailCaretDot = -1;

LogConsolePanel()
{
Expand All @@ -61,15 +64,65 @@ final class LogConsolePanel extends JPanel
textArea.setForeground(new Color(0, 255, 70));
textArea.setBorder(null);

DefaultCaret caret = (DefaultCaret) textArea.getCaret();
DefaultCaret caret = new DefaultCaret()
{
@Override
protected void adjustVisibility(Rectangle nloc)
{
if (getDot() != tailCaretDot || getMark() != tailCaretDot)
{
super.adjustVisibility(nloc);
}
}
};
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
textArea.setCaret(caret);

scrollPane.setBorder(null);
add(scrollPane, BorderLayout.CENTER);

// Following is decided by what the user does, never by measuring the view: under a flood the
// scroll position trails the document, which reads as "the user scrolled up".
scrollPane.addMouseWheelListener(ev -> reconsiderFollowingTail());
scrollPane.addMouseWheelListener(ev ->
{
if (ev.getPreciseWheelRotation() < 0)
{
followTail = false;
}
reconsiderFollowingTail();
});

textArea.addKeyListener(new KeyAdapter()
{
@Override
public void keyPressed(KeyEvent ev)
{
followTail = false;
SwingUtilities.invokeLater(LogConsolePanel.this::reconsiderFollowingTail);
}
});
textArea.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent ev)
{
followTail = false;
}

@Override
public void mouseReleased(MouseEvent ev)
{
reconsiderFollowingTail();
}
});
textArea.addMouseMotionListener(new MouseMotionAdapter()
{
@Override
public void mouseDragged(MouseEvent ev)
{
followTail = false;
}
});

JScrollBar vertical = scrollPane.getVerticalScrollBar();
vertical.addMouseListener(new MouseAdapter()
Expand Down Expand Up @@ -135,7 +188,7 @@ private void queueSnapToBottom()
private void snapToBottom()
{
int end = textArea.getDocument().getLength();
textArea.setCaretPosition(end);
moveCaretToTail();

Rectangle2D newestLine;
try
Expand Down Expand Up @@ -184,11 +237,10 @@ void append(String text)
{
pending.append(text, 0, text.length());

int excess = pending.length() - MAX_CHARACTERS_SCROLLED_UP;
if (excess > 0)
if (pending.length() > 2 * MAX_CHARACTERS_SCROLLED_UP)
{
// the oldest buffered lines would be trimmed out of the document on arrival anyway
pending.delete(0, excess);
pending.delete(0, pending.length() - MAX_CHARACTERS_SCROLLED_UP);
}

if (flushScheduled)
Expand All @@ -212,7 +264,8 @@ private void flushPending()
return;
}

text = pending.toString();
int cap = followTail ? MAX_CHARACTERS : MAX_CHARACTERS_SCROLLED_UP;
text = pending.substring(Math.max(0, pending.length() - cap));
pending.setLength(0);
}

Expand Down Expand Up @@ -252,7 +305,7 @@ private void appendOnEdt(String text)
trimIfNecessary(atBottom);
if (atBottom)
{
textArea.setCaretPosition(document.getLength());
moveCaretToTail();
// NEVER_UPDATE stops the caret dragging the view, so move it ourselves; the attempt
// budget is per batch, or a flood that outruns the layout would exhaust it for good.
snapAttempts = 0;
Expand All @@ -265,12 +318,18 @@ private void appendOnEdt(String text)
}
}

private void moveCaretToTail()
{
tailCaretDot = textArea.getDocument().getLength();
textArea.setCaretPosition(tailCaretDot);
}

// Deferred so the user's gesture has reached the scrollbar before it is read.
private void reconsiderFollowingTail()
{
SwingUtilities.invokeLater(() ->
{
followTail = isScrolledToBottom();
followTail = isScrolledToBottom() && textArea.getSelectionStart() == textArea.getSelectionEnd();
if (followTail)
{
snapAttempts = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package net.runelite.client.ui;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ConsoleLogAppenderTest
{
private PrintStream originalOut;
private LoggerContext context;

@Before
public void setUp()
{
originalOut = System.out;
context = new LoggerContext();
}

@After
public void tearDown()
{
System.setOut(originalOut);
context.stop();
}

@Test
public void logLineReachesTheConsoleOnceAndStdoutOnce()
{
ByteArrayOutputStream terminal = new ByteArrayOutputStream();
ByteArrayOutputStream consolePanel = new ByteArrayOutputStream();
StringBuilder console = new StringBuilder();

Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(context);
encoder.setPattern("%msg%n");
encoder.start();
ConsoleAppender<ILoggingEvent> stdout = new ConsoleAppender<>();
stdout.setContext(context);
stdout.setEncoder(encoder);
stdout.start();
root.addAppender(stdout);

System.setOut(new PrintStream(new Tee(consolePanel, terminal), true, StandardCharsets.UTF_8));

ConsoleLogAppender.bindStdoutAppenders(context, terminal);
ConsoleLogAppender appender = new ConsoleLogAppender(console::append);
appender.setContext(context);
appender.start();
root.addAppender(appender);

root.info("flood line");
System.out.println("plain print");

String panel = console + consolePanel.toString(StandardCharsets.UTF_8);
assertEquals(1, occurrences(panel, "flood line"));
assertEquals(1, occurrences(panel, "plain print"));

String out = terminal.toString(StandardCharsets.UTF_8);
assertEquals(1, occurrences(out, "flood line"));
assertEquals(1, occurrences(out, "plain print"));
}

private static int occurrences(String text, String needle)
{
int count = 0;
for (int i = text.indexOf(needle); i >= 0; i = text.indexOf(needle, i + 1))
{
count++;
}
return count;
}

private static final class Tee extends OutputStream
{
private final OutputStream first;
private final OutputStream second;

private Tee(OutputStream first, OutputStream second)
{
this.first = first;
this.second = second;
}

@Override
public void write(int b) throws IOException
{
first.write(b);
second.write(b);
}
}
}
Loading
Loading