diff --git a/SubServers.Console/pom.xml b/SubServers.Console/pom.xml index 54756712..e597ae52 100644 --- a/SubServers.Console/pom.xml +++ b/SubServers.Console/pom.xml @@ -18,8 +18,8 @@ org.fusesource.jansi jansi - 1.17.1 - provided + 1.18 + compile com.intellij diff --git a/SubServers.Console/src/bungee.yml b/SubServers.Console/src/bungee.yml index afb6b85a..0c19b5c7 100644 --- a/SubServers.Console/src/bungee.yml +++ b/SubServers.Console/src/bungee.yml @@ -1,4 +1,4 @@ name: SubServers-Console main: net.ME1312.SubServers.Console.ConsolePlugin -version: 2.16a +version: 2.17a author: ME1312 \ No newline at end of file diff --git a/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleFont.ttf b/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleFont.ttf index 5d1579c7..41413c15 100644 Binary files a/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleFont.ttf and b/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleFont.ttf differ diff --git a/SubServers.Console/src/net/ME1312/SubServers/Console/ConsolePlugin.java b/SubServers.Console/src/net/ME1312/SubServers/Console/ConsolePlugin.java index 3ecc41e3..ed5a7432 100644 --- a/SubServers.Console/src/net/ME1312/SubServers/Console/ConsolePlugin.java +++ b/SubServers.Console/src/net/ME1312/SubServers/Console/ConsolePlugin.java @@ -5,6 +5,7 @@ import net.ME1312.SubServers.Bungee.Event.SubCreateEvent; import net.ME1312.SubServers.Bungee.Event.SubSendCommandEvent; import net.ME1312.SubServers.Bungee.Event.SubStartEvent; import net.ME1312.SubServers.Bungee.Host.Host; +import net.ME1312.SubServers.Bungee.Host.RemotePlayer; import net.ME1312.SubServers.Bungee.Host.SubCreator; import net.ME1312.SubServers.Bungee.Host.SubServer; import net.ME1312.SubServers.Bungee.SubAPI; @@ -19,6 +20,7 @@ import net.md_5.bungee.event.EventPriority; import javax.swing.*; import java.io.File; import java.io.IOException; +import java.util.Calendar; import java.util.Collections; import java.util.HashMap; @@ -103,7 +105,8 @@ public final class ConsolePlugin extends Plugin implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onServerCommand(SubSendCommandEvent event) { if (!event.isCancelled() && sCurrent.keySet().contains(event.getServer().getName().toLowerCase())) { - sCurrent.get(event.getServer().getName().toLowerCase()).log('<' + ((event.getPlayer() == null)?"CONSOLE":((getProxy().getPlayer(event.getPlayer()) == null)?event.getPlayer().toString():getProxy().getPlayer(event.getPlayer()).getName())) + "> /" + event.getCommand()); + RemotePlayer player = (event.getPlayer() == null)? null : SubAPI.getInstance().getRemotePlayer(event.getPlayer()); + sCurrent.get(event.getServer().getName().toLowerCase()).log(((player == null)? "CONSOLE" : player.getName()) + "> /" + event.getCommand()); } } diff --git a/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleWindow.java b/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleWindow.java index 38a34f59..9d9e803a 100644 --- a/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleWindow.java +++ b/SubServers.Console/src/net/ME1312/SubServers/Console/ConsoleWindow.java @@ -24,11 +24,15 @@ import java.io.*; import java.text.SimpleDateFormat; import java.util.List; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import java.util.logging.Level; +import static java.nio.charset.StandardCharsets.UTF_8; + public final class ConsoleWindow implements SubLogFilter { - private static final int MAX_SCROLLBACK = (Integer.getInteger("subservers.console.max_scrollback", 0) >= 128)?Integer.getInteger("subservers.console.max_scrollback"):7500; - private static final String RESET_VALUE = "\n\u00A0\n\u00A0"; + private static final int MAX_SCROLLBACK = (Integer.getInteger("subservers.console.max_scrollback", 0) > 0)?Integer.getInteger("subservers.console.max_scrollback"):7500; private ConsolePlugin plugin; private JFrame window; private double scale = 1.0; @@ -48,53 +52,56 @@ public final class ConsoleWindow implements SubLogFilter { private int findO = 0; private int findI = 0; private boolean open = false; - private boolean running = true; - private LinkedList messages = new LinkedList(); + private ExecutorService thread; private SubLogger logger; private int fontSize; private File file = null; private FileOutputStream filewriter = null; - private List spost = new LinkedList(); - private ByteArrayOutputStream scache = new ByteArrayOutputStream(); - private AnsiUIOutputStream stream = AnsiUIOutputStream.wrap(new OutputStream() { + private long sbytes = -Long.MAX_VALUE; + private LinkedList slines = new LinkedList(); + private LinkedList spost = new LinkedList(); + private AnsiUIOutputStream stream = HTMLogger.wrap(new OutputStream() { + private final ByteArrayOutputStream scache = new ByteArrayOutputStream(); private int countLines(String str) { int count = 0; - for (int i = 0; i < str.length(); i++) if (str.charAt(i) == '\n') count++; + for (int i = 1; i < str.codePoints().count(); i++) if (str.codePointAt(i) == '\n') count++; return count; } @Override public void write(int b) throws IOException { scache.write(b); - if (b == '\n') { - try { - HTMLEditorKit kit = (HTMLEditorKit) log.getEditorKit(); - HTMLDocument doc = (HTMLDocument) log.getDocument(); - kit.insertHTML(doc, doc.getLength() - 2, new String(scache.toByteArray(), "UTF-8"), 0, 0, null); - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - hScroll(); - } - }); - } catch (Exception e) {} try { - int lines; - String content; - if (log.getSelectionStart() == log.getSelectionEnd() && (lines = countLines(content = log.getDocument().getText(0, log.getDocument().getLength()))) > MAX_SCROLLBACK + 2) { - int lineBreak = 1; - for (lines -= MAX_SCROLLBACK; lines > 0; lines--) lineBreak = content.indexOf('\n', lineBreak + 1); - if (lineBreak <= log.getDocument().getLength() - 2 && log.getSelectionStart() == log.getSelectionEnd()) { - log.getDocument().remove(0, lineBreak); - } + } + + @Override + public void flush() throws IOException { + try { + HTMLDocument doc = (HTMLDocument) log.getDocument(); + ((HTMLEditorKit) log.getEditorKit()).insertHTML(doc, doc.getLength() - 2, new String(scache.toByteArray(), UTF_8), 0, 0, null); + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + hScroll(); } - } catch (Exception e) {} - for (Runnable post : spost) try { - post.run(); - } catch (Throwable e) {} - spost.clear(); - scache = new ByteArrayOutputStream(); - } + }); + } catch (Exception e) {} try { + int lines; + String content; + HTMLDocument doc = (HTMLDocument) log.getDocument(); + if (log.getSelectionStart() == log.getSelectionEnd() && (lines = countLines(content = doc.getText(2, doc.getLength() - 2))) > MAX_SCROLLBACK) { + int lfl = 0; + for (lines -= MAX_SCROLLBACK; lines > 0; --lines) lfl = content.indexOf('\n', lfl + 1); + if (log.getSelectionStart() == log.getSelectionEnd()) { + doc.remove(2, lfl); + } + } + } catch (Exception e) {} + for (Runnable post : spost) try { + post.run(); + } catch (Throwable e) {} + spost.clear(); + scache.reset(); } }, new HTMLogger.HTMConstructor() { @Override @@ -193,6 +200,7 @@ public final class ConsoleWindow implements SubLogFilter { @Override public void actionPerformed(ActionEvent event) { ConsoleWindow.this.clear(); + ConsoleWindow.this.hScroll(); } }); menu.add(item); @@ -201,8 +209,9 @@ public final class ConsoleWindow implements SubLogFilter { item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { - log.setText(RESET_VALUE); + ConsoleWindow.this.clear(); ConsoleWindow.this.loadContent(); + ConsoleWindow.this.hScroll(); } }); menu.add(item); @@ -315,8 +324,9 @@ public final class ConsoleWindow implements SubLogFilter { @Override public void actionPerformed(ActionEvent event) { stream.ansi(((AbstractButton) event.getSource()).getModel().isSelected()); - log.setText(RESET_VALUE); + ConsoleWindow.this.clear(); ConsoleWindow.this.loadContent(); + ConsoleWindow.this.hScroll(); } }); menu.add(item); @@ -325,7 +335,7 @@ public final class ConsoleWindow implements SubLogFilter { @Override public void actionPerformed(ActionEvent event) { HTMLDocument doc = (HTMLDocument) log.getDocument(); - fontSize = (int) (12 * scale); + fontSize = (int) (13 * scale); doc.getStyleSheet().addRule("body {font-size: " + fontSize + ";}\n"); ConsoleWindow.this.hScroll(); } @@ -337,7 +347,7 @@ public final class ConsoleWindow implements SubLogFilter { @Override public void actionPerformed(ActionEvent event) { HTMLDocument doc = (HTMLDocument) log.getDocument(); - fontSize += 2 * scale; + fontSize += scale; doc.getStyleSheet().addRule("body {font-size: " + fontSize + ";}\n"); ConsoleWindow.this.hScroll(); } @@ -349,7 +359,7 @@ public final class ConsoleWindow implements SubLogFilter { @Override public void actionPerformed(ActionEvent event) { HTMLDocument doc = (HTMLDocument) log.getDocument(); - fontSize -= 2 * scale; + fontSize -= scale; doc.getStyleSheet().addRule("body {font-size: " + fontSize + ";}\n"); ConsoleWindow.this.hScroll(); } @@ -367,11 +377,10 @@ public final class ConsoleWindow implements SubLogFilter { } }); window.setTitle(logger.getName() + " \u2014 SubServers 2"); - window.setSize(1024, 576); - window.setSize((int) (1024 * scale), (int) (576 * scale)); + window.setSize((int) (1280 * scale), (int) (720 * scale)); window.setLocation( - (int) ((screen.getWidth() - window.getWidth()) / 2), - (int) ((screen.getHeight() - window.getHeight()) / 2) + (int) Math.abs((screen.getWidth() - window.getWidth()) / 2), + (int) Math.abs((screen.getHeight() - window.getHeight()) / 2) ); window.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); window.addWindowListener(new WindowAdapter() { @@ -391,13 +400,13 @@ public final class ConsoleWindow implements SubLogFilter { log.setContentType("text/html"); log.setEditorKit(new HTMLEditorKit()); StyleSheet style = new StyleSheet(); - fontSize = (int) (12 * scale); + fontSize = (int) (13 * scale); String font; try { Font f = Font.createFont(Font.TRUETYPE_FONT, ConsoleWindow.class.getResourceAsStream("/net/ME1312/SubServers/Console/ConsoleFont.ttf")); GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(f); font = f.getFontName(); - input.setFont(f.deriveFont((float) (14 * scale))); + input.setFont(f); } catch (Exception e) { font = "Courier"; } @@ -447,11 +456,13 @@ public final class ConsoleWindow implements SubLogFilter { if (offset < 1) { return; } + string = string.replace("\n", "\\n"); super.insertString(fb, offset, string, attr); } @Override public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { + text = text.replace("\n", "\\n"); if (offset < 1) { length = Math.max(0, length - 1); offset = input.getDocument().getLength(); @@ -552,7 +563,6 @@ public final class ConsoleWindow implements SubLogFilter { } }); findP.setBorder(new ButtonBorder(40, 44, 45, (int) (4 * scale))); - findN.setFont(findN.getFont().deriveFont((float) (findN.getFont().getSize() * scale))); findP.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { @@ -567,6 +577,7 @@ public final class ConsoleWindow implements SubLogFilter { } }); findN.setBorder(new ButtonBorder(40, 44, 45, (int) (4 * scale))); + findN.setFont(findN.getFont().deriveFont((float) (findN.getFont().getSize() * scale))); findN.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { @@ -600,32 +611,116 @@ public final class ConsoleWindow implements SubLogFilter { vScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); } + thread = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "SubServers.Console::Log_Spooler(" + logger.getName() + ")"); + } + }); + + try { + clear(); + slines.add(sbytes); + stream.write("\u00A0".getBytes(UTF_8)); + } catch (IOException e) {} + logger.registerFilter(this); - log.setText(RESET_VALUE); loadContent(); - log(); + hScroll(); KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keys); } - private void hScroll() { - hScroll.setMaximum(vScroll.getHorizontalScrollBar().getMaximum()); - hScroll.setMinimum(vScroll.getHorizontalScrollBar().getMinimum()); - hScroll.setVisibleAmount(vScroll.getHorizontalScrollBar().getVisibleAmount()); - hScroll.setVisible(input.isVisible() && hScroll.getVisibleAmount() < hScroll.getMaximum()); + + @Override + public void start() {} + public void open() { + if (!open) { + window.setVisible(true); + this.open = true; + } + window.toFront(); } - public SubLogger getLogger() { - return logger; + private void loadContent() { + this.sbytes = (slines.size() > 0) ? slines.getFirst() : -Long.MAX_VALUE; + this.slines.clear(); + this.slines.add(sbytes); + + try (FileInputStream reader = new FileInputStream(file)) { + if (sbytes > -Long.MAX_VALUE) { + if (sbytes > 0) { + reader.skip(sbytes); + reader.skip(Long.MAX_VALUE); + } else { + reader.skip(sbytes + Long.MAX_VALUE); + } + } + + int i; + byte[] b = new byte[1024]; + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + while ((i = reader.read(b)) != -1) { + stream.write(b, 0, i); + } + + submit(stream.toString(UTF_8.name())); + } catch (Exception e) { + e.printStackTrace(); + } } - public void log(Date date, String message) { + private void submit(final String s) { + thread.submit(new Runnable() { + @Override + public void run() { + try { + int begin = 0; + int end, i, bytes; + do { + i = s.indexOf('\n', begin); + end = (i == -1) ? s.length() : i + 1; + + // Submit the text (without any newlines) + stream.write(s.substring(begin, (i == -1) ? s.length() : i).getBytes(UTF_8)); + + // Count the bytes (with 1 possible newline) + bytes = s.substring(begin, end).getBytes(UTF_8).length; + if (sbytes + bytes < sbytes) sbytes = Long.MAX_VALUE; + else sbytes += bytes; + + // Submit a newline if necessary + if (i != -1) { + stream.closeAttributes(); + stream.write("\u00A0".getBytes(UTF_8)); + stream.flush(); + stream.write("\n\u00A0".getBytes(UTF_8)); + slines.add(sbytes); + while (slines.size() > MAX_SCROLLBACK + 1) { + slines.removeFirst(); + } + } + + // Continue to the next line if available + begin = end; + } while (begin < s.length()); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + public void log(String message) { + message += '\n'; try { - messages.add(('\u00A0' + new SimpleDateFormat("hh:mm:ss").format(date) + ' ' + message + "\u00A0\n").getBytes("UTF-8")); + filewriter.write(message.getBytes(UTF_8)); + filewriter.flush(); + + submit(message); } catch (IOException e) { e.printStackTrace(); } } - public void log(String message) { - log(Calendar.getInstance().getTime(), message); + public void log(Date date, String message) { + log(new SimpleDateFormat("hh:mm:ss").format(date) + ' ' + message); } public void log(Date date, Level level, String message) { log(date, "[" + level.getLocalizedName() + "] " + message); @@ -636,64 +731,16 @@ public final class ConsoleWindow implements SubLogFilter { return !open; } - private void log() { - new Thread("SubServers.Console::Log_Spooler(" + logger.getName() + ")") { - @Override - public void run() { - while (running) { - while (running && messages.size() > 0) try { - byte[] msg = (byte[]) Util.getDespiteException(new ExceptionReturnRunnable() { - @Override - public Object run() throws Throwable { - return messages.get(0); - } - }, null); - if (msg != null) { - filewriter.write(msg); - stream.write(msg); - } - try { ConsoleWindow.this.messages.remove(0); } catch (Throwable e) {} - } catch (Throwable e) { - try { ConsoleWindow.this.messages.remove(0); } catch (Throwable ex) {} - e.printStackTrace(); - } - try { Thread.sleep(32); } catch (Throwable e) {} - } - } - }.start(); - } - public void clear() { - log.setText(RESET_VALUE); - hScroll(); - } - - public void open() { - if (!open) { - window.setVisible(true); - this.open = true; - } - window.toFront(); + log.setText("\n\u00A0\n\u00A0"); } public boolean isOpen() { return open; } - @Override - public void start() {} - private void loadContent() { - if (file != null) { - try (FileInputStream reader = new FileInputStream(file)) { - int b; - while ((b = reader.read()) != -1) { - stream.write(b); - } - } catch (Exception e) { - e.printStackTrace(); - } - hScroll(); - } + public SubLogger getLogger() { + return logger; } @Override @@ -715,7 +762,7 @@ public final class ConsoleWindow implements SubLogFilter { public void destroy() { close(); - running = false; + thread.shutdown(); logger.unregisterFilter(this); if (filewriter != null) try { filewriter.close(); @@ -724,6 +771,13 @@ public final class ConsoleWindow implements SubLogFilter { KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keys); } + private void hScroll() { + hScroll.setMaximum(vScroll.getHorizontalScrollBar().getMaximum()); + hScroll.setMinimum(vScroll.getHorizontalScrollBar().getMinimum()); + hScroll.setVisibleAmount(vScroll.getHorizontalScrollBar().getVisibleAmount()); + hScroll.setVisible(input.isVisible() && hScroll.getVisibleAmount() < hScroll.getMaximum()); + } + private void find(boolean direction) { if (!direction) findI -= findO + 1; String find = findT.getText().toLowerCase(); @@ -887,6 +941,7 @@ public final class ConsoleWindow implements SubLogFilter { private class AnsiUIOutputStream extends HTMLogger { public AnsiUIOutputStream(OutputStream raw, OutputStream wrapped) { super(raw, wrapped); + nbsp = true; } public void ansi(boolean value) { @@ -908,7 +963,8 @@ public final class ConsoleWindow implements SubLogFilter { if (ansi) spost.add(new Runnable() { @Override public void run() { - log.setText(RESET_VALUE); + ConsoleWindow.this.clear(); + ConsoleWindow.this.hScroll(); } }); } @@ -940,11 +996,11 @@ public final class ConsoleWindow implements SubLogFilter { } } private class SmartScroller implements AdjustmentListener { - public final static int HORIZONTAL = 0; - public final static int VERTICAL = 1; + private final static int HORIZONTAL = 0; + private final static int VERTICAL = 1; - public final static int START = 0; - public final static int END = 1; + private final static int START = 0; + private final static int END = 1; private int viewportPosition; diff --git a/SubServers.Console/src/net/ME1312/SubServers/Console/Library/HTMLogger.java b/SubServers.Console/src/net/ME1312/SubServers/Console/Library/HTMLogger.java index 0e0bc168..3d383411 100644 --- a/SubServers.Console/src/net/ME1312/SubServers/Console/Library/HTMLogger.java +++ b/SubServers.Console/src/net/ME1312/SubServers/Console/Library/HTMLogger.java @@ -1,82 +1,74 @@ package net.ME1312.SubServers.Console.Library; import net.ME1312.Galaxi.Library.Container.Container; -import net.ME1312.Galaxi.Library.Container.Value; import org.fusesource.jansi.AnsiOutputStream; +import java.awt.*; import java.io.IOException; import java.io.OutputStream; import java.util.LinkedList; +import java.util.Locale; + +import static java.nio.charset.StandardCharsets.UTF_8; -/** - * HTML Log Stream Class - */ public class HTMLogger extends AnsiOutputStream { private static final String[] ANSI_COLOR_MAP = new String[]{"000000", "cd0000", "25bc24", "d7d700", "0000c3", "be00be", "00a5dc", "cccccc"}; private static final String[] ANSI_BRIGHT_COLOR_MAP = new String[]{"808080", "ff0000", "31e722", "ffff00", "0000ff", "ff00ff", "00c8ff", "ffffff"}; - private static final byte[] BYTES_NBSP = " ".getBytes(); - private static final byte[] BYTES_QUOT = """.getBytes(); - private static final byte[] BYTES_AMP = "&".getBytes(); - private static final byte[] BYTES_LT = "<".getBytes(); - private static final byte[] BYTES_GT = ">".getBytes(); - private LinkedList closingAttributes = new LinkedList(); + private static final byte[] BYTES_NBSP = "\u00A0".getBytes(UTF_8); + private static final byte[] BYTES_AMP = "&".getBytes(UTF_8); + private static final byte[] BYTES_LT = "<".getBytes(UTF_8); + private LinkedList currentAttributes = new LinkedList(); + private LinkedList queue = new LinkedList(); private OutputStream raw; protected boolean ansi = true; + protected boolean nbsp = false; private boolean underline = false; private boolean strikethrough = false; - /** - * Parse data from an OutputStream - * - * @param raw OutputStream - * @return HTMLogger - */ public static HTMLogger wrap(OutputStream raw) { return wrap(raw, new HTMConstructor() { @Override - public HTMLogger construct(OutputStream raw1, OutputStream wrapped) { - return new HTMLogger(raw1, wrapped); + public HTMLogger construct(OutputStream raw, OutputStream wrapped) { + return new HTMLogger(raw, wrapped); } }); } - /** - * Parse data from an OutputStream - * - * @param raw OutputStream - * @param constructor Implementing Constructor - * @param Logger Type - * @return HTMLogger - */ public static T wrap(final OutputStream raw, HTMConstructor constructor) { - final Value html = new Container(null); + final Container html = new Container(null); html.value(constructor.construct(raw, new OutputStream() { private boolean nbsp = false; @Override public void write(int data) throws IOException { + HTMLogger htm = html.value(); + if (htm.queue.size() > 0) { + LinkedList queue = htm.queue; + htm.queue = new LinkedList<>(); + for (String attr : queue) { + htm.write('<' + attr + '>'); + htm.currentAttributes.addFirst(attr); + } + } + if (data == 32) { - if (nbsp) raw.write(BYTES_NBSP); - else raw.write(data); - nbsp = !nbsp; + if (htm.nbsp) { + if (nbsp) raw.write(BYTES_NBSP); + else raw.write(data); + nbsp = !nbsp; + } else raw.write(data); } else { nbsp = false; switch(data) { - case 34: - raw.write(BYTES_QUOT); - break; - case 38: + case '&': raw.write(BYTES_AMP); break; - case 60: + case '<': raw.write(BYTES_LT); break; - case 62: - raw.write(BYTES_GT); - break; - case 10: - html.value().closeAttributes(); + case '\n': + htm.closeAttributes(); default: raw.write(data); } @@ -85,7 +77,7 @@ public class HTMLogger extends AnsiOutputStream { })); return html.value(); } - protected HTMLogger(final OutputStream raw, OutputStream wrapped) { + public HTMLogger(final OutputStream raw, OutputStream wrapped) { super(wrapped); this.raw = raw; } @@ -94,46 +86,62 @@ public class HTMLogger extends AnsiOutputStream { } private void write(String s) throws IOException { - raw.write(s.getBytes()); + raw.write(s.getBytes(UTF_8)); } - private void writeAttribute(String s) throws IOException { - write("<" + s + ">"); - closingAttributes.add(0, s); + private void writeAttribute(String attr) throws IOException { + queue.add(attr); } - protected void closeAttribute(String s) throws IOException { + public void closeAttribute(String s) throws IOException { + + // Try to remove a tag that doesn't exist yet first + String[] queue = this.queue.toArray(new String[0]); + for (int i = queue.length; i > 0;) { + String attr = queue[--i]; + if (attr.toLowerCase().startsWith(s.toLowerCase())) { + this.queue.removeLastOccurrence(attr); + return; + } + } + + // Close a tag that we've already written LinkedList closedAttributes = new LinkedList(); - LinkedList closingAttributes = new LinkedList(); + LinkedList currentAttributes = new LinkedList(this.currentAttributes); LinkedList unclosedAttributes = new LinkedList(); - closingAttributes.addAll(closingAttributes); - for (String attr : closingAttributes) { + for (String attr : currentAttributes) { if (attr.toLowerCase().startsWith(s.toLowerCase())) { for (String a : unclosedAttributes) { - closedAttributes.add(0, a); - write(""); + closedAttributes.add(a); + this.currentAttributes.removeFirst(); + write("'); } - closingAttributes.removeFirstOccurrence(attr); unclosedAttributes.clear(); - write(""); + this.currentAttributes.removeFirst(); + write("'); + break; } else { unclosedAttributes.add(attr); } } + + // Queue unrelated tags to be re-opened for (String attr : closedAttributes) { - write("<" + attr + ">"); + this.queue.addFirst(attr); } } - protected void closeAttributes() throws IOException { - for (String attr : closingAttributes) { + public void closeAttributes() throws IOException { + queue.clear(); + + for (String attr : currentAttributes) { write(""); } underline = false; strikethrough = false; - closingAttributes.clear(); + currentAttributes.clear(); } @Override @@ -141,13 +149,13 @@ public class HTMLogger extends AnsiOutputStream { super.processDeleteLine(amount); } - private String parseTextDecoration() { + private void renderTextDecoration() throws IOException { String dec = ""; if (underline) dec += " underline"; if (strikethrough) dec += " line-through"; - if (dec.length() <= 0) dec += " none"; - return dec.substring(1); + closeAttribute("span style=\"text-decoration:"); + if (dec.length() != 0) writeAttribute("span style=\"text-decoration:" + dec.substring(1) + "\""); } @Override @@ -162,14 +170,12 @@ public class HTMLogger extends AnsiOutputStream { writeAttribute("i"); break; case 4: - closeAttribute("span class=\"ansi-decoration"); underline = true; - writeAttribute("span class=\"ansi-decoration\" style=\"text-decoration: " + parseTextDecoration() + ";\""); + renderTextDecoration(); break; case 9: - closeAttribute("span class=\"ansi-decoration"); strikethrough = true; - writeAttribute("span class=\"ansi-decoration\" style=\"text-decoration: " + parseTextDecoration() + ";\""); + renderTextDecoration(); break; case 22: closeAttribute("b"); @@ -178,14 +184,23 @@ public class HTMLogger extends AnsiOutputStream { closeAttribute("i"); break; case 24: - closeAttribute("span class=\"ansi-decoration"); underline = false; - writeAttribute("span class=\"ansi-decoration\" style=\"text-decoration: " + parseTextDecoration() + ";\""); + renderTextDecoration(); break; case 29: - closeAttribute("span class=\"ansi-decoration"); strikethrough = false; - writeAttribute("span class=\"ansi-decoration\" style=\"text-decoration: " + parseTextDecoration() + ";\""); + renderTextDecoration(); + break; + case 73: + closeAttribute("su"); + writeAttribute("sup"); + break; + case 74: + closeAttribute("su"); + writeAttribute("sub"); + break; + case 75: + closeAttribute("su"); break; } } @@ -193,24 +208,30 @@ public class HTMLogger extends AnsiOutputStream { @Override protected void processUnknownOperatingSystemCommand(int label, String arg) { try { - if (ansi) switch (label) { - case 99900: // Galaxi Console Exclusives 99900-99999 - closeAttribute("a"); - writeAttribute("a href=\"" + arg + "\" target=\"_blank\""); - break; - case 99901: - closeAttribute("a"); - break; + if (ansi && label == 8) { + closeAttribute("a"); + String[] args = arg.split(";", 3); + if (args.length > 1 && args[1].length() > 0 && allowHyperlink(args[1])) { + writeAttribute("a href=\"" + args[1].replace("&", "&").replace("<", "<").replace("\"", """) + "\" target=\"_blank\""); + } } } catch (Exception e) {} } + protected boolean allowHyperlink(String link) { + if (link.toLowerCase(Locale.ENGLISH).startsWith("mailto:execute@galaxi.engine")) { + return false; + } else { + return true; + } + } + @Override protected void processAttributeRest() throws IOException { closeAttributes(); } - private String parse8BitColor(int color) throws IOException { + private String parse256(int color) throws IOException { if (color < 8) { return ANSI_COLOR_MAP[color]; } else if (color < 16) { @@ -230,7 +251,7 @@ public class HTMLogger extends AnsiOutputStream { @Override protected void processDefaultTextColor() throws IOException { - closeAttribute("span class=\"ansi-foreground"); + closeAttribute("span style=\"color:"); } @Override @@ -242,7 +263,8 @@ public class HTMLogger extends AnsiOutputStream { protected void processSetForegroundColor(int color, boolean bright) throws IOException { if (ansi) { processDefaultTextColor(); - writeAttribute("span class=\"ansi-foreground\" style=\"color: #" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + ";\""); + writeAttribute("span style=\"color:#" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + "\""); + renderTextDecoration(); } } @@ -250,7 +272,8 @@ public class HTMLogger extends AnsiOutputStream { protected void processSetForegroundColorExt(int index) throws IOException { if (ansi) { processDefaultTextColor(); - writeAttribute("span class=\"ansi-foreground\" style=\"color: #" + parse8BitColor(index) + ";\""); + writeAttribute("span style=\"color:#" + parse256(index) + "\""); + renderTextDecoration(); } } @@ -258,13 +281,14 @@ public class HTMLogger extends AnsiOutputStream { protected void processSetForegroundColorExt(int r, int g, int b) throws IOException { if (ansi) { processDefaultTextColor(); - writeAttribute("span class=\"ansi-foreground\" style=\"color: #" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + ";\""); + writeAttribute("span style=\"color:#" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + "\""); + renderTextDecoration(); } } @Override protected void processDefaultBackgroundColor() throws IOException { - closeAttribute("span class=\"ansi-background"); + closeAttribute("span style=\"background-color:"); } @Override @@ -276,7 +300,7 @@ public class HTMLogger extends AnsiOutputStream { protected void processSetBackgroundColor(int color, boolean bright) throws IOException { if (ansi) { processDefaultBackgroundColor(); - writeAttribute("span class=\"ansi-background\" style=\"background-color: #" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + ";\""); + writeAttribute("span style=\"background-color:#" + ((!bright)?ANSI_COLOR_MAP:ANSI_BRIGHT_COLOR_MAP)[color] + "\""); } } @@ -284,7 +308,7 @@ public class HTMLogger extends AnsiOutputStream { protected void processSetBackgroundColorExt(int index) throws IOException { if (ansi) { processDefaultBackgroundColor(); - writeAttribute("span class=\"ansi-background\" style=\"background-color: #" + parse8BitColor(index) + ";\""); + writeAttribute("span style=\"background-color:#" + parse256(index) + "\""); } } @@ -292,10 +316,16 @@ public class HTMLogger extends AnsiOutputStream { protected void processSetBackgroundColorExt(int r, int g, int b) throws IOException { if (ansi) { processDefaultBackgroundColor(); - writeAttribute("span class=\"ansi-background\" style=\"background-color: #" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + ";\""); + writeAttribute("span style=\"background-color:#" + ((r >= 16)?"":"0") + Integer.toString(r, 16) + ((g >= 16)?"":"0") + Integer.toString(g, 16) + ((b >= 16)?"":"0") + Integer.toString(b, 16) + "\""); } } + @Override + public void flush() throws IOException { + super.flush(); + raw.flush(); + } + @Override public void close() throws IOException { closeAttributes();