From 1379505e1355cdbae4ecf97fa1f08e4c4a99bfec Mon Sep 17 00:00:00 2001 From: "ricardo.jl.rufino" Date: Wed, 22 Apr 2015 14:13:37 -0300 Subject: [PATCH] new editor based on RSyntaxTextArea --- app/src/ArduinoIDE.java | 36 + .../cc/arduino/packages/formatter/AStyle.java | 30 +- .../app/CaretAwareUndoableEdit.java | 6 +- app/src/processing/app/Editor.java | 618 +++++++--------- app/src/processing/app/EditorLineStatus.java | 25 +- app/src/processing/app/EditorListener.java | 667 ++---------------- .../app/LastUndoableEditAwareUndoManager.java | 29 +- app/src/processing/app/Sketch.java | 6 +- .../processing/app/SketchCodeDocument.java | 40 +- app/src/processing/app/Theme.java | 43 +- .../processing/app/syntax/PdeKeywords.java | 107 ++- .../processing/app/syntax/SketchTextArea.java | 243 +++++++ .../app/syntax/SketchTokenMaker.java | 121 ++++ .../processing/app/tools/DiscourseFormat.java | 129 +--- 14 files changed, 939 insertions(+), 1161 deletions(-) create mode 100644 app/src/ArduinoIDE.java create mode 100644 app/src/processing/app/syntax/SketchTextArea.java create mode 100644 app/src/processing/app/syntax/SketchTokenMaker.java diff --git a/app/src/ArduinoIDE.java b/app/src/ArduinoIDE.java new file mode 100644 index 000000000..2afd436ae --- /dev/null +++ b/app/src/ArduinoIDE.java @@ -0,0 +1,36 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2004-10 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +import processing.app.Base; + +/** + * Arduino IDE Launcher class + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 23/01/2015 + */ +public class ArduinoIDE { + + public static void main(String[] args) throws Exception { + Base.main(args); + } + +} diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index 1747c622f..ae99886e8 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -1,3 +1,5 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + /* * This file is part of Arduino. * @@ -29,16 +31,18 @@ package cc.arduino.packages.formatter; -import processing.app.Base; -import processing.app.Editor; -import processing.app.helpers.FileUtils; -import processing.app.syntax.JEditTextArea; -import processing.app.tools.Tool; +import static processing.app.I18n._; import java.io.File; import java.io.IOException; -import static processing.app.I18n._; +import javax.swing.text.BadLocationException; + +import processing.app.Base; +import processing.app.Editor; +import processing.app.helpers.FileUtils; +import processing.app.syntax.SketchTextArea; +import processing.app.tools.Tool; public class AStyle implements Tool { @@ -84,13 +88,17 @@ public class AStyle implements Tool { return; } - JEditTextArea textArea = editor.getTextArea(); - int line = textArea.getLineOfOffset(textArea.getCaretPosition()); - int lineOffset = textArea.getCaretPosition() - textArea.getLineStartOffset(line); - + SketchTextArea textArea = editor.getTextArea(); editor.setText(formattedText); editor.getSketch().setModified(true); - textArea.setCaretPosition(Math.min(textArea.getLineStartOffset(line) + lineOffset, textArea.getSafeLineStopOffset(line) - 1)); + + try { + int line = textArea.getLineOfOffset(textArea.getCaretPosition()); + int lineOffset = textArea.getCaretPosition() - textArea.getLineStartOffset(line); + textArea.setCaretPosition(Math.min(textArea.getLineStartOffset(line) + lineOffset, textArea.getLineEndOffset(line) - 1)); + } catch (BadLocationException e) { + e.printStackTrace(); + } // mark as finished editor.statusNotice(_("Auto Format finished.")); } diff --git a/app/src/processing/app/CaretAwareUndoableEdit.java b/app/src/processing/app/CaretAwareUndoableEdit.java index ba8e67d85..d9b1349a5 100644 --- a/app/src/processing/app/CaretAwareUndoableEdit.java +++ b/app/src/processing/app/CaretAwareUndoableEdit.java @@ -1,17 +1,17 @@ package processing.app; -import processing.app.syntax.JEditTextArea; - import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoableEdit; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; + public class CaretAwareUndoableEdit implements UndoableEdit { private final UndoableEdit undoableEdit; private final int caretPosition; - public CaretAwareUndoableEdit(UndoableEdit undoableEdit, JEditTextArea textArea) { + public CaretAwareUndoableEdit(UndoableEdit undoableEdit, RSyntaxTextArea textArea) { this.undoableEdit = undoableEdit; this.caretPosition = textArea.getCaretPosition(); } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index e5b7bb108..d5b2e035b 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -51,6 +51,12 @@ import javax.swing.event.*; import javax.swing.text.*; import javax.swing.undo.*; +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit; +import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.rtextarea.Gutter; +import org.fife.ui.rtextarea.RTextScrollPane; + import cc.arduino.packages.BoardPort; import cc.arduino.packages.Uploader; import cc.arduino.packages.uploaders.SerialUploader; @@ -125,8 +131,8 @@ public class Editor extends JFrame implements RunnerListener { //JEditorPane editorPane; - JEditTextArea textarea; - EditorListener listener; + SketchTextArea textarea; + RTextScrollPane scrollPane; // runtime information and window placement Point sketchWindowLocation; @@ -144,9 +150,6 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem undoItem, redoItem; protected UndoAction undoAction; protected RedoAction redoAction; - LastUndoableEditAwareUndoManager undo; - // used internally, and only briefly - CompoundEdit compoundEdit; FindReplace find; @@ -231,10 +234,8 @@ public class Editor extends JFrame implements RunnerListener { header = new EditorHeader(this); upper.add(header); - textarea = new JEditTextArea(new PdeTextAreaDefaults()); + textarea = createTextArea(); textarea.setName("editor"); - textarea.setRightClickPopup(new TextAreaPopup()); - textarea.setHorizontalOffset(6); // assemble console panel, consisting of status area and the console itself consolePanel = new JPanel(); @@ -252,9 +253,19 @@ public class Editor extends JFrame implements RunnerListener { lineStatus = new EditorLineStatus(textarea); consolePanel.add(lineStatus, BorderLayout.SOUTH); - upper.add(textarea); - splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, - upper, consolePanel); + //scrollPane = new RTextScrollPane(textarea); + + scrollPane = new RTextScrollPane(textarea, true); + scrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); + scrollPane.setIconRowHeaderEnabled(true); + + Gutter gutter = scrollPane.getGutter(); + gutter.setBookmarkingEnabled(false); + //gutter.setBookmarkIcon(CompletionsRenderer.getIcon(CompletionType.TEMPLATE)); + gutter.setIconRowHeaderInheritsGutterBackground(true); + + upper.add(scrollPane); + splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, upper, consolePanel); splitPane.setOneTouchExpandable(true); // repaint child panes while resizing @@ -280,7 +291,7 @@ public class Editor extends JFrame implements RunnerListener { // hopefully these are no longer needed w/ swing // (har har har.. that was wishful thinking) - listener = new EditorListener(this, textarea); + // listener = new EditorListener(this, textarea); pain.add(box); // get shift down/up events so we can show the alt version of toolbar buttons @@ -439,32 +450,29 @@ public class Editor extends JFrame implements RunnerListener { saveMenuItem.setEnabled(!external); saveAsMenuItem.setEnabled(!external); - textarea.setDisplayLineNumbers(PreferencesData.getBoolean("editor.linenumbers")); + textarea.setMarginLineEnabled(PreferencesData.getBoolean("editor.linenumbers")); - TextAreaPainter painter = textarea.getPainter(); if (external) { // disable line highlight and turn off the caret when disabling Color color = Theme.getColor("editor.external.bgcolor"); - painter.setBackground(color); - painter.setLineHighlightEnabled(false); - textarea.setCaretVisible(false); + textarea.setBackground(color); + textarea.setHighlightCurrentLine(false); + textarea.setEditable(false); } else { - Color color = Theme.getColor("editor.bgcolor"); - painter.setBackground(color); boolean highlight = PreferencesData.getBoolean("editor.linehighlight"); - painter.setLineHighlightEnabled(highlight); - textarea.setCaretVisible(true); + textarea.setHighlightCurrentLine(highlight); + textarea.setEditable(true); } // apply changes to the font size for the editor //TextAreaPainter painter = textarea.getPainter(); - painter.setFont(PreferencesData.getFont("editor.font")); + textarea.setFont(PreferencesData.getFont("editor.font")); //Font font = painter.getFont(); //textarea.getPainter().setFont(new Font("Courier", Font.PLAIN, 36)); // in case tab expansion stuff has changed - listener.applyPreferences(); + // listener.applyPreferences(); // in case moved to a new location // For 0125, changing to async version (to be implemented later) @@ -943,6 +951,23 @@ public class Editor extends JFrame implements RunnerListener { return null; } + + protected SketchTextArea createTextArea(){ + SketchTextArea textArea = new SketchTextArea(); + textArea.requestFocusInWindow(); + textArea.setMarkOccurrences(true); + textArea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.codefolding")); + textArea.setAntiAliasingEnabled(PreferencesData.getBoolean("editor.antialias")); +// textArea.setClearWhitespaceLinesEnabled(false); + textArea.setTabsEmulated(PreferencesData.getBoolean("editor.tabs.expand")); + textArea.setTabSize(PreferencesData.getInteger("editor.tabs.size")); + textArea.setEditorListener(new EditorListener(this)); + + ToolTipManager.sharedInstance().registerComponent(textArea); + + configurePopupMenu(textArea); + return textArea; + } protected JMenuItem createToolMenuItem(String className) { try { @@ -1401,18 +1426,6 @@ public class Editor extends JFrame implements RunnerListener { }); menu.add(item); - item = newJMenuItem(_("Use Selection For Find"), 'E'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (find == null) { - find = new FindReplace(Editor.this); - } - find.setLocationRelativeTo(Editor.this); - find.setFindText( getSelectedText() ); - } - }); - menu.add(item); - return menu; } @@ -1462,24 +1475,18 @@ public class Editor extends JFrame implements RunnerListener { public void actionPerformed(ActionEvent e) { try { - undo.undo(); + textarea.undoLastAction(); sketch.setModified(true); } catch (CannotUndoException ex) { //System.out.println("Unable to undo: " + ex); //ex.printStackTrace(); } - if (undo.getLastUndoableEdit() != null && undo.getLastUndoableEdit() instanceof CaretAwareUndoableEdit) { - CaretAwareUndoableEdit undoableEdit = (CaretAwareUndoableEdit) undo.getLastUndoableEdit(); - int nextCaretPosition = undoableEdit.getCaretPosition() - 1; - if (nextCaretPosition >= 0 && textarea.getDocumentLength() > nextCaretPosition) { - textarea.setCaretPosition(nextCaretPosition); - } - } - updateUndoState(); - redoAction.updateRedoState(); } protected void updateUndoState() { + + UndoManager undo = textarea.getUndoManager(); + if (undo.canUndo()) { this.setEnabled(true); undoItem.setEnabled(true); @@ -1503,21 +1510,17 @@ public class Editor extends JFrame implements RunnerListener { public void actionPerformed(ActionEvent e) { try { - undo.redo(); + textarea.redoLastAction(); sketch.setModified(true); } catch (CannotRedoException ex) { //System.out.println("Unable to redo: " + ex); //ex.printStackTrace(); } - if (undo.getLastUndoableEdit() != null && undo.getLastUndoableEdit() instanceof CaretAwareUndoableEdit) { - CaretAwareUndoableEdit undoableEdit = (CaretAwareUndoableEdit) undo.getLastUndoableEdit(); - textarea.setCaretPosition(undoableEdit.getCaretPosition()); - } - updateRedoState(); - undoAction.updateUndoState(); } protected void updateRedoState() { + UndoManager undo = textarea.getUndoManager(); + if (undo.canRedo()) { redoItem.setEnabled(true); redoItem.setText(undo.getRedoPresentationName()); @@ -1581,13 +1584,13 @@ public class Editor extends JFrame implements RunnerListener { /** - * Get the JEditTextArea object for use (not recommended). This should only + * Get the TextArea object for use (not recommended). This should only * be used in obscure cases that really need to hack the internals of the * JEditTextArea. Most tools should only interface via the get/set functions * found in this class. This will maintain compatibility with future releases, - * which will not use JEditTextArea. + * which will not use TextArea. */ - public JEditTextArea getTextArea() { + public SketchTextArea getTextArea() { return textarea; } @@ -1604,7 +1607,11 @@ public class Editor extends JFrame implements RunnerListener { * Get a range of text from the current buffer. */ public String getText(int start, int stop) { - return textarea.getText(start, stop - start); + try { + return textarea.getText(start, stop - start); + } catch (BadLocationException e) { + return null; + } } @@ -1612,20 +1619,10 @@ public class Editor extends JFrame implements RunnerListener { * Replace the entire contents of the front-most tab. */ public void setText(String what) { - startCompoundEdit(); textarea.setText(what); - stopCompoundEdit(); } - public void insertText(String what) { - startCompoundEdit(); - int caret = getCaretOffset(); - setSelection(caret, caret); - textarea.setSelectedText(what); - stopCompoundEdit(); - } - /** * Called to update the text but not switch to a different set of code @@ -1651,15 +1648,10 @@ public class Editor extends JFrame implements RunnerListener { public void setSelectedText(String what) { - textarea.setSelectedText(what); + textarea.replaceSelection(what); } - public void setSelection(int start, int stop) { - // make sure that a tool isn't asking for a bad location - start = PApplet.constrain(start, 0, textarea.getDocumentLength()); - stop = PApplet.constrain(stop, 0, textarea.getDocumentLength()); - textarea.select(start, stop); } @@ -1695,7 +1687,7 @@ public class Editor extends JFrame implements RunnerListener { * Get the end point of the current selection. */ public int getSelectionStop() { - return textarea.getSelectionStop(); + return textarea.getSelectionEnd(); } @@ -1703,18 +1695,11 @@ public class Editor extends JFrame implements RunnerListener { * Get text for a specified line. */ public String getLineText(int line) { - return textarea.getLineText(line); - } - - - /** - * Replace the text on a specified line. - */ - public void setLineText(int line, String what) { - startCompoundEdit(); - textarea.select(getLineStartOffset(line), getLineStopOffset(line)); - textarea.setSelectedText(what); - stopCompoundEdit(); + try { + return textarea.getText(textarea.getLineStartOffset(line), textarea.getLineEndOffset(line)); + } catch (BadLocationException e) { + return ""; + } } @@ -1722,7 +1707,11 @@ public class Editor extends JFrame implements RunnerListener { * Get character offset for the start of a given line of text. */ public int getLineStartOffset(int line) { - return textarea.getLineStartOffset(line); + try { + return textarea.getLineStartOffset(line); + } catch (BadLocationException e) { + return -1; + } } @@ -1730,7 +1719,11 @@ public class Editor extends JFrame implements RunnerListener { * Get character offset for end of a given line of text. */ public int getLineStopOffset(int line) { - return textarea.getLineStopOffset(line); + try { + return textarea.getLineEndOffset(line); + } catch (BadLocationException e) { + return -1; + } } @@ -1740,31 +1733,10 @@ public class Editor extends JFrame implements RunnerListener { public int getLineCount() { return textarea.getLineCount(); } - - - /** - * Use before a manipulating text to group editing operations together as a - * single undo. Use stopCompoundEdit() once finished. - */ - public void startCompoundEdit() { - compoundEdit = new CompoundEdit(); - } - - - /** - * Use with startCompoundEdit() to group edit operations in a single undo. - */ - public void stopCompoundEdit() { - compoundEdit.end(); - undo.addEdit(compoundEdit); - undoAction.updateUndoState(); - redoAction.updateRedoState(); - compoundEdit = null; - } - + public int getScrollPosition() { - return textarea.getScrollPosition(); + return scrollPane.getVerticalScrollBar().getValue(); } @@ -1775,15 +1747,12 @@ public class Editor extends JFrame implements RunnerListener { * Switch between tabs, this swaps out the Document object * that's currently being manipulated. */ - protected void setCode(SketchCodeDocument codeDoc) { - SyntaxDocument document = (SyntaxDocument) codeDoc.getDocument(); + protected void setCode(final SketchCodeDocument codeDoc) { + RSyntaxDocument document = (RSyntaxDocument) codeDoc.getDocument(); if (document == null) { // this document not yet inited - document = new SyntaxDocument(); - codeDoc.setDocument(document); - - // turn on syntax highlighting - document.setTokenMarker(new PdeKeywords()); + document = new RSyntaxDocument(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS); + document.putProperty(PlainDocument.tabSizeAttribute, Preferences.getInteger("editor.tabs.size")); // insert the program text into the document object try { @@ -1791,37 +1760,39 @@ public class Editor extends JFrame implements RunnerListener { } catch (BadLocationException bl) { bl.printStackTrace(); } - // set up this guy's own undo manager // code.undo = new UndoManager(); - - // connect the undo listener to the editor - document.addUndoableEditListener(new UndoableEditListener() { - public void undoableEditHappened(UndoableEditEvent e) { - if (compoundEdit != null) { - compoundEdit.addEdit(new CaretAwareUndoableEdit(e.getEdit(), textarea)); - } else if (undo != null) { - undo.addEdit(new CaretAwareUndoableEdit(e.getEdit(), textarea)); - } - if (compoundEdit != null || undo != null) { - sketch.setModified(true); - undoAction.updateUndoState(); - redoAction.updateRedoState(); - } - } - }); + + codeDoc.setDocument(document); } - // update the document object that's in use - textarea.setDocument(document, - codeDoc.getSelectionStart(), codeDoc.getSelectionStop(), - codeDoc.getScrollPosition()); - + if(codeDoc.getUndo() == null){ + codeDoc.setUndo(new LastUndoableEditAwareUndoManager(textarea, this)); + document.addUndoableEditListener(codeDoc.getUndo()); + } + + // Update the document object that's in use + textarea.switchDocument(document, codeDoc.getUndo()); + + // HACK multiple tabs: for update Listeners of Gutter, forcin call: Gutter.setTextArea(RTextArea) + // BUG: https://github.com/bobbylight/RSyntaxTextArea/issues/84 + scrollPane.setViewportView(textarea); + + textarea.select(codeDoc.getSelectionStart(), codeDoc.getSelectionStop()); textarea.requestFocus(); // get the caret blinking - - this.undo = codeDoc.getUndo(); - undoAction.updateUndoState(); - redoAction.updateRedoState(); + + final int position = codeDoc.getScrollPosition(); + + // invokeLater: Expect the document to be rendered correctly to set the new position + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + scrollPane.getVerticalScrollBar().setValue(position); + undoAction.updateUndoState(); + redoAction.updateRedoState(); + } + }); + } @@ -1833,7 +1804,6 @@ public class Editor extends JFrame implements RunnerListener { */ public void handleCut() { textarea.cut(); - sketch.setModified(true); } @@ -1860,7 +1830,6 @@ public class Editor extends JFrame implements RunnerListener { */ public void handlePaste() { textarea.paste(); - sketch.setModified(true); } @@ -1871,104 +1840,68 @@ public class Editor extends JFrame implements RunnerListener { textarea.selectAll(); } - - protected void handleCommentUncomment() { - startCompoundEdit(); - - int startLine = textarea.getSelectionStartLine(); - int stopLine = textarea.getSelectionStopLine(); - - int lastLineStart = textarea.getLineStartOffset(stopLine); - int selectionStop = textarea.getSelectionStop(); - // If the selection ends at the beginning of the last line, - // then don't (un)comment that line. - if (selectionStop == lastLineStart) { - // Though if there's no selection, don't do that - if (textarea.isSelectionActive()) { - stopLine--; - } - } - - // If the text is empty, ignore the user. - // Also ensure that all lines are commented (not just the first) - // when determining whether to comment or uncomment. - int length = textarea.getDocumentLength(); - boolean commented = true; - for (int i = startLine; commented && (i <= stopLine); i++) { - int pos = textarea.getLineStartOffset(i); - if (pos + 2 > length) { - commented = false; - } else { - // Check the first two characters to see if it's already a comment. - String begin = textarea.getText(pos, 2); - //System.out.println("begin is '" + begin + "'"); - commented = begin.equals("//"); - } - } - - for (int line = startLine; line <= stopLine; line++) { - int location = textarea.getLineStartOffset(line); - if (commented) { - // remove a comment - textarea.select(location, location+2); - if (textarea.getSelectedText().equals("//")) { - textarea.setSelectedText(""); - } - } else { - // add a comment - textarea.select(location, location); - textarea.setSelectedText("//"); - } - } - // Subtract one from the end, otherwise selects past the current line. - // (Which causes subsequent calls to keep expanding the selection) - textarea.select(textarea.getLineStartOffset(startLine), - textarea.getLineStopOffset(stopLine) - 1); - stopCompoundEdit(); + /** + * Begins an "atomic" edit. This method is called when TextArea + * KNOWS that some edits should be compound automatically, such as the playing back of a macro. + * + * @see #endInternalAtomicEdit() + */ + public void beginInternalAtomicEdit(){ + textarea.getUndoManager().beginInternalAtomicEdit(); } + /** + * Ends an "atomic" edit. + * + * @see #beginInternalAtomicEdit() + */ + public void endInternalAtomicEdit(){ + textarea.getUndoManager().endInternalAtomicEdit(); + } + + + void handleCommentUncomment() { + + Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaToggleCommentAction); + action.actionPerformed(null); + + } + protected void handleIndentOutdent(boolean indent) { - int tabSize = PreferencesData.getInteger("editor.tabs.size"); - String tabString = Editor.EMPTY.substring(0, tabSize); + if (indent) { - startCompoundEdit(); + int caretPosition = textarea.getCaretPosition(); + boolean noSelec = !textarea.isSelectionActive(); - int startLine = textarea.getSelectionStartLine(); - int stopLine = textarea.getSelectionStopLine(); - - // If the selection ends at the beginning of the last line, - // then don't (un)comment that line. - int lastLineStart = textarea.getLineStartOffset(stopLine); - int selectionStop = textarea.getSelectionStop(); - if (selectionStop == lastLineStart) { - // Though if there's no selection, don't do that - if (textarea.isSelectionActive()) { - stopLine--; - } - } - - for (int line = startLine; line <= stopLine; line++) { - int location = textarea.getLineStartOffset(line); - - if (indent) { - textarea.select(location, location); - textarea.setSelectedText(tabString); - - } else { // outdent - textarea.select(location, location + tabSize); - // Don't eat code if it's not indented - if (textarea.getSelectedText().equals(tabString)) { - textarea.setSelectedText(""); + // if no selection, focus on first char. + if (noSelec) { + try { + int line = textarea.getCaretLineNumber(); + int startOffset = textarea.getLineStartOffset(line); + textarea.setCaretPosition(startOffset); + } catch (BadLocationException e) { } } + + // Insert Tab or Spaces.. + Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.insertTabAction); + action.actionPerformed(null); + + if (noSelec) { + textarea.setCaretPosition(caretPosition); + } + + } else { + Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaDecreaseIndentAction); + action.actionPerformed(null); } - // Subtract one from the end, otherwise selects past the current line. - // (Which causes subsequent calls to keep expanding the selection) - textarea.select(textarea.getLineStartOffset(startLine), - textarea.getLineStopOffset(stopLine) - 1); - stopCompoundEdit(); } + + /** Checks the preferences you are in external editing mode */ + public static boolean isExternalMode(){ + return PreferencesData.getBoolean("editor.external"); + } protected String getCurrentKeyword() { String text = ""; @@ -2077,6 +2010,7 @@ public class Editor extends JFrame implements RunnerListener { @Override public void run() { try { + textarea.removeAllLineHighlights(); sketch.prepare(); sketch.build(verbose, saveHex); statusNotice(_("Done compiling.")); @@ -2258,7 +2192,7 @@ public class Editor extends JFrame implements RunnerListener { sketch.setCurrentCode(codeIndex); textarea.select(selStart, selStop); - textarea.setScrollPosition(scrollPos); + scrollPane.getVerticalScrollBar().setValue(scrollPos); } @@ -2383,6 +2317,7 @@ public class Editor extends JFrame implements RunnerListener { public boolean handleSave(boolean immediately) { //stopRunner(); handleStop(); // 0136 + textarea.removeAllLineHighlights(); if (untitled) { return handleSaveAs(); @@ -2759,9 +2694,9 @@ public class Editor extends JFrame implements RunnerListener { } if (pageFormat != null) { //System.out.println("setting page format " + pageFormat); - printerJob.setPrintable(textarea.getPainter(), pageFormat); + printerJob.setPrintable(textarea, pageFormat); } else { - printerJob.setPrintable(textarea.getPainter()); + printerJob.setPrintable(textarea); } // set the name of the job to the code name printerJob.setJobName(sketch.getCurrentCode().getPrettyName()); @@ -2818,7 +2753,7 @@ public class Editor extends JFrame implements RunnerListener { // The error is at the end of this current chunk of code, // so the last line needs to be selected. line = textarea.getLineCount() - 1; - if (textarea.getLineText(line).length() == 0) { + if (getLineText(line).length() == 0) { // The last line may be zero length, meaning nothing to select. // If so, back up one more line. line--; @@ -2827,8 +2762,12 @@ public class Editor extends JFrame implements RunnerListener { if (line < 0 || line >= textarea.getLineCount()) { System.err.println(I18n.format(_("Bad error line: {0}"), line)); } else { - textarea.select(textarea.getLineStartOffset(line), - textarea.getLineStopOffset(line) - 1); + try { + textarea.addLineHighlight(line, new Color(1, 0, 0, 0.2f)); + textarea.setCaretPosition(textarea.getLineStartOffset(line)); + } catch (BadLocationException e1) { + e1.printStackTrace(); + } } } } @@ -2879,148 +2818,101 @@ public class Editor extends JFrame implements RunnerListener { lineStatus.repaint(); } - /** - * Returns the edit popup menu. - */ - class TextAreaPopup extends JPopupMenu { - //private String currentDir = System.getProperty("user.dir"); - private String referenceFile = null; - private JMenuItem cutItem; - private JMenuItem copyItem; - private JMenuItem discourseItem; - private JMenuItem referenceItem; - private JMenuItem openURLItem; - private JSeparator openURLItemSeparator; + protected void configurePopupMenu(final SketchTextArea textarea){ - private String clickedURL; + JPopupMenu menu = textarea.getPopupMenu(); - public TextAreaPopup() { - openURLItem = new JMenuItem(_("Open URL")); - openURLItem.addActionListener(new ActionListener() { + menu.addSeparator(); + + JMenuItem item = createToolMenuItem("cc.arduino.packages.formatter.AStyle"); + item.setName("menuToolsAutoFormat"); + + menu.add(item); + + item = newJMenuItem(_("Comment/Uncomment"), '/'); + item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.openURL(clickedURL); + handleCommentUncomment(); } - }); - add(openURLItem); + }); + menu.add(item); - openURLItemSeparator = new JSeparator(); - add(openURLItemSeparator); - - cutItem = new JMenuItem(_("Cut")); - cutItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCut(); - } - }); - add(cutItem); - - copyItem = new JMenuItem(_("Copy")); - copyItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCopy(); - } - }); - add(copyItem); - - discourseItem = new JMenuItem(_("Copy for Forum")); - discourseItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleDiscourseCopy(); - } - }); - add(discourseItem); - - discourseItem = new JMenuItem(_("Copy as HTML")); - discourseItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleHTMLCopy(); - } - }); - add(discourseItem); - - JMenuItem item = new JMenuItem(_("Paste")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handlePaste(); - } - }); - add(item); - - item = new JMenuItem(_("Select All")); - item.addActionListener(new ActionListener() { + item = newJMenuItem(_("Increase Indent"), ']'); + item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleSelectAll(); + handleIndentOutdent(true); } - }); - add(item); + }); + menu.add(item); - addSeparator(); + item = newJMenuItem(_("Decrease Indent"), '['); + item.setName("menuDecreaseIndent"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleIndentOutdent(false); + } + }); + menu.add(item); - item = new JMenuItem(_("Comment/Uncomment")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); - } - }); - add(item); + item = new JMenuItem(_("Copy for Forum")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleDiscourseCopy(); + } + }); + menu.add(item); - item = new JMenuItem(_("Increase Indent")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); - } - }); - add(item); + item = new JMenuItem(_("Copy as HTML")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleHTMLCopy(); + } + }); + menu.add(item); - item = new JMenuItem(_("Decrease Indent")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(false); - } - }); - add(item); + final JMenuItem referenceItem = new JMenuItem(_("Find in Reference")); + referenceItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleFindReference(); + } + }); + menu.add(referenceItem); - addSeparator(); + final JMenuItem openURLItem = new JMenuItem(_("Open URL")); + openURLItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Base.openURL(e.getActionCommand()); + } + }); + menu.add(openURLItem); + + menu.addPopupMenuListener(new PopupMenuListener() { - referenceItem = new JMenuItem(_("Find in Reference")); - referenceItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleFindReference(); - } - }); - add(referenceItem); - } - - // if no text is selected, disable copy and cut menu items - public void show(Component component, int x, int y) { - int lineNo = textarea.getLineOfOffset(textarea.xyToOffset(x, y)); - int offset = textarea.xToOffset(lineNo, x); - String line = textarea.getLineText(lineNo); - clickedURL = textarea.checkClickedURL(line, offset); - if (clickedURL != null) { - openURLItem.setVisible(true); - openURLItemSeparator.setVisible(true); - } else { - openURLItem.setVisible(false); - openURLItemSeparator.setVisible(false); + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + String referenceFile = PdeKeywords.getReference(getCurrentKeyword()); + referenceItem.setEnabled(referenceFile != null); + + int offset = textarea.getCaretPosition(); + org.fife.ui.rsyntaxtextarea.Token token = RSyntaxUtilities.getTokenAtOffset(textarea, offset); + if (token != null && token.isHyperlink()) { + openURLItem.setEnabled(true); + openURLItem.setActionCommand(token.getLexeme()); + } else { + openURLItem.setEnabled(false); + } } - if (textarea.isSelectionActive()) { - cutItem.setEnabled(true); - copyItem.setEnabled(true); - discourseItem.setEnabled(true); - - } else { - cutItem.setEnabled(false); - copyItem.setEnabled(false); - discourseItem.setEnabled(false); + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } - referenceFile = PdeKeywords.getReference(getCurrentKeyword()); - referenceItem.setEnabled(referenceFile != null); + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); - super.show(component, x, y); - } } + } diff --git a/app/src/processing/app/EditorLineStatus.java b/app/src/processing/app/EditorLineStatus.java index 408b6545b..2acb1eb10 100644 --- a/app/src/processing/app/EditorLineStatus.java +++ b/app/src/processing/app/EditorLineStatus.java @@ -1,3 +1,5 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + /* Part of the Processing project - http://processing.org @@ -20,26 +22,23 @@ package processing.app; -import processing.app.helpers.OSUtils; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Image; +import java.awt.*; import java.awt.geom.Rectangle2D; +import java.util.Map; import javax.swing.JComponent; +import processing.app.helpers.OSUtils; import processing.app.helpers.PreferencesMap; -import processing.app.syntax.JEditTextArea; +import processing.app.syntax.SketchTextArea; + /** * Li'l status bar fella that shows the line number. */ -@SuppressWarnings("serial") public class EditorLineStatus extends JComponent { - JEditTextArea textarea; + SketchTextArea textarea; + int start = -1, stop; Image resize; @@ -55,9 +54,11 @@ public class EditorLineStatus extends JComponent { String name = ""; String serialport = ""; - public EditorLineStatus(JEditTextArea textarea) { + + public EditorLineStatus(SketchTextArea textarea) { + this.textarea = textarea; - textarea.editorLineStatus = this; + textarea.setEditorLineStatus(this); background = Theme.getColor("linestatus.bgcolor"); font = Theme.getFont("linestatus.font"); diff --git a/app/src/processing/app/EditorListener.java b/app/src/processing/app/EditorListener.java index 0acb7e4c8..19e90b1c3 100644 --- a/app/src/processing/app/EditorListener.java +++ b/app/src/processing/app/EditorListener.java @@ -1,636 +1,83 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2004-08 Ben Fry and Casey Reas - Copyright (c) 2001-04 Massachusetts Institute of Technology - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - package processing.app; -import processing.app.syntax.*; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; -import java.awt.*; -import java.awt.event.*; +import processing.app.syntax.SketchTextArea; - -/** - * Filters key events for tab expansion/indent/etc. - *

- * For version 0099, some changes have been made to make the indents - * smarter. There are still issues though: - * + indent happens when it picks up a curly brace on the previous line, - * but not if there's a blank line between them. - * + It also doesn't handle single indent situations where a brace - * isn't used (i.e. an if statement or for loop that's a single line). - * It shouldn't actually be using braces. - * Solving these issues, however, would probably best be done by a - * smarter parser/formatter, rather than continuing to hack this class. - */ -public class EditorListener { +public class EditorListener implements KeyListener { + private Editor editor; - private JEditTextArea textarea; - - private boolean externalEditor; - private boolean tabsExpand; - private boolean tabsIndent; - private int tabSize; - private String tabString; - private boolean autoIndent; - -// private int selectionStart, selectionEnd; -// private int position; - - /** ctrl-alt on windows and linux, cmd-alt on mac os x */ - static final int CTRL_ALT = ActionEvent.ALT_MASK | - Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); - - - public EditorListener(Editor editor, JEditTextArea textarea) { + + public EditorListener(Editor editor) { + super(); this.editor = editor; - this.textarea = textarea; - - // let him know that i'm leechin' - textarea.editorListener = this; - - applyPreferences(); } + + /** ctrl-alt on windows and linux, cmd-alt on mac os x */ + static final int CTRL_ALT = ActionEvent.ALT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + static final int CTRL_SHIFT = ActionEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + + static final int CTRL = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + - public void applyPreferences() { - tabsExpand = PreferencesData.getBoolean("editor.tabs.expand"); - //tabsIndent = Preferences.getBoolean("editor.tabs.indent"); - tabSize = PreferencesData.getInteger("editor.tabs.size"); - tabString = Editor.EMPTY.substring(0, tabSize); - autoIndent = PreferencesData.getBoolean("editor.indent"); - externalEditor = PreferencesData.getBoolean("editor.external"); - } - - - //public void setExternalEditor(boolean externalEditor) { - //this.externalEditor = externalEditor; - //} - - - /** - * Intercepts key pressed events for JEditTextArea. - *

- * Called by JEditTextArea inside processKeyEvent(). Note that this - * won't intercept actual characters, because those are fired on - * keyTyped(). - * @return true if the event has been handled (to remove it from the queue) - */ - public boolean keyPressed(KeyEvent event) { - // don't do things if the textarea isn't editable - if (externalEditor) return false; - - //deselect(); // this is for paren balancing + public void keyTyped(KeyEvent event) { char c = event.getKeyChar(); - int code = event.getKeyCode(); -// if (code == KeyEvent.VK_SHIFT) { -// editor.toolbar.setShiftPressed(true); -// } - - //System.out.println((int)c + " " + code + " " + event); - //System.out.println(); + if ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0) { + // The char is not control code when CTRL key pressed? It should be a shortcut. + if (!Character.isISOControl(c)) { + event.consume(); + } + } + } + @Override + public void keyPressed(KeyEvent event) { + + SketchTextArea textarea = editor.getTextArea(); + + if (!textarea.isEditable()) return; + Sketch sketch = editor.getSketch(); + int code = event.getKeyCode(); + + // Navigation.. + if ((event.getModifiers() & CTRL) == CTRL && code == KeyEvent.VK_TAB) { + sketch.handleNextCode(); + } + + // Navigation.. + // FIXME: not working on LINUX !!! + if (((event.getModifiers() & CTRL_SHIFT) == CTRL_SHIFT)) { + if(code == KeyEvent.VK_TAB) + sketch.handlePrevCode(); + } + + // Navigation.. if ((event.getModifiers() & CTRL_ALT) == CTRL_ALT) { if (code == KeyEvent.VK_LEFT) { sketch.handlePrevCode(); - return true; } else if (code == KeyEvent.VK_RIGHT) { sketch.handleNextCode(); - return true; } } - - if ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0) { - // Consume ctrl-m(carriage return) keypresses - if (code == KeyEvent.VK_M) { - event.consume(); // does nothing - return false; - } - - // The char is not control code when CTRL key pressed? It should be a shortcut. - if (!Character.isISOControl(c)) { - return false; - } - } - - if ((event.getModifiers() & KeyEvent.META_MASK) != 0) { - //event.consume(); // does nothing - return false; - } - - // TODO i don't like these accessors. clean em up later. - if (!editor.getSketch().isModified()) { - if ((code == KeyEvent.VK_BACK_SPACE) || (code == KeyEvent.VK_TAB) || - (code == KeyEvent.VK_ENTER) || ((c >= 32) && (c < 128))) { - sketch.setModified(true); - } - } - - if ((code == KeyEvent.VK_UP) && - ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { - // back up to the last empty line - char contents[] = textarea.getText().toCharArray(); - //int origIndex = textarea.getCaretPosition() - 1; - int caretIndex = textarea.getCaretPosition(); - - int index = calcLineStart(caretIndex - 1, contents); - //System.out.println("line start " + (int) contents[index]); - index -= 2; // step over the newline - //System.out.println((int) contents[index]); - boolean onlySpaces = true; - while (index > 0) { - if (contents[index] == 10) { - if (onlySpaces) { - index++; - break; - } else { - onlySpaces = true; // reset - } - } else if (contents[index] != ' ') { - onlySpaces = false; - } - index--; - } - // if the first char, index will be -2 - if (index < 0) index = 0; - - if ((event.getModifiers() & KeyEvent.SHIFT_MASK) != 0) { - textarea.setSelectionStart(caretIndex); - textarea.setSelectionEnd(index); - } else { - textarea.setCaretPosition(index); - } - event.consume(); - return true; - - } else if ((code == KeyEvent.VK_DOWN) && - ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { - char contents[] = textarea.getText().toCharArray(); - int caretIndex = textarea.getCaretPosition(); - - int index = caretIndex; - int lineStart = 0; - boolean onlySpaces = false; // don't count this line - while (index < contents.length) { - if (contents[index] == 10) { - if (onlySpaces) { - index = lineStart; // this is it - break; - } else { - lineStart = index + 1; - onlySpaces = true; // reset - } - } else if (contents[index] != ' ') { - onlySpaces = false; - } - index++; - } - // if the first char, index will be -2 - //if (index < 0) index = 0; - - //textarea.setSelectionStart(index); - //textarea.setSelectionEnd(index); - if ((event.getModifiers() & KeyEvent.SHIFT_MASK) != 0) { - textarea.setSelectionStart(caretIndex); - textarea.setSelectionEnd(index); - } else { - textarea.setCaretPosition(index); - } - event.consume(); - return true; - } - - - switch ((int) c) { - - case 9: // TAB - if (textarea.isSelectionActive()) { - boolean outdent = (event.getModifiers() & KeyEvent.SHIFT_MASK) != 0; - editor.handleIndentOutdent(!outdent); - - } else if (tabsExpand) { // expand tabs - textarea.setSelectedText(tabString); - event.consume(); - return true; - - } else if (tabsIndent) { - // this code is incomplete - - // if this brace is the only thing on the line, outdent - //char contents[] = getCleanedContents(); - char contents[] = textarea.getText().toCharArray(); - // index to the character to the left of the caret - int prevCharIndex = textarea.getCaretPosition() - 1; - - // now find the start of this line - int lineStart = calcLineStart(prevCharIndex, contents); - - int lineEnd = lineStart; - while ((lineEnd < contents.length - 1) && - (contents[lineEnd] != 10)) { - lineEnd++; - } - - // get the number of braces, to determine whether this is an indent - int braceBalance = 0; - int index = lineStart; - while ((index < contents.length) && - (contents[index] != 10)) { - if (contents[index] == '{') { - braceBalance++; - } else if (contents[index] == '}') { - braceBalance--; - } - index++; - } - - // if it's a starting indent, need to ignore it, so lineStart - // will be the counting point. but if there's a closing indent, - // then the lineEnd should be used. - int where = (braceBalance > 0) ? lineStart : lineEnd; - int indent = calcBraceIndent(where, contents); - if (indent == -1) { - // no braces to speak of, do nothing - indent = 0; - } else { - indent += tabSize; - } - - // and the number of spaces it has - int spaceCount = calcSpaceCount(prevCharIndex, contents); - - textarea.setSelectionStart(lineStart); - textarea.setSelectionEnd(lineStart + spaceCount); - textarea.setSelectedText(Editor.EMPTY.substring(0, indent)); - - event.consume(); - return true; - } - break; - - case 10: // auto-indent - case 13: - if (autoIndent) { - char contents[] = textarea.getText().toCharArray(); - - // this is the previous character - // (i.e. when you hit return, it'll be the last character - // just before where the newline will be inserted) - int origIndex = textarea.getCaretPosition() - 1; - - // NOTE all this cursing about CRLF stuff is probably moot - // NOTE since the switch to JEditTextArea, which seems to use - // NOTE only LFs internally (thank god). disabling for 0099. - // walk through the array to the current caret position, - // and count how many weirdo windows line endings there are, - // which would be throwing off the caret position number - /* - int offset = 0; - int realIndex = origIndex; - for (int i = 0; i < realIndex-1; i++) { - if ((contents[i] == 13) && (contents[i+1] == 10)) { - offset++; - realIndex++; - } - } - // back up until \r \r\n or \n.. @#($* cross platform - //System.out.println(origIndex + " offset = " + offset); - origIndex += offset; // ARGH!#(* WINDOWS#@($* - */ - - // if the previous thing is a brace (whether prev line or - // up farther) then the correct indent is the number of spaces - // on that line + 'indent'. - // if the previous line is not a brace, then just use the - // identical indentation to the previous line - - // calculate the amount of indent on the previous line - // this will be used *only if the prev line is not an indent* - int spaceCount = calcSpaceCount(origIndex, contents); - - // If the last character was a left curly brace, then indent. - // For 0122, walk backwards a bit to make sure that the there - // isn't a curly brace several spaces (or lines) back. Also - // moved this before calculating extraCount, since it'll affect - // that as well. - int index2 = origIndex; - while ((index2 >= 0) && - Character.isWhitespace(contents[index2])) { - index2--; - } - if (index2 != -1) { - // still won't catch a case where prev stuff is a comment - if (contents[index2] == '{') { - // intermediate lines be damned, - // use the indent for this line instead - spaceCount = calcSpaceCount(index2, contents); - spaceCount += tabSize; - } - } - //System.out.println("spaceCount should be " + spaceCount); - - // now before inserting this many spaces, walk forward from - // the caret position and count the number of spaces, - // so that the number of spaces aren't duplicated again - int index = origIndex + 1; - int extraCount = 0; - while ((index < contents.length) && - (contents[index] == ' ')) { - //spaceCount--; - extraCount++; - index++; - } - int braceCount = 0; - while ((index < contents.length) && (contents[index] != '\n')) { - if (contents[index] == '}') { - braceCount++; - } - index++; - } - - // hitting return on a line with spaces *after* the caret - // can cause trouble. for 0099, was ignoring the case, but this is - // annoying, so in 0122 we're trying to fix that. - /* - if (spaceCount - extraCount > 0) { - spaceCount -= extraCount; - } - */ - spaceCount -= extraCount; - //if (spaceCount < 0) spaceCount = 0; - //System.out.println("extraCount is " + extraCount); - - // now, check to see if the current line contains a } and if so, - // outdent again by indent - //if (braceCount > 0) { - //spaceCount -= 2; - //} - - if (spaceCount < 0) { - // for rev 0122, actually delete extra space - //textarea.setSelectionStart(origIndex + 1); - textarea.setSelectionEnd(textarea.getSelectionStop() - spaceCount); - textarea.setSelectedText("\n"); - } else { - String insertion = "\n" + Editor.EMPTY.substring(0, spaceCount); - textarea.setSelectedText(insertion); - } - - // not gonna bother handling more than one brace - if (braceCount > 0) { - int sel = textarea.getSelectionStart(); - // sel - tabSize will be -1 if start/end parens on the same line - // http://dev.processing.org/bugs/show_bug.cgi?id=484 - if (sel - tabSize >= 0) { - textarea.select(sel - tabSize, sel); - String s = Editor.EMPTY.substring(0, tabSize); - // if these are spaces that we can delete - if (textarea.getSelectedText().equals(s)) { - textarea.setSelectedText(""); - } else { - textarea.select(sel, sel); - } - } - } - } else { - // Enter/Return was being consumed by somehow even if false - // was returned, so this is a band-aid to simply fire the event again. - // http://dev.processing.org/bugs/show_bug.cgi?id=1073 - textarea.setSelectedText(String.valueOf(c)); - } - // mark this event as already handled (all but ignored) - event.consume(); - return true; - - case '}': - if (autoIndent) { - // first remove anything that was there (in case this multiple - // characters are selected, so that it's not in the way of the - // spaces for the auto-indent - if (textarea.getSelectionStart() != textarea.getSelectionStop()) { - textarea.setSelectedText(""); - } - - // if this brace is the only thing on the line, outdent - char contents[] = textarea.getText().toCharArray(); - // index to the character to the left of the caret - int prevCharIndex = textarea.getCaretPosition() - 1; - - // backup from the current caret position to the last newline, - // checking for anything besides whitespace along the way. - // if there's something besides whitespace, exit without - // messing any sort of indenting. - int index = prevCharIndex; - boolean finished = false; - while ((index != -1) && (!finished)) { - if (contents[index] == 10) { - finished = true; - index++; - } else if (contents[index] != ' ') { - // don't do anything, this line has other stuff on it - return false; - } else { - index--; - } - } - if (!finished) return false; // brace with no start - int lineStartIndex = index; - - int pairedSpaceCount = calcBraceIndent(prevCharIndex, contents); //, 1); - if (pairedSpaceCount == -1) return false; - - textarea.setSelectionStart(lineStartIndex); - textarea.setSelectedText(Editor.EMPTY.substring(0, pairedSpaceCount)); - - // mark this event as already handled - event.consume(); - return true; - } - break; - } - return false; - } - - -// public boolean keyReleased(KeyEvent event) { -// if (code == KeyEvent.VK_SHIFT) { -// editor.toolbar.setShiftPressed(false); + +// if (event.isAltDown() && code == KeyEvent.VK_T) { +// int line = textarea.getCaretLineNumber(); +// textarea.setActiveLineRange(line, line + 3); // } -// } - - - public boolean keyTyped(KeyEvent event) { - char c = event.getKeyChar(); - - if ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0) { - // The char is not control code when CTRL key pressed? It should be a shortcut. - if (!Character.isISOControl(c)) { - event.consume(); - return true; - } - } - return false; + } - - - /** - * Return the index for the first character on this line. - */ - protected int calcLineStart(int index, char contents[]) { - // backup from the current caret position to the last newline, - // so that we can figure out how far this line was indented - /*int spaceCount = 0;*/ - boolean finished = false; - while ((index != -1) && (!finished)) { - if ((contents[index] == 10) || - (contents[index] == 13)) { - finished = true; - //index++; // maybe ? - } else { - index--; // new - } - } - // add one because index is either -1 (the start of the document) - // or it's the newline character for the previous line - return index + 1; + @Override + public void keyReleased(KeyEvent e) { + // TODO Auto-generated method stub + } - - /** - * Calculate the number of spaces on this line. - */ - protected int calcSpaceCount(int index, char contents[]) { - index = calcLineStart(index, contents); - - int spaceCount = 0; - // now walk forward and figure out how many spaces there are - while ((index < contents.length) && (index >= 0) && - (contents[index++] == ' ')) { - spaceCount++; - } - return spaceCount; - } - - - /** - * Walk back from 'index' until the brace that seems to be - * the beginning of the current block, and return the number of - * spaces found on that line. - */ - protected int calcBraceIndent(int index, char contents[]) { - // now that we know things are ok to be indented, walk - // backwards to the last { to see how far its line is indented. - // this isn't perfect cuz it'll pick up commented areas, - // but that's not really a big deal and can be fixed when - // this is all given a more complete (proper) solution. - int braceDepth = 1; - boolean finished = false; - while ((index != -1) && (!finished)) { - if (contents[index] == '}') { - // aww crap, this means we're one deeper - // and will have to find one more extra { - braceDepth++; - //if (braceDepth == 0) { - //finished = true; - //} - index--; - } else if (contents[index] == '{') { - braceDepth--; - if (braceDepth == 0) { - finished = true; - } - index--; - } else { - index--; - } - } - // never found a proper brace, be safe and don't do anything - if (!finished) return -1; - - // check how many spaces on the line with the matching open brace - //int pairedSpaceCount = calcSpaceCount(index, contents); - //System.out.println(pairedSpaceCount); - return calcSpaceCount(index, contents); - } - - - /** - * Get the character array and blank out the commented areas. - * This hasn't yet been tested, the plan was to make auto-indent - * less gullible (it gets fooled by braces that are commented out). - */ - protected char[] getCleanedContents() { - char c[] = textarea.getText().toCharArray(); - - int index = 0; - while (index < c.length - 1) { - if ((c[index] == '/') && (c[index+1] == '*')) { - c[index++] = 0; - c[index++] = 0; - while ((index < c.length - 1) && - !((c[index] == '*') && (c[index+1] == '/'))) { - c[index++] = 0; - } - - } else if ((c[index] == '/') && (c[index+1] == '/')) { - // clear out until the end of the line - while ((index < c.length) && (c[index] != 10)) { - c[index++] = 0; - } - if (index != c.length) { - index++; // skip over the newline - } - } - } - return c; - } - - /* - protected char[] getCleanedContents() { - char c[] = textarea.getText().toCharArray(); - boolean insideMulti; // multi-line comment - boolean insideSingle; // single line double slash - - //for (int i = 0; i < c.length - 1; i++) { - int index = 0; - while (index < c.length - 1) { - if (insideMulti && (c[index] == '*') && (c[index+1] == '/')) { - insideMulti = false; - index += 2; - } else if ((c[index] == '/') && (c[index+1] == '*')) { - insideMulti = true; - index += 2; - } else if ((c[index] == '/') && (c[index+1] == '/')) { - // clear out until the end of the line - while (c[index] != 10) { - c[index++] = 0; - } - index++; - } - } - } - */ -} +} \ No newline at end of file diff --git a/app/src/processing/app/LastUndoableEditAwareUndoManager.java b/app/src/processing/app/LastUndoableEditAwareUndoManager.java index 0cd678a93..736be42d3 100644 --- a/app/src/processing/app/LastUndoableEditAwareUndoManager.java +++ b/app/src/processing/app/LastUndoableEditAwareUndoManager.java @@ -2,31 +2,36 @@ package processing.app; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; -import javax.swing.undo.UndoManager; -import javax.swing.undo.UndoableEdit; -@SuppressWarnings("serial") -public class LastUndoableEditAwareUndoManager extends UndoManager { +import org.fife.ui.rtextarea.RUndoManager; - private UndoableEdit lastUndoableEdit; +import processing.app.syntax.SketchTextArea; - public LastUndoableEditAwareUndoManager() { - this.lastUndoableEdit = null; +public class LastUndoableEditAwareUndoManager extends RUndoManager { + + private Editor editor; + + public LastUndoableEditAwareUndoManager(SketchTextArea textarea, Editor editor) { + super(textarea); + this.editor = editor; } @Override public synchronized void undo() throws CannotUndoException { - lastUndoableEdit = super.editToBeUndone(); super.undo(); } @Override public synchronized void redo() throws CannotRedoException { - lastUndoableEdit = super.editToBeRedone(); super.redo(); } - - public UndoableEdit getLastUndoableEdit() { - return lastUndoableEdit; + + @Override + public void updateActions() { + super.updateActions(); + editor.undoAction.updateUndoState(); + editor.redoAction.updateRedoState(); } + + } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index d36f72522..74ca35733 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -112,7 +112,7 @@ public class Sketch { for (SketchCode code : data.getCodes()) { if (code.getMetadata() == null) - code.setMetadata(new SketchCodeDocument(code)); + code.setMetadata(new SketchCodeDocument(this, code)); } // set the main file to be the current tab @@ -409,7 +409,7 @@ public class Sketch { return; } ensureExistence(); - data.addCode((new SketchCodeDocument(newFile)).getCode()); + data.addCode((new SketchCodeDocument(this, newFile)).getCode()); } // sort the entries @@ -905,7 +905,7 @@ public class Sketch { } if (codeExtension != null) { - SketchCode newCode = (new SketchCodeDocument(destFile)).getCode(); + SketchCode newCode = (new SketchCodeDocument(this, destFile)).getCode(); if (replacement) { data.replaceCode(newCode); diff --git a/app/src/processing/app/SketchCodeDocument.java b/app/src/processing/app/SketchCodeDocument.java index 857a270ab..dd3054777 100644 --- a/app/src/processing/app/SketchCodeDocument.java +++ b/app/src/processing/app/SketchCodeDocument.java @@ -2,37 +2,47 @@ package processing.app; import java.io.File; +import javax.swing.Action; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.text.Document; +import javax.swing.undo.UndoManager; -public class SketchCodeDocument{ +import org.fife.ui.rtextarea.RTextArea; + +public class SketchCodeDocument implements SketchDocumentProvider, DocumentListener{ private SketchCode code; + private Sketch sketch; private Document document; // Undo Manager for this tab, each tab keeps track of their own Editor.undo // will be set to this object when this code is the tab that's currently the // front. - private LastUndoableEditAwareUndoManager undo = new LastUndoableEditAwareUndoManager(); + private UndoManager undo; // saved positions from last time this tab was used private int selectionStart; private int selectionStop; private int scrollPosition; - public SketchCodeDocument(SketchCode code) { + public SketchCodeDocument(Sketch sketch, SketchCode code) { this.code = code; + this.sketch = sketch; this.code.setMetadata(this); } - public SketchCodeDocument(File file) { + public SketchCodeDocument(Sketch sketch, File file) { this.code = new SketchCode(file, this); + this.sketch = sketch; } - public LastUndoableEditAwareUndoManager getUndo() { + public UndoManager getUndo() { return undo; } - public void setUndo(LastUndoableEditAwareUndoManager undo) { + public void setUndo(UndoManager undo) { this.undo = undo; } @@ -74,6 +84,24 @@ public class SketchCodeDocument{ public void setDocument(Document document) { this.document = document; + document.addDocumentListener(this); } + @Override + public void insertUpdate(DocumentEvent e) { + if(!code.isModified()) sketch.setModified(true); + } + + + @Override + public void removeUpdate(DocumentEvent e) { + if(!code.isModified()) sketch.setModified(true); + } + + @Override + public void changedUpdate(DocumentEvent e) { + // Callback for when styles in the current document change. + // This method is never called. + } + } diff --git a/app/src/processing/app/Theme.java b/app/src/processing/app/Theme.java index 4100bdfc5..9ad4f2aef 100644 --- a/app/src/processing/app/Theme.java +++ b/app/src/processing/app/Theme.java @@ -28,9 +28,11 @@ import java.awt.Font; import java.awt.SystemColor; import java.io.File; +import javax.swing.text.StyleContext; + +import processing.app.helpers.OSUtils; import processing.app.helpers.PreferencesHelper; import processing.app.helpers.PreferencesMap; -import processing.app.syntax.SyntaxStyle; /** * Storage class for theme settings. This was separated from the Preferences @@ -104,17 +106,40 @@ public class Theme { } return font; } + + /** + * Returns the default font for text areas. + * + * @return The default font. + */ + public static final Font getDefaultFont() { - static public SyntaxStyle getStyle(String what) { - String split[] = get("editor." + what + ".style").split(","); + // Use StyleContext to get a composite font for better Asian language + // support; see Sun bug S282887. + StyleContext sc = StyleContext.getDefaultStyleContext(); + Font font = null; - Color color = PreferencesHelper.parseColor(split[0]); + if (OSUtils.isMacOS()) { + // Snow Leopard (1.6) uses Menlo as default monospaced font, + // pre-Snow Leopard used Monaco. + font = sc.getFont("Menlo", Font.PLAIN, 12); + if (!"Menlo".equals(font.getFamily())) { + font = sc.getFont("Monaco", Font.PLAIN, 12); + if (!"Monaco".equals(font.getFamily())) { // Shouldn't happen + font = sc.getFont("Monospaced", Font.PLAIN, 13); + } + } + } + else { + // Consolas added in Vista, used by VS2010+. + font = sc.getFont("Consolas", Font.PLAIN, 13); + if (!"Consolas".equals(font.getFamily())) { + font = sc.getFont("Monospaced", Font.PLAIN, 13); + } + } - String style = split[1]; - boolean bold = style.contains("bold"); - boolean italic = style.contains("italic"); - boolean underlined = style.contains("underlined"); + //System.out.println(font.getFamily() + ", " + font.getName()); + return font; - return new SyntaxStyle(color, italic, bold, underlined); } } diff --git a/app/src/processing/app/syntax/PdeKeywords.java b/app/src/processing/app/syntax/PdeKeywords.java index b221796a5..d3823802a 100644 --- a/app/src/processing/app/syntax/PdeKeywords.java +++ b/app/src/processing/app/syntax/PdeKeywords.java @@ -24,59 +24,55 @@ package processing.app.syntax; -import processing.app.*; +import cc.arduino.contributions.libraries.ContributedLibrary; +import org.fife.ui.rsyntaxtextarea.TokenTypes; +import processing.app.Base; +import processing.app.BaseNoGui; import processing.app.legacy.PApplet; -import java.io.*; -import java.util.*; - -import cc.arduino.contributions.libraries.ContributedLibrary; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.HashMap; -public class PdeKeywords extends CTokenMarker { +public class PdeKeywords { - // lookup table for the TokenMarker subclass, handles coloring - static KeywordMap keywordColoring; + // Value is org.fife.ui.rsyntaxtextarea.TokenTypes + private static HashMap keywords = new HashMap(); - // lookup table that maps keywords to their html reference pages - static Hashtable keywordToReference; + private static HashMap keywordToReference = new HashMap(); - - public PdeKeywords() { - super(false, getKeywords()); + public static HashMap reload() { + keywords.clear(); + keywordToReference.clear(); + return get(); } + public static HashMap get() { - /** - * Handles loading of keywords file. - *

- * Uses getKeywords() method because that's part of the - * TokenMarker classes. - *

- * It is recommended that a # sign be used for comments - * inside keywords.txt. - */ - static public KeywordMap getKeywords() { - if (keywordColoring == null) { + if (keywords.isEmpty()) { try { - keywordColoring = new KeywordMap(false); - keywordToReference = new Hashtable(); - getKeywords(new File(BaseNoGui.getContentFile("lib"), "keywords.txt")); - for (ContributedLibrary lib : Base.getLibraries()) { - File keywords = new File(lib.getInstalledFolder(), "keywords.txt"); - if (keywords.exists()) getKeywords(keywords); + load(new File(BaseNoGui.getContentFile("lib"), "keywords.txt")); + if (Base.getLibraries() != null) { + for (ContributedLibrary lib : Base.getLibraries()) { + File keywords = new File(lib.getInstalledFolder(), "keywords.txt"); + if (keywords.exists()) load(keywords); + } } } catch (Exception e) { Base.showError("Problem loading keywords", - "Could not load keywords.txt,\n" + - "please re-install Arduino.", e); + "Could not load keywords.txt,\n" + + "please re-install Arduino.", e); System.exit(1); } } - return keywordColoring; + + return keywords; } - - static private void getKeywords(File input) throws IOException { + + static private void load(File input) throws Exception { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(input))); @@ -111,7 +107,40 @@ public class PdeKeywords extends CTokenMarker { ((isKey ? Token.KEYWORD1 : Token.LITERAL1) + num); //System.out.println("got " + (isKey ? "keyword" : "literal") + // (num+1) + " for " + keyword); - keywordColoring.add(keyword, id); + + int tokenType = TokenTypes.IDENTIFIER; + + // KEYWORD1 Classes, datatypes, and C++ keywords + // KEYWORD2 Methods and functions + // KEYWORD3 setup and loop functions, as well as the Serial keywords + // LITERAL1 Constants + // LITERAL2 Built-in variables (INPUT,OUTPUT,CHANGE,FALLING) + + switch (id) { + case Token.KEYWORD1: + tokenType = TokenTypes.VARIABLE; + break; + case Token.KEYWORD2: + tokenType = TokenTypes.FUNCTION; + break; + case Token.KEYWORD3: + tokenType = TokenTypes.RESERVED_WORD; + break; + case Token.LITERAL1: + tokenType = TokenTypes.PREPROCESSOR; + break; + case Token.LITERAL2: + tokenType = TokenTypes.RESERVED_WORD_2; + break; + default: + break; + } + + if ("true".equals(keyword) || "false".equals(keyword)) { + tokenType = TokenTypes.LITERAL_BOOLEAN; + } + + keywords.put(keyword, tokenType); } if (pieces.length >= 3) { String htmlFilename = pieces[2].trim(); @@ -128,8 +157,8 @@ public class PdeKeywords extends CTokenMarker { } } - - static public String getReference(String keyword) { - return (String) keywordToReference.get(keyword); + public static String getReference(String keyword) { + if (keywordToReference == null) return null; + return keywordToReference.get(keyword); } } diff --git a/app/src/processing/app/syntax/SketchTextArea.java b/app/src/processing/app/syntax/SketchTextArea.java new file mode 100644 index 000000000..61ee6559b --- /dev/null +++ b/app/src/processing/app/syntax/SketchTextArea.java @@ -0,0 +1,243 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +package processing.app.syntax; + +import java.awt.AWTKeyStroke; +import java.awt.KeyboardFocusManager; +import java.awt.event.KeyEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.event.HyperlinkEvent; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Segment; +import javax.swing.undo.UndoManager; + +import org.fife.ui.rsyntaxtextarea.*; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.focusabletip.FocusableTip; +import org.fife.ui.rtextarea.RUndoManager; + +import processing.app.Base; +import processing.app.BaseNoGui; +import processing.app.EditorLineStatus; +import processing.app.EditorListener; + +/** + * Arduino Sketch code editor based on RSyntaxTextArea (http://fifesoft.com/rsyntaxtextarea) + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 20/04/2015 + * @since 1.6.4 + */ +public class SketchTextArea extends RSyntaxTextArea { + + private final static Logger LOG = Logger.getLogger(SketchTextArea.class.getName()); + + /** The last docTooltip displayed. */ + private FocusableTip docTooltip; + + /** + * The component that tracks the current line number. + */ + protected EditorLineStatus editorLineStatus; + + private EditorListener editorListener; + + public SketchTextArea() { + super(); + installFeatures(); + } + + + protected void installFeatures(){ + FileInputStream defaultXmlInputStream = null; + try { + defaultXmlInputStream = new FileInputStream(new File(BaseNoGui.getContentFile("lib"), "theme/syntax/default.xml")); + Theme theme = Theme.load(defaultXmlInputStream); + theme.apply(this); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (defaultXmlInputStream != null) { + try { + defaultXmlInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + setLinkGenerator(new DocLinkGenerator()); + + fixControlTab(); + installTokenMaker(); + } + + // Removing the default focus traversal keys + // This is because the DefaultKeyboardFocusManager handles the keypress and consumes the event + protected void fixControlTab(){ + KeyStroke ctrlTab = KeyStroke.getKeyStroke("ctrl TAB"); + KeyStroke ctrlShiftTab = KeyStroke.getKeyStroke("ctrl shift TAB"); + + // Remove ctrl-tab from normal focus traversal + Set forwardKeys = new HashSet(this.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); + forwardKeys.remove(ctrlTab); + this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys); + + // Remove ctrl-shift-tab from normal focus traversal + Set backwardKeys = new HashSet(this.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); + backwardKeys.remove(ctrlShiftTab); + } + + + + public void setEditorLineStatus(EditorLineStatus editorLineStatus) { + this.editorLineStatus = editorLineStatus; + } + + @Override + public void select(int selectionStart, int selectionEnd) { + super.select(selectionStart, selectionEnd); + if(editorLineStatus != null) editorLineStatus.set(selectionStart, selectionEnd); + } + + public boolean isSelectionActive() { + return this.getSelectedText() != null; + } + + public void setSelectedText(String text){ + + int old = getTextMode(); + setTextMode(OVERWRITE_MODE); + replaceSelection(text); + setTextMode(old); + + } + + protected void installTokenMaker(){ + AbstractTokenMakerFactory atmf = (AbstractTokenMakerFactory)TokenMakerFactory.getDefaultInstance(); + atmf.putMapping(SYNTAX_STYLE_CPLUSPLUS, "processing.app.syntax.SketchTokenMaker"); + setSyntaxEditingStyle(SYNTAX_STYLE_CPLUSPLUS); + } + + public void processKeyEvent(KeyEvent evt) { + + // this had to be added because the menu key events weren't making it up to the frame. + + switch(evt.getID()) { + case KeyEvent.KEY_TYPED: + if (editorListener != null) editorListener.keyTyped(evt); + break; + case KeyEvent.KEY_PRESSED: + if (editorListener != null) editorListener.keyPressed(evt); + break; + case KeyEvent.KEY_RELEASED: + // inputHandler.keyReleased(evt); + break; + } + + if(!evt.isConsumed()){ + super.processKeyEvent(evt); + } + } + + public void switchDocument(Document document, UndoManager newUndo) { + + // HACK: Dont discard changes on curret UndoManager. + // BUG: https://github.com/bobbylight/RSyntaxTextArea/issues/84 + setUndoManager(null); // bypass reset current undo manager... + + super.setDocument(document); + + setUndoManager((RUndoManager) newUndo); + + // HACK: Complement previous hack (hide code folding on switch) | Drawback: Lose folding state +// if(sketch.getCodeCount() > 1 && textarea.isCodeFoldingEnabled()){ +// textarea.setCodeFoldingEnabled(false); +// textarea.setCodeFoldingEnabled(true); +// } + + + } + + @Override + protected JPopupMenu createPopupMenu() { + JPopupMenu menu = super.createPopupMenu(); + return menu; + } + + @Override + protected void configurePopupMenu(JPopupMenu popupMenu) { + super.configurePopupMenu(popupMenu); + } + + public void getTextLine(int line, Segment segment) { + try { + int offset = getLineStartOffset(line); + int end = getLineEndOffset(line); + getDocument().getText(offset, end - offset, segment); + } catch (BadLocationException e) { + } + } + + public String getTextLine(int line) { + try { + int offset = getLineStartOffset(line); + int end = getLineEndOffset(line); + return getDocument().getText(offset, end - offset); + } catch (BadLocationException e) { + return null; + } + } + + + public void setEditorListener(EditorListener editorListener) { + this.editorListener = editorListener; + } + + private static class DocLinkGenerator implements LinkGenerator{ + + @Override + public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, final int offs) { + + final Token token = textArea.modelToToken(offs); + + final String reference = PdeKeywords.getReference(token.getLexeme()); + + // LOG.fine("reference: " + reference + ", match: " + (token.getType() == TokenTypes.DATA_TYPE || token.getType() == TokenTypes.VARIABLE || token.getType() == TokenTypes.FUNCTION)); + + if(token != null && (reference != null || (token.getType() == TokenTypes.DATA_TYPE || token.getType() == TokenTypes.VARIABLE || token.getType() == TokenTypes.FUNCTION) )){ + + LinkGeneratorResult generatorResult = new LinkGeneratorResult() { + + @Override + public int getSourceOffset() { + return offs; + } + + @Override + public HyperlinkEvent execute() { + + LOG.fine("Open Reference: " + reference); + + Base.showReference("Reference/" + reference); + + return null; + } + }; + + return generatorResult; + } + + return null; + } + }; + +} diff --git a/app/src/processing/app/syntax/SketchTokenMaker.java b/app/src/processing/app/syntax/SketchTokenMaker.java new file mode 100644 index 000000000..75ffb4fe2 --- /dev/null +++ b/app/src/processing/app/syntax/SketchTokenMaker.java @@ -0,0 +1,121 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +package processing.app.syntax; + +import java.util.HashMap; +import java.util.Set; + +import org.fife.ui.rsyntaxtextarea.TokenMap; +import org.fife.ui.rsyntaxtextarea.TokenTypes; +import org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker; + +import processing.app.Base; + +/** + * Controls the syntax highlighting of {@link SketchTextArea} based on the {@link PdeKeywords} + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 20/04/2015 + * @since 1.6.4 + */ +public class SketchTokenMaker extends CPlusPlusTokenMaker { + + static TokenMap extraTokens; + + public SketchTokenMaker() { + extraTokens = getKeywords(); + } + + @Override + public void addToken(char[] array, int start, int end, int tokenType, int startOffset, boolean hyperlink) { + // This assumes all of your extra tokens would normally be scanned as IDENTIFIER. + if (tokenType == TokenTypes.IDENTIFIER || tokenType == TokenTypes.DATA_TYPE) { + int newType = extraTokens.get(array, start, end); + if (newType>-1) { + tokenType = newType; + } + } + super.addToken(array, start, end, tokenType, startOffset, hyperlink); + } + + public static void addKeyword(String keyword, int type) { + extraTokens.put(keyword, type); + } + + public void clear() { + extraTokens = new TokenMap(); + } + + + /** + * Handles loading of keywords file. + *

+ * It is recommended that a # sign be used for comments + * inside keywords.txt. + */ + static public TokenMap getKeywords() { + if (extraTokens == null) { + try { + extraTokens = new TokenMap(false); + + extraTokens.put("setup", TokenTypes.RESERVED_WORD); + extraTokens.put("loop", TokenTypes.RESERVED_WORD); + + extraTokens.put("HIGH", TokenTypes.RESERVED_WORD_2); + extraTokens.put("LOW", TokenTypes.RESERVED_WORD_2); + extraTokens.put("OUTPUT", TokenTypes.RESERVED_WORD_2); + extraTokens.put("INPUT", TokenTypes.RESERVED_WORD_2); + extraTokens.put("INPUT_PULLUP", TokenTypes.RESERVED_WORD_2); + + extraTokens.put("CHANGE", TokenTypes.RESERVED_WORD_2); + extraTokens.put("FALLING", TokenTypes.RESERVED_WORD_2); + extraTokens.put("RISING", TokenTypes.RESERVED_WORD_2); + + extraTokens.put("PI", TokenTypes.LITERAL_NUMBER_FLOAT); + extraTokens.put("HALF_PI", TokenTypes.LITERAL_NUMBER_FLOAT); + extraTokens.put("TWO_PI", TokenTypes.LITERAL_NUMBER_FLOAT); + extraTokens.put("DEG_TO_RAD", TokenTypes.LITERAL_NUMBER_FLOAT); + extraTokens.put("RAD_TO_DEG", TokenTypes.LITERAL_NUMBER_FLOAT); + extraTokens.put("EULER", TokenTypes.LITERAL_NUMBER_FLOAT); + + // Print. + extraTokens.put("DEC", TokenTypes.RESERVED_WORD_2); + extraTokens.put("HEX", TokenTypes.RESERVED_WORD_2); + extraTokens.put("OCT", TokenTypes.RESERVED_WORD_2); + extraTokens.put("BIN", TokenTypes.RESERVED_WORD_2); + + extraTokens.put("true", TokenTypes.LITERAL_BOOLEAN); + extraTokens.put("false", TokenTypes.LITERAL_BOOLEAN); + + // Related IO + extraTokens.put("pinMode", TokenTypes.FUNCTION); + extraTokens.put("digitalWrite", TokenTypes.FUNCTION); + extraTokens.put("digitalRead", TokenTypes.FUNCTION); + extraTokens.put("analogRead", TokenTypes.FUNCTION); + extraTokens.put("analogReference", TokenTypes.FUNCTION); + extraTokens.put("analogWrite", TokenTypes.FUNCTION); + + // Others. + extraTokens.put("DIGITAL", TokenTypes.RESERVED_WORD_2); + extraTokens.put("ANALOG", TokenTypes.RESERVED_WORD_2); + + // force load references. + PdeKeywords.reload(); + + + HashMap keywords = PdeKeywords.get(); + Set keys = keywords.keySet(); + for (String key : keys) { + extraTokens.put(key, keywords.get(key)); + } + + } catch (Exception e) { + Base.showError("Problem loading keywords", + "Could not load keywords.txt,\n" + + "please re-install Arduino.", e); + System.exit(1); + } + } + return extraTokens; + } + +} diff --git a/app/src/processing/app/tools/DiscourseFormat.java b/app/src/processing/app/tools/DiscourseFormat.java index a4a381c5a..75240cb69 100644 --- a/app/src/processing/app/tools/DiscourseFormat.java +++ b/app/src/processing/app/tools/DiscourseFormat.java @@ -25,11 +25,14 @@ package processing.app.tools; import java.awt.*; import java.awt.datatransfer.*; + +import javax.swing.text.BadLocationException; import javax.swing.text.Segment; +import org.fife.ui.rsyntaxtextarea.Token; + import processing.app.*; import processing.app.syntax.*; -import processing.app.legacy.PApplet; /** * Format for Discourse Tool @@ -44,6 +47,8 @@ import processing.app.legacy.PApplet; *

* Updated for 0144 to only format the selected lines. *

+ * Updated for 1.5.8 - Simplification, using RSyntaxTextArea TokenImpl formatter (08 dec 2014 - Ricardo JL Rufino) + *

* Notes from the original source: * Discourse.java This is a dirty-mix source. * NOTE that: No macs and no keyboard. Unreliable source. @@ -54,7 +59,7 @@ public class DiscourseFormat { private Editor editor; // JTextArea of the actual Editor - private JEditTextArea textarea; + private SketchTextArea textarea; private boolean html; @@ -78,10 +83,16 @@ public class DiscourseFormat { StringBuilder cf = new StringBuilder(html ? "

\n" : "[code]\n");
 
     int selStart = textarea.getSelectionStart();
-    int selStop = textarea.getSelectionStop();
+    int selStop = textarea.getSelectionEnd();
 
-    int startLine = textarea.getSelectionStartLine();
-    int stopLine = textarea.getSelectionStopLine();
+    int startLine;
+    int stopLine;
+    try {
+      startLine = textarea.getLineOfOffset(selStart);
+      stopLine = textarea.getLineOfOffset(selStop);
+    } catch (BadLocationException e) {
+      return;
+    }
 
     // If no selection, convert all the lines
     if (selStart == selStop) {
@@ -89,8 +100,11 @@ public class DiscourseFormat {
       stopLine = textarea.getLineCount() - 1;
     } else {
       // Make sure the selection doesn't end at the beginning of the last line
-      if (textarea.getLineStartOffset(stopLine) == selStop) {
-        stopLine--;
+      try {
+        if (textarea.getLineStartOffset(stopLine) == selStop) {
+          stopLine--;
+        }
+      } catch (BadLocationException e) {
       }
     }
 
@@ -139,23 +153,15 @@ public class DiscourseFormat {
   public void appendFormattedLine(StringBuilder cf, int line) {
     Segment segment = new Segment();
 
-    TextAreaPainter painter = textarea.getPainter();
-    TokenMarker tokenMarker = textarea.getTokenMarker();
-
-    // Use painter's cached info for speed
-//    FontMetrics fm = painter.getFontMetrics();
-
     // get line text from parent text area
-    textarea.getLineText(line, segment);
-
+    textarea.getTextLine(line, segment);
+    
     char[] segmentArray = segment.array;
-    int limit = segment.getEndIndex();
     int segmentOffset = segment.offset;
     int segmentCount = segment.count;
 //    int width = 0;
 
-    // If syntax coloring is disabled, do simple translation
-    if (tokenMarker == null) {
+    if (!html) {
       for (int j = 0; j < segmentCount; j++) {
         char c = segmentArray[j + segmentOffset];
         appendToHTML(c, cf);
@@ -169,82 +175,19 @@ public class DiscourseFormat {
       }
 
     } else {
-      // If syntax coloring is enabled, we have to do this
-      // because tokens can vary in width
-      Token tokens;
-      if ((painter.getCurrentLineIndex() == line) &&
-          (painter.getCurrentLineTokens() != null)) {
-        tokens = painter.getCurrentLineTokens();
-
-      } else {
-        painter.setCurrentLineIndex(line);
-        painter.setCurrentLineTokens(tokenMarker.markTokens(segment, line));
-        tokens = painter.getCurrentLineTokens();
-      }
-
-      int offset = 0;
-//      Font defaultFont = painter.getFont();
-      SyntaxStyle[] styles = painter.getStyles();
-
-      for (;;) {
-        byte id = tokens.id;
-        if (id == Token.END) {
-          char c = segmentArray[segmentOffset + offset];
-          if (segmentOffset + offset < limit) {
-            appendToHTML(c, cf);
-          } else {
-            cf.append('\n');
-          }
-          return; // cf.toString();
-        }
-        if (id == Token.NULL) {
-//          fm = painter.getFontMetrics();
-        } else {
-          // Place open tags []
-          if (html) {
-            cf.append("");
-          }
-
-          if (html && styles[id].isBold())
-            cf.append("");
-
-//          fm = styles[id].getFontMetrics(defaultFont);
-        }
-        int length = tokens.length;
-
-        for (int j = 0; j < length; j++) {
-          char c = segmentArray[segmentOffset + offset + j];
-          if (offset == 0 && c == ' ') {
-            // Works on Safari but not Camino 1.6.3 or Firefox 2.x on OS X.
-            cf.append(html ? " " : '\u00A0');  //  
-//            if ((j % 2) == 1) {
-//              cf.append("[b]\u00A0[/b]");
-//            } else {
-//              cf.append(' ');
-//            }
-          } else {
-            appendToHTML(c, cf);
-          }
-          // Place close tags [/]
-          if (html && j == (length - 1) && id != Token.NULL && styles[id].isBold())
-            cf.append("");
-          if (html && j == (length - 1) && id != Token.NULL)
-            cf.append("");
-//          int charWidth;
-//          if (c == '\t') {
-//            charWidth = (int) painter
-//              .nextTabStop(width, offset + j)
-//              - width;
-//          } else {
-//            charWidth = fm.charWidth(c);
-//          }
-//          width += charWidth;
-        }
-        offset += length;
-        tokens = tokens.next;
+      
+      Token tokenList = textarea.getTokenListForLine(line);
+      
+      while(tokenList != null){
+        if(tokenList.getType() == Token.NULL){
+          cf.append('\n');
+        }else if(tokenList.isPaintable()){
+          tokenList.appendHTMLRepresentation(cf, textarea, false);
+        }
+        
+        tokenList = tokenList.getNextToken();
       }
+  
     }
   }
 }