package tk.amberide.ide.gui.editor.text; import tk.amberide.Amber; import tk.amberide.ide.data.io.FileIO; import tk.amberide.ide.gui.FileViewerPanel; import tk.amberide.ide.gui.misc.ErrorHandler; import tk.amberide.ide.gui.misc.RubyConsole; import tk.amberide.ide.swing.UIUtil; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JScrollPane; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import static org.fife.ui.rsyntaxtextarea.SyntaxConstants.*; import org.fife.ui.rtextarea.Gutter; import org.fife.ui.rtextarea.RTextScrollPane; import org.jruby.Ruby; import tk.amberide.ide.data.io.XML; import tk.amberide.ide.swing.MenuBuilder; /** * @author Tudor */ public class ScriptEditorPanel extends FileViewerPanel { /** * A lookup for extension <-> syntax highlighting style. Queries should be * lowercase, and without the '.' prefix. For example, fetching the Ruby * syntax highlighter would require a query for "rb". */ public static final HashMap<String, String> SYNTAX_EXTENSIONS = new HashMap<String, String>() { { put("rb", SYNTAX_STYLE_RUBY); put("as", SYNTAX_STYLE_ACTIONSCRIPT); put("c", SYNTAX_STYLE_C); put("cpp", SYNTAX_STYLE_CPLUSPLUS); put("cs", SYNTAX_STYLE_CSHARP); put("dtd", SYNTAX_STYLE_DTD); put("css", SYNTAX_STYLE_CSS); put("java", SYNTAX_STYLE_JAVA); put("scala", SYNTAX_STYLE_JAVA); put("js", SYNTAX_STYLE_JAVASCRIPT); put("lua", SYNTAX_STYLE_SCALA); put("py", SYNTAX_STYLE_PYTHON); put("jsp", SYNTAX_STYLE_JSP); put("bat", SYNTAX_STYLE_WINDOWS_BATCH); put("sh", SYNTAX_STYLE_UNIX_SHELL); put("xml", SYNTAX_STYLE_XML); put("html", SYNTAX_STYLE_HTML); put("htm", SYNTAX_STYLE_HTML); } }; protected boolean modified = false; protected static Pattern FORMATTABLE = Pattern.compile("(?:html|htm|xml)"); /** * Creates new form ScriptEditorPanel * * @param file the file to load into the form */ public ScriptEditorPanel(final File file) { super(file); initComponents(); editor.setCodeFoldingEnabled(true); editor.setAntiAliasingEnabled(true); editor.addCaretListener(new CaretListener() { public void caretUpdate(CaretEvent e) { // Lines start at 0, so +1 cursorLabel.setText(editor.getCaretLineNumber() + 1 + " | " + editor.getCaretOffsetFromLineStart()); } }); scrollPane.setLineNumbersEnabled(true); UIUtil.mapInput(this, JComponent.WHEN_IN_FOCUSED_WINDOW, KeyEvent.VK_S, InputEvent.CTRL_MASK, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { save(); } }); if (formatSupported()) { UIUtil.mapInput(this, JComponent.WHEN_IN_FOCUSED_WINDOW, KeyEvent.VK_F, InputEvent.ALT_MASK | KeyEvent.SHIFT_MASK, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { doFormat(); } }); } String ext = FileIO.getFileExtension(file); toolbar.setVisible(ext.equals("rb")); editor.setSyntaxEditingStyle(SYNTAX_EXTENSIONS.containsKey(ext) ? SYNTAX_EXTENSIONS.get(ext) : SyntaxConstants.SYNTAX_STYLE_NONE); editor.setTabSize(3); editor.setCaretPosition(0); editor.setMarkOccurrences(true); editor.setClearWhitespaceLinesEnabled(false); Gutter gutter = scrollPane.getGutter(); gutter.setBookmarkingEnabled(true); gutter.setBookmarkIcon(new ImageIcon(ClassLoader.getSystemResource("icon/TextEditor.Bookmark.png"))); try { editor.setText(FileIO.read(file)); } catch (Exception e) { ErrorHandler.alert(e); } editor.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent documentEvent) { modified = true; } @Override public void removeUpdate(DocumentEvent documentEvent) { modified = true; } @Override public void changedUpdate(DocumentEvent documentEvent) { modified = true; } }); } private void ensureOutputShown() { if (jSplitPane1.getDividerLocation() >= jSplitPane1.getMaximumDividerLocation() - 20) { jSplitPane1.setDividerLocation(0.8D); jSplitPane1.revalidate(); } } private boolean formatSupported() { return FORMATTABLE.matcher(FileIO.getFileExtension(file)).matches(); } private void doFormat() { String text = editor.getText(); editor.beginAtomicEdit(); text = XML.formatXML(text); editor.setText(text); editor.endAtomicEdit(); } @Override public JMenu[] getContextMenus() { if (formatSupported()) { return new JMenu[]{new MenuBuilder("Tools").addButton("Format", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { doFormat(); } }).create()}; } else { return new JMenu[0]; } } @Override public boolean modified() { return modified; } @Override public void save() { PrintStream out = null; try { out = new PrintStream(new FileOutputStream(file)); out.println(editor.getText()); modified = false; } catch (Exception ex) { ErrorHandler.alert(ex); } finally { if (out != null) { out.close(); } } } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { toolbar = new javax.swing.JToolBar(); runButton = new javax.swing.JButton(); irbButton = new javax.swing.JButton(); jPanel1 = new javax.swing.JPanel(); cursorLabel = new javax.swing.JLabel(); jSplitPane1 = new tk.amberide.ide.swing.misc.ThinSplitPane(); scrollPane = new org.fife.ui.rtextarea.RTextScrollPane(); editor = new org.fife.ui.rsyntaxtextarea.RSyntaxTextArea(); closeableTabbedPane = new tk.amberide.ide.swing.tabs.CloseableTabbedPane(); toolbar.setBorder(null); toolbar.setFloatable(false); toolbar.setRollover(true); toolbar.setAlignmentY(0.5F); java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("amber/Bundle"); // NOI18N runButton.setText(bundle.getString("ScriptEditorPanel.runButton.text")); // NOI18N runButton.setFocusable(false); runButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); runButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); runButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { runButtonActionPerformed(evt); } }); toolbar.add(runButton); irbButton.setText(bundle.getString("ScriptEditorPanel.irbButton.text")); // NOI18N irbButton.setFocusable(false); irbButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); irbButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); irbButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { irbButtonActionPerformed(evt); } }); toolbar.add(irbButton); jPanel1.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); jPanel1.setLayout(new java.awt.BorderLayout()); cursorLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); jPanel1.add(cursorLabel, java.awt.BorderLayout.CENTER); jSplitPane1.setBorder(null); jSplitPane1.setDividerSize(0); jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); jSplitPane1.setResizeWeight(1.0); jSplitPane1.setMinimumSize(new java.awt.Dimension(0, 0)); jSplitPane1.setPreferredSize(new java.awt.Dimension(0, 0)); scrollPane.setBorder(javax.swing.BorderFactory.createTitledBorder("")); editor.setBorder(null); editor.setColumns(5); editor.setRows(5); editor.setAlignmentX(0.1F); editor.setAlignmentY(0.1F); editor.setAnimateBracketMatching(false); editor.setAntiAliasingEnabled(false); editor.setCodeFoldingEnabled(true); scrollPane.setViewportView(editor); jSplitPane1.setLeftComponent(scrollPane); closeableTabbedPane.setMinimumSize(new java.awt.Dimension(0, 0)); closeableTabbedPane.setPreferredSize(new java.awt.Dimension(0, 0)); jSplitPane1.setRightComponent(closeableTabbedPane); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(377, 377, 377) .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 95, Short.MAX_VALUE)) .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(toolbar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(toolbar, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 365, Short.MAX_VALUE) .addGap(0, 0, 0) .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) ); }// </editor-fold>//GEN-END:initComponents private void addDirectoryTree(Ruby runtime, File root) { runtime.getLoadService().addPaths(root.getAbsolutePath()); for (File node : root.listFiles()) { if (node.isDirectory()) { addDirectoryTree(runtime, node); } } } private void runButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_runButtonActionPerformed ensureOutputShown(); final RubyConsole con = new RubyConsole(); con.setMargin(new Insets(8, 8, 8, 8)); RTextScrollPane pane = new RTextScrollPane(); pane.setBorder(BorderFactory.createEmptyBorder()); pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); pane.setViewportView(con); addDirectoryTree(con.getRuntime(), Amber.getWorkspace().getRootDirectory()); closeableTabbedPane.add(pane, "Run"); closeableTabbedPane.setSelectedComponent(pane); new Thread() { @Override public void run() { con.eval(editor.getText()); con.getRuntime().getOutputStream().println("\n\nExited."); con.setEditable(false); } }.start(); }//GEN-LAST:event_runButtonActionPerformed private void irbButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_irbButtonActionPerformed ensureOutputShown(); final RubyConsole con = new RubyConsole(); con.setMargin(new Insets(8, 8, 8, 8)); RTextScrollPane pane = new RTextScrollPane(); pane.setBorder(BorderFactory.createEmptyBorder()); pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); pane.setViewportView(con); addDirectoryTree(con.getRuntime(), Amber.getWorkspace().getRootDirectory()); closeableTabbedPane.add(pane, "IRB"); closeableTabbedPane.setSelectedComponent(pane); con.getRuntime().getOutputStream().println("Welcome to the IRB Console\n\n"); new Thread() { @Override public void run() { con.eval("require 'irb'; require 'irb/completion'; IRB.start"); } }.start(); }//GEN-LAST:event_irbButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private tk.amberide.ide.swing.tabs.CloseableTabbedPane closeableTabbedPane; private javax.swing.JLabel cursorLabel; private org.fife.ui.rsyntaxtextarea.RSyntaxTextArea editor; private javax.swing.JButton irbButton; private javax.swing.JPanel jPanel1; private tk.amberide.ide.swing.misc.ThinSplitPane jSplitPane1; private javax.swing.JButton runButton; private org.fife.ui.rtextarea.RTextScrollPane scrollPane; private javax.swing.JToolBar toolbar; // End of variables declaration//GEN-END:variables }