/** * */ package apu.scratch.converter; import static apu.antlr.clike.ScratchCLikeLexer.ARRAY_TAG; import static apu.antlr.clike.ScratchCLikeLexer.ATOMIC_METHOD_TAG; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_AND; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_EQUALS; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_GT; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_GTE; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_LT; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_LTE; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_NEQUALS; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_NOT; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_OR; import static apu.antlr.clike.ScratchCLikeLexer.BOOL_TRUE; import static apu.antlr.clike.ScratchCLikeLexer.BlockComment; import static apu.antlr.clike.ScratchCLikeLexer.ELSE_TAG; import static apu.antlr.clike.ScratchCLikeLexer.FOR_TAG; import static apu.antlr.clike.ScratchCLikeLexer.HEX_CODE; import static apu.antlr.clike.ScratchCLikeLexer.IDENTIFIER; import static apu.antlr.clike.ScratchCLikeLexer.IF_TAG; import static apu.antlr.clike.ScratchCLikeLexer.LineComment; import static apu.antlr.clike.ScratchCLikeLexer.MATH_DIVIDE; import static apu.antlr.clike.ScratchCLikeLexer.MATH_MINUS; import static apu.antlr.clike.ScratchCLikeLexer.MATH_MODULO; import static apu.antlr.clike.ScratchCLikeLexer.MATH_PLUS; import static apu.antlr.clike.ScratchCLikeLexer.MATH_TIMES; import static apu.antlr.clike.ScratchCLikeLexer.METHOD_DEF_TAG; import static apu.antlr.clike.ScratchCLikeLexer.OPENP; import static apu.antlr.clike.ScratchCLikeLexer.REPEAT_TAG; import static apu.antlr.clike.ScratchCLikeLexer.RETURN_TAG; import static apu.antlr.clike.ScratchCLikeLexer.SINGLE_FRAME_TAG; import static apu.antlr.clike.ScratchCLikeLexer.STRINGLITERAL; import static apu.antlr.clike.ScratchCLikeLexer.WHEN_CLICKED; import static apu.antlr.clike.ScratchCLikeLexer.WHEN_CLONED; import static apu.antlr.clike.ScratchCLikeLexer.WHEN_FLAG; import static apu.antlr.clike.ScratchCLikeLexer.WHEN_KEY; import static apu.antlr.clike.ScratchCLikeLexer.WHEN_RECEIVE; import static apu.antlr.clike.ScratchCLikeLexer.WHEN_SENSOR; import static apu.antlr.clike.ScratchCLikeLexer.WHEN_TAG; import static apu.antlr.clike.ScratchCLikeLexer.WHILE_TAG; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; 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; import java.io.File; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextPane; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.DocumentFilter; import javax.swing.text.DocumentFilter.FilterBypass; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyleContext; import javax.swing.text.StyledDocument; import javax.swing.text.TabSet; import javax.swing.text.TabStop; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Token; import org.json.JSONArray; import apu.antlr.clike.ScratchCLikeLexer; import apu.scratch.converter.ScratchConverter.CompileError; import apu.scratch.converter.ScratchConverter.CompileResult; /** * @author MegaApuTurkUltra * */ public class CodePanel extends JPanel { private static final long serialVersionUID = -7689135789632381673L; public static class Tab { boolean isSaved = false; JTextPane codeArea; JScrollPane scrollPane; StyleContext context; DefaultStyledDocument doc; Style defaultStyle, linesDefaultStyle, linesRedStyle; Map<Integer, Style> styles = new HashMap<>(); JLabel title; JTextPane lines; Thread compileThread; String stackTrace = null; Runnable compileTimer; ScratchCLikeLexer lexer; File saveFile = null; List<CompileError> currentErrors = new ArrayList<CompileError>(); JSONArray currentJson; public Tab() { title = new JLabel("New File"); lexer = new ScratchCLikeLexer(null); lexer.removeErrorListeners(); scrollPane = new JScrollPane(); context = new StyleContext(); doc = new DefaultStyledDocument(context); doc.setDocumentFilter(new DocumentFilter() { public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { fb.insertString(offset, string, attr); countLines(); highlight(fb); } public void remove(FilterBypass fb, int offs, int len) throws BadLocationException { fb.remove(offs, len); countLines(); highlight(fb); } public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { fb.replace(offset, length, text, attrs); countLines(); highlight(fb); } }); defaultStyle = context.getStyle(StyleContext.DEFAULT_STYLE); Style style = context.addStyle("identifier", defaultStyle); StyleConstants.setForeground(style, Color.BLUE); StyleConstants.setItalic(style, true); styles.put(IDENTIFIER, style); style = context.addStyle("methodIdentifier", style); StyleConstants.setForeground(style, new Color(0.7f, 0f, 1f)); styles.put(999, style); styles.put(WHEN_CLICKED, style); styles.put(WHEN_RECEIVE, style); styles.put(WHEN_KEY, style); styles.put(WHEN_CLONED, style); styles.put(WHEN_SENSOR, style); styles.put(WHEN_FLAG, style); styles.put(RETURN_TAG, style); style = context.addStyle("keyword", defaultStyle); StyleConstants.setBold(style, true); StyleConstants.setForeground(style, new Color(0.5f, 0f, 0.5f)); styles.put(BOOL_TRUE, style); styles.put(IF_TAG, style); styles.put(ELSE_TAG, style); styles.put(FOR_TAG, style); styles.put(WHILE_TAG, style); styles.put(METHOD_DEF_TAG, style); styles.put(REPEAT_TAG, style); styles.put(ARRAY_TAG, style); style = context.addStyle("comment", defaultStyle); StyleConstants.setForeground(style, new Color(0.3f, 0.6f, 0.4f)); StyleConstants.setItalic(style, true); styles.put(LineComment, style); styles.put(BlockComment, style); style = context.addStyle("string", defaultStyle); StyleConstants.setForeground(style, new Color(0.3f, 0.5f, 1f)); styles.put(STRINGLITERAL, style); style = context.addStyle("annotation", defaultStyle); StyleConstants.setItalic(style, true); StyleConstants.setForeground(style, new Color(0.7f, 0.3f, 0.5f)); styles.put(SINGLE_FRAME_TAG, style); styles.put(ATOMIC_METHOD_TAG, style); styles.put(WHEN_TAG, style); style = context.addStyle("operators", defaultStyle); StyleConstants.setBold(style, true); StyleConstants.setForeground(style, new Color(0f, 0.5f, 0f)); styles.put(BOOL_AND, style); styles.put(BOOL_OR, style); styles.put(BOOL_NOT, style); styles.put(BOOL_EQUALS, style); styles.put(BOOL_NEQUALS, style); styles.put(BOOL_GT, style); styles.put(BOOL_LT, style); styles.put(BOOL_GTE, style); styles.put(BOOL_LTE, style); styles.put(MATH_PLUS, style); styles.put(MATH_MINUS, style); styles.put(MATH_TIMES, style); styles.put(MATH_DIVIDE, style); styles.put(MATH_MODULO, style); style = context.addStyle("syntaxerror", defaultStyle); StyleConstants.setForeground(style, Color.RED); StyleConstants.setUnderline(style, true); styles.put(1000, style); style = context.addStyle("hex", defaultStyle); StyleConstants.setItalic(style, true); StyleConstants.setForeground(style, new Color(0f, 0.7f, 0.5f)); styles.put(HEX_CODE, style); compileTimer = new Runnable() { @Override public void run() { try { Thread.sleep(1000); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { checkCode(false); } }); } catch (InterruptedException e) { // do nothing; we were interrupted } } }; codeArea = new JTextPane(doc); String tab = ""; for (int i = 0; i < 4; i++) { tab += " "; } Font font = new Font(StyleConstants.getFontFamily(defaultStyle), Font.PLAIN, StyleConstants.getFontSize(defaultStyle)); float f = (float) codeArea.getFontMetrics(font).stringWidth(tab); TabStop[] tabs = new TabStop[500]; // this sucks for (int i = 0; i < tabs.length; i++) { tabs[i] = new TabStop(f * (i + 1), TabStop.ALIGN_LEFT, TabStop.LEAD_NONE); } TabSet tabset = new TabSet(tabs); AttributeSet attrs = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.TabSet, tabset); codeArea.setParagraphAttributes(attrs, false); codeArea.addKeyListener(new KeyAdapter() { public void keyTyped(KeyEvent ke) { if (compileThread != null && compileThread.isAlive()) compileThread.interrupt(); compileThread = new Thread(compileTimer); compileThread.setName("Compiler timer"); compileThread.setDaemon(true); compileThread.start(); } }); codeArea.setBackground(Color.WHITE); JPanel codeContainer = new JPanel(new BorderLayout()); scrollPane.setViewportView(codeContainer); codeContainer.add(codeArea, BorderLayout.CENTER); new LinePainter(codeArea, new Color(0.9f, 0.9f, 1f)); lines = new JTextPane(); lines.addMouseMotionListener(new MouseMotionAdapter() { public void mouseMoved(MouseEvent me) { Font f = new Font(StyleConstants .getFontFamily(linesDefaultStyle), Font.PLAIN, StyleConstants.getFontSize(linesDefaultStyle)); int lineHeight = lines.getFontMetrics(f).getHeight(); int line = me.getY() / lineHeight; line++; StringBuilder tooltip = new StringBuilder(); if (stackTrace != null) { lines.setToolTipText("<html>Serious compile error:<br/>" + stackTrace.replaceAll("\n", "<br/>") + "</html>"); return; } tooltip.append("<html>"); int i = 0; for (CompileError error : currentErrors) { if (line == error.line) { tooltip.append(error.toString()); tooltip.append("<br/>"); i++; } } tooltip.append("</html>"); if (i == 0) { lines.setToolTipText(null); } else { lines.setToolTipText(tooltip.toString()); } } }); lines.setBackground(Color.WHITE); lines.setText("1"); lines.setEditable(false); linesDefaultStyle = lines.getStyledDocument().getStyle( StyleContext.DEFAULT_STYLE); StyleConstants.setFontFamily(linesDefaultStyle, "monospaced"); linesRedStyle = lines.getStyledDocument().addStyle("red", linesDefaultStyle); StyleConstants.setForeground(linesRedStyle, Color.RED); StyleConstants.setBold(linesRedStyle, true); Border oldBorder = lines.getBorder(); Border blackBorder = BorderFactory.createMatteBorder(0, 0, 0, 1, Color.BLACK); Border newBorder = BorderFactory.createCompoundBorder(blackBorder, oldBorder); lines.setBorder(newBorder); scrollPane.setRowHeaderView(lines); } public void checkCode(boolean save) { isSaved = false; if (IdeFrame.instance == null) return; stackTrace = null; try { String code = doc.getText(0, doc.getEndPosition().getOffset()); try { CompileResult result = ScratchConverter.compile(code); currentJson = result.scripts; currentErrors.clear(); currentErrors.addAll(result.errors); highlightErrorLines(); if (currentErrors.size() == 0) IdeFrame.instance.callback.codeSaved( result.scripts.toString(), save); if (save) { if (saveFile == null) { File f = SavePanel.showDialog(false, true, true); if (f != null) { saveFile = f; IdeFrame.instance.lblFile.setText("File: " + f.getName()); title.setText(f.getName()); } } if (saveFile != null) { Sprite2IO.writeToFile(saveFile, code); isSaved = true; } } } catch (Exception e) { StyledDocument doc = lines.getStyledDocument(); doc.setCharacterAttributes(0, doc.getEndPosition() .getOffset(), linesRedStyle, true); StringWriter w = new StringWriter(); e.printStackTrace(new PrintWriter(w)); stackTrace = w.toString(); e.printStackTrace(); if (save) { JOptionPane.showMessageDialog(IdeFrame.instance, "Compile failed!\n" + e.getMessage(), "", JOptionPane.ERROR_MESSAGE); } } } catch (BadLocationException e) { e.printStackTrace(); } } private void highlightErrorLines() { StyledDocument doc = lines.getStyledDocument(); doc.setCharacterAttributes(0, doc.getEndPosition().getOffset(), linesDefaultStyle, true); highlight(null); for (CompileError e : currentErrors) { System.out.println(e); int len = Integer.toString(e.line).length(); int pos = lines.getText().indexOf(e.line + "\n"); if (pos == -1) pos = lines.getText().indexOf("\n" + e.line) + 1; if (pos == -1) pos = 0; doc.setCharacterAttributes(pos, len, linesRedStyle, true); int start = e.position, length = e.endPosition + 1 - start; this.doc.setCharacterAttributes(start, length, styles.get(1000), false); } } private void countLines() { String text; try { text = doc.getText(doc.getStartPosition().getOffset(), doc .getEndPosition().getOffset()); } catch (BadLocationException e) { e.printStackTrace(); return; } int index = 0; int c = 1; StringBuilder count = new StringBuilder(); while (index < text.length() - 1) { index++; if (text.charAt(index) == '\n') { count.append(c); count.append("\n"); c++; } } String l = count.toString(); lines.setText(l.length() > 0 ? l : "1"); highlightErrorLines(); } private void highlight(FilterBypass fb) { try { String text; try { text = doc.getText(doc.getStartPosition().getOffset(), doc .getEndPosition().getOffset()); } catch (BadLocationException e) { e.printStackTrace(); return; } lexer.reset(); lexer.setInputStream(new ANTLRInputStream( new StringReader(text))); CommonTokenStream tokens = new CommonTokenStream(lexer); tokens.fill(); doc.setCharacterAttributes(0, text.length(), defaultStyle, true); List<Token> tokenList = tokens.getTokens(); for (int i = 0; i < tokenList.size(); i++) { Token t = tokenList.get(i); int type = t.getType(); if (styles.containsKey(type)) { if (type == IDENTIFIER && i + 1 < tokenList.size()) { if (tokenList.get(i + 1).getType() == OPENP) { type = 999; } } int offs = t.getStartIndex(); int len = t.getStopIndex() - offs + 1; doc.setCharacterAttributes(offs, len, styles.get(type), true); } } } catch (Exception e1) { e1.printStackTrace(); } } } public List<Tab> tabs = new LinkedList<>(); public JTabbedPane tabbedPane; public CodePanel() { setLayout(new BorderLayout(0, 0)); tabbedPane = new JTabbedPane(JTabbedPane.TOP); tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); tabbedPane.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (IdeFrame.instance == null) return; Tab tab = getCurrentlySelectedTab(); if (tab != null) { tab.checkCode(false); if (tab.saveFile != null) { IdeFrame.instance.lblFile.setText("File: " + tab.saveFile.getName()); } else { IdeFrame.instance.lblFile.setText("File: "); } } } }); add(tabbedPane, BorderLayout.CENTER); addNewTab(); } public Tab getCurrentlySelectedTab() { Component comp = tabbedPane.getSelectedComponent(); for (Tab t : tabs) { if (t.scrollPane == comp) return t; } return null; } public Tab addNewTab() { final Tab tab = new Tab(); tabs.add(tab); String title = "Tab" + Math.random(); tabbedPane.addTab(title, tab.scrollPane); JPanel tabComp = new JPanel(new GridBagLayout()); tabComp.setOpaque(false); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; tabComp.add(tab.title, gbc); tab.title.setFont(tab.title.getFont().deriveFont(12f)); JButton close = new JButton("x"); close.setFont(close.getFont().deriveFont(15f).deriveFont(Font.BOLD)); close.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (!tab.isSaved) { if (JOptionPane.showConfirmDialog(IdeFrame.instance, "Your code is not saved! Are you sure?", "Tab close", JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) return; } tabs.remove(tab); tabbedPane.remove(tab.scrollPane); } }); close.setBorder(new EmptyBorder(0, 10, 0, 0)); close.setFocusPainted(false); close.setContentAreaFilled(false); close.addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent me){ me.getComponent().setForeground(Color.red); } @Override public void mouseExited(MouseEvent me){ me.getComponent().setForeground(Color.black); } }); gbc.gridx++; gbc.weightx = 0; tabComp.add(close, gbc); tabbedPane.setTabComponentAt(tabbedPane.indexOfTab(title), tabComp); return tab; } }