diff --git a/app/.classpath b/app/.classpath index aca9311551b..b789e9df072 100644 --- a/app/.classpath +++ b/app/.classpath @@ -21,5 +21,10 @@ + + + + + diff --git a/app/build.xml b/app/build.xml index 31f52adf216..8ef590319f1 100644 --- a/app/build.xml +++ b/app/build.xml @@ -7,6 +7,7 @@ + diff --git a/app/lib/cplus-libparser-0.0.1.jar b/app/lib/cplus-libparser-0.0.1.jar new file mode 100755 index 00000000000..6cda525a7ef Binary files /dev/null and b/app/lib/cplus-libparser-0.0.1.jar differ diff --git a/app/lib/jsoup-htmlparser-1.8.1.jar b/app/lib/jsoup-htmlparser-1.8.1.jar new file mode 100755 index 00000000000..ae717d450e8 Binary files /dev/null and b/app/lib/jsoup-htmlparser-1.8.1.jar differ diff --git a/app/lib/rsyntax-autocomplete-2.6.0-SNAPSHOT.jar b/app/lib/rsyntax-autocomplete-2.6.0-SNAPSHOT.jar new file mode 100755 index 00000000000..12beccfef93 Binary files /dev/null and b/app/lib/rsyntax-autocomplete-2.6.0-SNAPSHOT.jar differ diff --git a/app/lib/rsyntaxtextarea-2.6.0-SNAPSHOT.jar b/app/lib/rsyntaxtextarea-2.6.0-SNAPSHOT.jar new file mode 100755 index 00000000000..2d6b2d20be7 Binary files /dev/null and b/app/lib/rsyntaxtextarea-2.6.0-SNAPSHOT.jar differ diff --git a/app/run-linux.launch b/app/run-linux.launch index 9ad239a8b91..d8f10d77091 100644 --- a/app/run-linux.launch +++ b/app/run-linux.launch @@ -6,15 +6,6 @@ - - - - - - - - - diff --git a/app/src/ArduinoIDE.java b/app/src/ArduinoIDE.java new file mode 100755 index 00000000000..2afd436ae74 --- /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 c92d1c5cd1a..62f3632605e 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -1,15 +1,17 @@ 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 { @@ -55,13 +57,17 @@ public void run() { 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/Base.java b/app/src/processing/app/Base.java index 3c6c6e8c95c..c8d7d84e1ee 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -27,18 +27,20 @@ import java.io.*; import java.util.*; import java.util.List; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.*; +import br.com.criativasoft.cpluslibparser.LibraryIndex; import cc.arduino.packages.DiscoveryManager; +import cc.arduino.packages.autocomplete.SketchCompletionProvider; import processing.app.debug.TargetBoard; import processing.app.debug.TargetPackage; import processing.app.debug.TargetPlatform; -import processing.app.helpers.CommandlineParser; -import processing.app.helpers.FileUtils; -import processing.app.helpers.GUIUserNotifier; -import processing.app.helpers.OSUtils; -import processing.app.helpers.PreferencesMap; +import processing.app.helpers.*; import processing.app.helpers.filefilters.OnlyDirs; import processing.app.helpers.filefilters.OnlyFilesWithExtension; import processing.app.javax.swing.filechooser.FileNameExtensionFilter; @@ -89,6 +91,7 @@ public class Base { static public void main(String args[]) throws Exception { BaseNoGui.initLogger(); + initLogger(); BaseNoGui.notifier = new GUIUserNotifier(); @@ -166,6 +169,34 @@ static public void main(String args[]) throws Exception { new Base(args); } + + static public void initLogger() { + Handler consoleHandler = new ConsoleLogger(); + consoleHandler.setLevel(Level.ALL); + consoleHandler.setFormatter(new LogFormatter("%1$tl:%1$tM:%1$tS [%4$7s] %2$s: %5$s%n")); + + Logger globalLogger = Logger.getGlobal(); + globalLogger.setLevel(consoleHandler.getLevel()); + + // Remove default + Handler[] handlers = globalLogger.getHandlers(); + for(Handler handler : handlers) { + globalLogger.removeHandler(handler); + } + Logger root = Logger.getLogger(""); + handlers = root.getHandlers(); + for(Handler handler : handlers) { + root.removeHandler(handler); + } + + globalLogger.addHandler(consoleHandler); + + Logger.getLogger(SketchCompletionProvider.class.getPackage().getName()).setParent(globalLogger); + Logger.getLogger(LibraryIndex.class.getPackage().getName()).setParent(globalLogger); + Logger.getLogger(Base.class.getPackage().getName()).setParent(globalLogger); + + } + static protected void setCommandLine() { commandLine = true; @@ -1855,6 +1886,14 @@ static public void setIcon(Frame frame) { // don't use the low-res icon on Mac OS X; the window should // already have the right icon from the .app file. if (OSUtils.isMacOS()) return; + + // don't use the low-res icon on Linux + if (OSUtils.isLinux()){ + String current = System.getProperty("user.dir"); + Image image = Toolkit.getDefaultToolkit().createImage(current + "/lib/arduino.png"); + frame.setIconImage(image); + return; + } Image image = Toolkit.getDefaultToolkit().createImage(PApplet.ICON_IMAGE); frame.setIconImage(image); diff --git a/app/src/processing/app/CaretAwareUndoableEdit.java b/app/src/processing/app/CaretAwareUndoableEdit.java index ba8e67d8570..d9b1349a54d 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 bd0d6c75c11..252d6776942 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -22,43 +22,79 @@ package processing.app; -import cc.arduino.packages.MonitorFactory; - -import com.jcraft.jsch.JSchException; - -import processing.app.debug.*; -import processing.app.forms.PasswordAuthorizationDialog; -import processing.app.helpers.OSUtils; -import processing.app.helpers.PreferencesMapException; -import processing.app.legacy.PApplet; -import processing.app.syntax.*; -import processing.app.tools.*; import static processing.app.I18n._; import java.awt.*; -import java.awt.datatransfer.*; -import java.awt.event.*; -import java.awt.print.*; -import java.io.*; -import java.net.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.print.PageFormat; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.ConnectException; +import java.net.URL; +import java.net.URLClassLoader; import java.util.*; import java.util.List; -import java.util.zip.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import javax.swing.*; import javax.swing.event.*; -import javax.swing.text.*; -import javax.swing.undo.*; - +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; +import javax.swing.undo.CannotRedoException; +import javax.swing.undo.CannotUndoException; +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoManager; + +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit; +import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rtextarea.Gutter; +import org.fife.ui.rtextarea.RTextScrollPane; +import org.fife.ui.rtextarea.RUndoManager; + +import processing.app.debug.RunnerException; +import processing.app.debug.RunnerListener; +import processing.app.forms.PasswordAuthorizationDialog; +import processing.app.helpers.OSUtils; +import processing.app.helpers.PreferencesMapException; +import processing.app.legacy.PApplet; +import processing.app.syntax.PdeKeywords; +import processing.app.syntax.SketchTextArea; +import processing.app.syntax.SyntaxErrorMarker; +import processing.app.syntax.TokenErrorMarker; +import processing.app.tools.DiscourseFormat; +import processing.app.tools.MenuScroller; +import processing.app.tools.Tool; +import br.com.criativasoft.cpluslibparser.LibraryIndex; +import br.com.criativasoft.cpluslibparser.metadata.TElementLocation; +import br.com.criativasoft.cpluslibparser.metadata.TError; +import br.com.criativasoft.cpluslibparser.metadata.TLibrary; import cc.arduino.packages.BoardPort; +import cc.arduino.packages.MonitorFactory; import cc.arduino.packages.Uploader; +import cc.arduino.packages.autocomplete.CompletionType; +import cc.arduino.packages.autocomplete.CompletionsRenderer; +import cc.arduino.packages.autocomplete.SketchCompletionProvider; import cc.arduino.packages.uploaders.SerialUploader; +import com.jcraft.jsch.JSchException; + /** * Main editor panel for the Processing Development Environment. */ @SuppressWarnings("serial") -public class Editor extends JFrame implements RunnerListener { +public class Editor extends JFrame implements RunnerListener, HyperlinkListener { Base base; @@ -123,11 +159,12 @@ public class Editor extends JFrame implements RunnerListener { Sketch sketch; EditorLineStatus lineStatus; + SyntaxErrorMarker errorMarker; //JEditorPane editorPane; - JEditTextArea textarea; - EditorListener listener; + SketchTextArea textarea; + RTextScrollPane scrollPane; // runtime information and window placement Point sketchWindowLocation; @@ -145,9 +182,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; @@ -183,6 +217,11 @@ public void windowClosing(WindowEvent e) { public void windowActivated(WindowEvent e) { // System.err.println("activate"); // not coming through base.handleActivated(Editor.this); + + if(sketch != null){ + LibraryIndex.setContextCache(sketch.getLibraryCacheContext()); + } + // re-add the sub-menus that are shared by all windows fileMenu.insert(sketchbookMenu, 2); fileMenu.insert(examplesMenu, 3); @@ -234,10 +273,8 @@ public void windowDeactivated(WindowEvent e) { 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(); @@ -255,9 +292,19 @@ public void windowDeactivated(WindowEvent e) { 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 @@ -283,7 +330,7 @@ public void windowDeactivated(WindowEvent e) { // 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 @@ -442,32 +489,32 @@ protected void applyPreferences() { saveMenuItem.setEnabled(!external); saveAsMenuItem.setEnabled(!external); - textarea.setDisplayLineNumbers(Preferences.getBoolean("editor.linenumbers")); + textarea.setMarginLineEnabled(Preferences.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); + //Color color = Theme.getColor("editor.bgcolor"); + // textarea.setBackground(color); // NOTE use themes... + // FIXME: Ao voltar o normal está com probleas... !!! boolean highlight = Preferences.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(Preferences.getFont("editor.font")); + textarea.setFont(Preferences.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) @@ -887,7 +934,44 @@ protected String findClassInZipFile(String base, File file) { return null; } + + protected SketchTextArea createTextArea(){ + SketchTextArea textArea = new SketchTextArea(); + textArea.addHyperlinkListener(this); + textArea.requestFocusInWindow(); + textArea.addParser(new TokenErrorMarker(textArea)); + textArea.setMarkOccurrences(true); + textArea.setCodeFoldingEnabled(Preferences.getBoolean("editor.codefolding")); + textArea.setAntiAliasingEnabled(Preferences.getBoolean("editor.antialias")); +// textArea.setClearWhitespaceLinesEnabled(false); + textArea.setTabsEmulated(Preferences.getBoolean("editor.tabs.expand")); + textArea.setTabSize(Preferences.getInteger("editor.tabs.size")); + textArea.addKeyListener(new EditorListener(this)); + + ToolTipManager.sharedInstance().registerComponent(textArea); + + configurePopupMenu(textArea); + return textArea; + } + /** + * Called when a hyperlink is clicked in the text area. + * + * @param e The event. + */ + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) { + URL url = e.getURL(); + if (url==null) { + UIManager.getLookAndFeel().provideErrorFeedback(null); + } + else { + JOptionPane.showMessageDialog(this, + "URL clicked:\n" + url.toString()); + } + } + } + protected JMenuItem createToolMenuItem(String className) { try { Class toolClass = Class.forName(className); @@ -1271,17 +1355,6 @@ public void actionPerformed(ActionEvent e) { }); 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.setFindText( getSelectedText() ); - } - }); - menu.add(item); - return menu; } @@ -1331,39 +1404,27 @@ public UndoAction() { public void actionPerformed(ActionEvent e) { try { - undo.undo(); + textarea.undoLastAction(); } 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); undoItem.setText(undo.getUndoPresentationName()); putValue(Action.NAME, undo.getUndoPresentationName()); - if (sketch != null) { - sketch.setModified(true); // 0107 - } } else { this.setEnabled(false); undoItem.setEnabled(false); undoItem.setText(_("Undo")); putValue(Action.NAME, "Undo"); - if (sketch != null) { - sketch.setModified(false); // 0107 - } } } } @@ -1377,20 +1438,16 @@ public RedoAction() { public void actionPerformed(ActionEvent e) { try { - undo.redo(); + textarea.redoLastAction(); } 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()); @@ -1446,13 +1503,13 @@ public Sketch getSketch() { /** - * Get the JEditTextArea object for use (not recommended). This should only + * Get the PdeTextArea 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 PdeTextArea. */ - public JEditTextArea getTextArea() { + public SketchTextArea getTextArea() { return textarea; } @@ -1469,7 +1526,11 @@ public String getText() { * 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; + } } @@ -1477,20 +1538,10 @@ public String getText(int start, int stop) { * 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 @@ -1516,15 +1567,10 @@ public String getSelectedText() { 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); } @@ -1560,7 +1606,7 @@ public int getSelectionStart() { * Get the end point of the current selection. */ public int getSelectionStop() { - return textarea.getSelectionStop(); + return textarea.getSelectionEnd(); } @@ -1568,26 +1614,24 @@ public int getSelectionStop() { * Get text for a specified line. */ public String getLineText(int line) { - return textarea.getLineText(line); + try { + return textarea.getText(textarea.getLineStartOffset(line), textarea.getLineEndOffset(line)); + } catch (BadLocationException e) { + return ""; + } } - /** - * 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(); - } - /** * 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; + } } @@ -1595,7 +1639,11 @@ public int getLineStartOffset(int line) { * 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; + } } @@ -1607,29 +1655,8 @@ public int 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(); } @@ -1640,15 +1667,12 @@ public int getScrollPosition() { * 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 { @@ -1656,35 +1680,40 @@ protected void setCode(SketchCodeDocument codeDoc) { } 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(e.getEdit()); - - } else if (undo != null) { - undo.addEdit(new CaretAwareUndoableEdit(e.getEdit(), textarea)); - 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) + // Drawback: When using multiple tabs, the code folding and some markers will not work 100% + // 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(); + } + }); + } @@ -1696,7 +1725,6 @@ public void undoableEditHappened(UndoableEditEvent e) { */ public void handleCut() { textarea.cut(); - sketch.setModified(true); } @@ -1723,7 +1751,6 @@ protected void handleHTMLCopy() { */ public void handlePaste() { textarea.paste(); - sketch.setModified(true); } @@ -1733,106 +1760,71 @@ public void handlePaste() { public void handleSelectAll() { textarea.selectAll(); } + + /** + * 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(); + } 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(); + + Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaToggleCommentAction); + action.actionPerformed(null); + } protected void handleIndentOutdent(boolean indent) { - int tabSize = Preferences.getInteger("editor.tabs.size"); - String tabString = Editor.EMPTY.substring(0, tabSize); - - startCompoundEdit(); - - 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(indent){ + + int caretPosition = textarea.getCaretPosition(); + boolean noSelec = !textarea.isSelectionActive(); + + // 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 Preferences.getBoolean("editor.external"); + } + protected String getCurrentKeyword() { String text = ""; if (textarea.getSelectedText() != null) @@ -1932,13 +1924,15 @@ public BuildHandler(boolean verbose) { @Override public void run() { try { + textarea.removeAllLineHighlights(); + errorMarker.removeAll(); sketch.prepare(); sketch.build(verbose); statusNotice(_("Done compiling.")); - } catch (PreferencesMapException e) { - statusError(I18n.format( - _("Error while compiling: missing '{0}' configuration parameter"), - e.getMessage())); +// } catch (PreferencesMapException e) { +// statusError(I18n.format( +// _("Error while compiling: missing '{0}' configuration parameter"), +// e.getMessage())); } catch (Exception e) { status.unprogress(); statusError(e); @@ -2111,7 +2105,7 @@ protected void handleOpenUnchecked(File file, int codeIndex, sketch.setCurrentCode(codeIndex); textarea.select(selStart, selStop); - textarea.setScrollPosition(scrollPos); + scrollPane.getVerticalScrollBar().setValue(scrollPos); } @@ -2197,6 +2191,18 @@ protected boolean handleOpenInternal(File sketchFile) { try { sketch = new Sketch(this, file); + + LibraryIndex.setContextCache(sketch.getLibraryCacheContext()); + + // This needs to be after the ' LibraryIndex.setContextCache' for listeners work properly (on SketchCompletionProvider) + textarea.setupAutoComplete(new SketchCompletionProvider(sketch)); + + // This needs to be after the ' LibraryIndex.setContextCache' for listeners work properly + if(errorMarker == null){ + errorMarker = new SyntaxErrorMarker(textarea); + textarea.addParser(errorMarker); + } + } catch (IOException e) { Base.showWarning(_("Error"), _("Could not create the sketch."), e); return false; @@ -2236,6 +2242,7 @@ protected boolean handleOpenInternal(File sketchFile) { public boolean handleSave(boolean immediately) { //stopRunner(); handleStop(); // 0136 + textarea.removeAllLineHighlights(); if (untitled) { return handleSaveAs(); @@ -2606,9 +2613,9 @@ public void handlePrint() { } 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()); @@ -2660,12 +2667,13 @@ public void statusError(Exception e) { } if (re.hasCodeLine()) { int line = re.getCodeLine(); + // subtract one from the end so that the \n ain't included if (line >= textarea.getLineCount()) { // 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--; @@ -2674,8 +2682,30 @@ public void statusError(Exception e) { 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)); + + TLibrary metadata = sketch.getSketchMetadata(); + int startOffset = textarea.getLineStartOffset(line); + int endOffset = textarea.getLineEndOffset(line); + + TError error = new TError(re.getMessage(), TError.COMPILER_ERROR); + error.setLocation(new TElementLocation(startOffset, endOffset - startOffset)); + errorMarker.addError(error); + textarea.forceReparsing(errorMarker); + + if(metadata != null){ + metadata.getErrors().add(error); + } + + textarea.setCaretPosition(textarea.getLineStartOffset(line)); + + // textarea.add + + } catch (BadLocationException e1) { + e1.printStackTrace(); + } } } } @@ -2723,149 +2753,78 @@ protected void onBoardOrPortChange() { 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; - - private String clickedURL; - - public TextAreaPopup() { - openURLItem = new JMenuItem(_("Open URL")); - openURLItem.addActionListener(new ActionListener() { + + protected void configurePopupMenu(final SketchTextArea textarea){ + + JPopupMenu menu = textarea.getPopupMenu(); + + menu.addSeparator(); + + + JMenuItem item = createToolMenuItem("cc.arduino.packages.formatter.AStyle"); + item.setName("menuToolsAutoFormat"); + + menu.add(item); + + item = new JMenuItem(_("Copy for Forum")); + item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - Base.openURL(clickedURL); + handleDiscourseCopy(); } }); - add(openURLItem); - - 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); + menu.add(item); - item = new JMenuItem(_("Select All")); - item.addActionListener(new ActionListener() { + item = new JMenuItem(_("Copy as HTML")); + item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleSelectAll(); + handleHTMLCopy(); } }); - add(item); - - addSeparator(); - - item = new JMenuItem(_("Comment/Uncomment")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); - } - }); - add(item); - - item = new JMenuItem(_("Increase Indent")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); - } - }); - add(item); - - item = new JMenuItem(_("Decrease Indent")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(false); - } + menu.add(item); + + final JMenuItem referenceItem = new JMenuItem(_("Find in Reference")); + referenceItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleFindReference(); + } }); - add(item); - - addSeparator(); - - 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); + menu.add(referenceItem); + + final JMenuItem openURLItem = new JMenuItem(_("Open URL")); + openURLItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.out.println("e.getActionCommand() -> " + e.getActionCommand()); + Base.openURL(e.getActionCommand()); } - - if (textarea.isSelectionActive()) { - cutItem.setEnabled(true); - copyItem.setEnabled(true); - discourseItem.setEnabled(true); - - } else { - cutItem.setEnabled(false); - copyItem.setEnabled(false); - discourseItem.setEnabled(false); + }); + menu.add(openURLItem); + + + menu.addPopupMenuListener(new PopupMenuListener() { + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + String referenceFile = PdeKeywords.getReference(getCurrentKeyword()); + referenceItem.setEnabled(referenceFile != null); + + int offset = textarea.getCaretPosition(); + Token token = RSyntaxUtilities.getTokenAtOffset(textarea, offset); + if(token != null && token.isHyperlink()){ + openURLItem.setEnabled(true); + openURLItem.setActionCommand(token.getLexeme()); + }else{ + openURLItem.setEnabled(false); + } + } - - referenceFile = PdeKeywords.getReference(getCurrentKeyword()); - referenceItem.setEnabled(referenceFile != null); - - super.show(component, x, y); - } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {} + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { } + }); + } + } diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index ba96382a55f..5095da79114 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -153,16 +153,16 @@ public void paintComponent(Graphics screen) { Graphics g = offscreen.getGraphics(); if (font == null) { - font = Theme.getFont("header.text.font"); + font = Theme.getDefaultFont(); // Get optimal font. + if(font == null) font = Theme.getFont("header.text.font"); } + g.setFont(font); // need to set this each time through metrics = g.getFontMetrics(); fontAscent = metrics.getAscent(); //} - //Graphics2D g2 = (Graphics2D) g; - //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - // RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + Graphics2D g2 = (Graphics2D) g; // set the background for the offscreen g.setColor(backgroundColor); @@ -182,9 +182,8 @@ public void paintComponent(Graphics screen) { code.getPrettyName() : code.getFileName(); // if modified, add the li'l glyph next to the name - String text = " " + codeName + (code.isModified() ? " \u00A7" : " "); + String text = " " + codeName + " "; - Graphics2D g2 = (Graphics2D) g; int textWidth = (int) font.getStringBounds(text, g2.getFontRenderContext()).getWidth(); @@ -207,7 +206,16 @@ public void paintComponent(Graphics screen) { g.setColor(textColor[state]); int baseline = (sizeH + fontAscent) / 2; //g.drawString(sketch.code[i].name, textLeft, baseline); + + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g.drawString(text, textLeft, baseline); + + if(code.isModified()){ + g.setColor(Color.RED); + g.drawString("*", textLeft - 3, baseline); + } g.drawImage(pieces[state][RIGHT], x, 0, null); x += PIECE_WIDTH - 1; // overlap by 1 pixel diff --git a/app/src/processing/app/EditorLineStatus.java b/app/src/processing/app/EditorLineStatus.java old mode 100644 new mode 100755 index 8420ddd4c20..c0d7c47da16 --- 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,22 @@ 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.PreferencesMap; -import processing.app.syntax.JEditTextArea; +import processing.app.helpers.OSUtils; +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 +53,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"); @@ -72,6 +72,7 @@ public EditorLineStatus(JEditTextArea textarea) { //linestatus.color = #FFFFFF } + public void set(int newStart, int newStop) { if ((newStart == start) && (newStop == stop)) return; @@ -94,10 +95,11 @@ public void set(int newStart, int newStop) { repaint(); } + public void paintComponent(Graphics g) { - if (name == "" && serialport == "") { - PreferencesMap boardPreferences = Base.getBoardPreferences(); - if (boardPreferences != null) + if (name=="" && serialport=="") { + Map boardPreferences = Base.getBoardPreferences(); + if (boardPreferences!=null) setBoardName(boardPreferences.get("name")); else setBoardName("-"); @@ -124,13 +126,8 @@ public void paintComponent(Graphics g) { } } - public void setBoardName(String name) { - this.name = name; - } - - public void setSerialPort(String serialport) { - this.serialport = serialport; - } + public void setBoardName(String name) { this.name = name; } + public void setSerialPort(String serialport) { this.serialport = serialport; } public Dimension getPreferredSize() { return new Dimension(300, high); diff --git a/app/src/processing/app/EditorListener.java b/app/src/processing/app/EditorListener.java old mode 100644 new mode 100755 index c2d2fe600a7..47aa899e8e0 --- a/app/src/processing/app/EditorListener.java +++ b/app/src/processing/app/EditorListener.java @@ -1,636 +1,104 @@ -/* -*- 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 javax.swing.text.BadLocationException; +import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.rsyntaxtextarea.Token; -/** - * 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 { - private Editor editor; - private JEditTextArea textarea; - - private boolean externalEditor; - private boolean tabsExpand; - private boolean tabsIndent; - private int tabSize; - private String tabString; - private boolean autoIndent; +import processing.app.syntax.SketchTextArea; +import cc.arduino.packages.autocomplete.SketchCompletionProvider; -// 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 class EditorListener implements KeyListener { + + private Editor editor; + + 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 = Preferences.getBoolean("editor.tabs.expand"); - //tabsIndent = Preferences.getBoolean("editor.tabs.indent"); - tabSize = Preferences.getInteger("editor.tabs.size"); - tabString = Editor.EMPTY.substring(0, tabSize); - autoIndent = Preferences.getBoolean("editor.indent"); - externalEditor = Preferences.getBoolean("editor.external"); + @Override + public void keyTyped(KeyEvent e) { + // TODO Auto-generated method stub } + @Override + public void keyPressed(KeyEvent event) { + + SketchTextArea textarea = editor.getTextArea(); + + if (!textarea.isEditable()) return; + + Sketch sketch = editor.getSketch(); - //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 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(); - - Sketch sketch = editor.getSketch(); - + + // 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; + + // Generate New Variable + if (event.isAltDown() && code == KeyEvent.VK_ENTER) { + + int line = textarea.getCaretLineNumber(); + + Token tokenListForLine = textarea.getTokenListForLine(line); + int start = RSyntaxUtilities.getNextImportantToken(tokenListForLine, textarea, line).getOffset(); + int end = textarea.getLineEndOffsetOfCurrentLine(); + + try { + String expression = textarea.getText(start, end - start); + SketchCompletionProvider provider = textarea.getCompletionProvider(); + provider.generateNewVariableFor(expression, start); + + + + } catch (BadLocationException e) {} + } - 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 old mode 100644 new mode 100755 index 0cd678a935b..736be42d39b --- a/app/src/processing/app/LastUndoableEditAwareUndoManager.java +++ b/app/src/processing/app/LastUndoableEditAwareUndoManager.java @@ -2,31 +2,36 @@ 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 8a32717f712..c122b02c1e3 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -42,21 +42,13 @@ /** * Stores information about files in the current sketch */ -public class Sketch { - static private File tempBuildFolder; +public class Sketch extends BaseSketch { + static private File tempBuildFolder; + private Editor editor; - /** true if any of the files have been modified. */ - private boolean modified; - private SketchCodeDocument current; - private int currentIndex; - - private SketchData data; - - /** Class name for the PApplet, as determined by the preprocessor. */ - private String appletClassName; /** * path is location of the main .pde file, because this is also @@ -64,7 +56,7 @@ public class Sketch { */ public Sketch(Editor _editor, File file) throws IOException { editor = _editor; - data = new SketchData(file); + data = new SketchData(this, file); // lib/build must exist when the application is started // it is added to the CLASSPATH by default, but if it doesn't @@ -105,7 +97,7 @@ public Sketch(Editor _editor, File file) throws IOException { */ protected void load() throws IOException { data.load(); - + for (SketchCode code : data.getCodes()) { if (code.getMetadata() == null) code.setMetadata(new SketchCodeDocument(code)); @@ -405,7 +397,7 @@ protected void nameCode(String newName) { return; } ensureExistence(); - data.addCode((new SketchCodeDocument(newFile)).getCode()); + data.addCode((new SketchCodeDocument(this, newFile)).getCode()); } // sort the entries @@ -485,32 +477,15 @@ public void handleDeleteCode() { } } - - /** - * Move to the previous tab. - */ - public void handlePrevCode() { - int prev = currentIndex - 1; - if (prev < 0) prev = data.getCodeCount()-1; - setCurrentCode(prev); - } - - - /** - * Move to the next tab. - */ - public void handleNextCode() { - setCurrentCode((currentIndex + 1) % data.getCodeCount()); - } - - /** * Sets the modified value for the code in the frontmost tab. */ public void setModified(boolean state) { //System.out.println("setting modified to " + state); //new Exception().printStackTrace(); - current.getCode().setModified(state); +// if(current.getCode().isModified() != state){ +// current.getCode().setModified(state); +// } calcModified(); } @@ -533,10 +508,6 @@ protected void calcModified() { } - public boolean isModified() { - return modified; - } - /** * Save all code in the current sketch. @@ -607,19 +578,6 @@ public boolean accept(File dir, String name) { } - protected boolean renameCodeToInoExtension(File pdeFile) { - for (SketchCode c : data.getCodes()) { - if (!c.getFile().equals(pdeFile)) - continue; - - String pdeName = pdeFile.getPath(); - pdeName = pdeName.substring(0, pdeName.length() - 4) + ".ino"; - return c.renameTo(new File(pdeName)); - } - return false; - } - - /** * Handles 'Save As' for a sketch. *

@@ -902,7 +860,7 @@ public boolean addFile(File sourceFile) { } if (codeExtension != null) { - SketchCode newCode = (new SketchCodeDocument(destFile)).getCode(); + SketchCode newCode = (new SketchCodeDocument(this, destFile)).getCode(); if (replacement) { data.replaceCode(newCode); @@ -930,10 +888,6 @@ public boolean addFile(File sourceFile) { } - public void importLibrary(Library lib) throws IOException { - importLibrary(lib.getSrcFolder()); - } - /** * Add import statements to the current tab for all of packages inside * the specified jar file. @@ -941,7 +895,7 @@ public void importLibrary(Library lib) throws IOException { public void importLibrary(File jarPath) throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); - + String list[] = Base.headerListFromIncludePath(jarPath); // import statements into the main sketch file (code[0]) @@ -998,19 +952,6 @@ public void setCurrentCode(int which) { } - /** - * Internal helper function to set the current tab based on a name. - * @param findName the file name (not pretty name) to be shown - */ - protected void setCurrentCode(String findName) { - for (SketchCode code : data.getCodes()) { - if (findName.equals(code.getFileName()) || - findName.equals(code.getPrettyName())) { - setCurrentCode(data.indexOfCode(code)); - return; - } - } - } /** @@ -1234,207 +1175,10 @@ protected boolean upload(String buildPath, String suggestedClassName, boolean us } - public boolean exportApplicationPrompt() throws IOException, RunnerException { - return false; - } - - - /** - * Export to application via GUI. - */ - protected boolean exportApplication() throws IOException, RunnerException { - return false; - } - - - /** - * Export to application without GUI. - */ - public boolean exportApplication(String destPath, - int exportPlatform) throws IOException, RunnerException { - return false; - } - - - /** - * Make sure the sketch hasn't been moved or deleted by some - * nefarious user. If they did, try to re-create it and save. - * Only checks to see if the main folder is still around, - * but not its contents. - */ - protected void ensureExistence() { - if (data.getFolder().exists()) return; - - Base.showWarning(_("Sketch Disappeared"), - _("The sketch folder has disappeared.\n " + - "Will attempt to re-save in the same location,\n" + - "but anything besides the code will be lost."), null); - try { - data.getFolder().mkdirs(); - modified = true; - - for (SketchCode code : data.getCodes()) { - code.save(); // this will force a save - } - calcModified(); - - } catch (Exception e) { - Base.showWarning(_("Could not re-save sketch"), - _("Could not properly re-save the sketch. " + - "You may be in trouble at this point,\n" + - "and it might be time to copy and paste " + - "your code to another text editor."), e); - } - } - - - /** - * Returns true if this is a read-only sketch. Used for the - * examples directory, or when sketches are loaded from read-only - * volumes or folders without appropriate permissions. - */ - public boolean isReadOnly() { - String apath = data.getFolder().getAbsolutePath(); - for (File folder : Base.getLibrariesPath()) { - if (apath.startsWith(folder.getAbsolutePath())) - return true; - } - if (apath.startsWith(Base.getExamplesPath()) || - apath.startsWith(Base.getSketchbookLibrariesPath())) { - return true; - } - - // canWrite() doesn't work on directories - // } else if (!folder.canWrite()) { - - // check to see if each modified code file can be written to - for (SketchCode code : data.getCodes()) { - if (code.isModified() && code.fileReadOnly() && code.fileExists()) { - // System.err.println("found a read-only file " + code[i].file); - return true; - } - } - return false; - } - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - // Breaking out extension types in order to clean up the code, and make it - // easier for other environments (like Arduino) to incorporate changes. - - /** - * True if the specified code has the default file extension. - */ - public boolean hasDefaultExtension(SketchCode code) { - return code.isExtension(getDefaultExtension()); - } - - - /** - * True if the specified extension is the default file extension. - */ - public boolean isDefaultExtension(String what) { - return what.equals(getDefaultExtension()); - } - - - /** - * Check this extension (no dots, please) against the list of valid - * extensions. - */ - public boolean validExtension(String what) { - return data.getExtensions().contains(what); - } - - - /** - * Returns the default extension for this editor setup. - */ - public String getDefaultExtension() { - return data.getDefaultExtension(); - } - - static private List hiddenExtensions = Arrays.asList("ino", "pde"); - - public List getHiddenExtensions() { - return hiddenExtensions; - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - // Additional accessors added in 0136 because of package work. // These will also be helpful for tool developers. - /** - * Returns the name of this sketch. (The pretty name of the main tab.) - */ - public String getName() { - return data.getName(); - } - - - /** - * Returns path to the main .pde file for this sketch. - */ - public String getMainFilePath() { - return data.getMainFilePath(); - } - - - /** - * Returns the sketch folder. - */ - public File getFolder() { - return data.getFolder(); - } - - - /** - * Create the data folder if it does not exist already. As a convenience, - * it also returns the data folder, since it's likely about to be used. - */ - public File prepareDataFolder() { - if (!data.getDataFolder().exists()) { - data.getDataFolder().mkdirs(); - } - return data.getDataFolder(); - } - - - /** - * Create the code folder if it does not exist already. As a convenience, - * it also returns the code folder, since it's likely about to be used. - */ - public File prepareCodeFolder() { - if (!data.getCodeFolder().exists()) { - data.getCodeFolder().mkdirs(); - } - return data.getCodeFolder(); - } - - - public SketchCode[] getCodes() { - return data.getCodes(); - } - - - public int getCodeCount() { - return data.getCodeCount(); - } - - - public SketchCode getCode(int index) { - return data.getCode(index); - } - - - public int getCodeIndex(SketchCode who) { - return data.indexOfCode(who); - } - - public SketchCode getCurrentCode() { return current.getCode(); } @@ -1449,31 +1193,13 @@ public boolean isUntitled() { return editor.untitled; } - - public String getAppletClassName2() { - return appletClassName; + @Override + public boolean isExternalMode() { + return Editor.isExternalMode(); } // ................................................................. - /** - * Convert to sanitized name and alert the user - * if changes were made. - */ - static public String checkName(String origName) { - String newName = BaseNoGui.sanitizeName(origName); - - if (!newName.equals(origName)) { - String msg = - _("The sketch name had to be modified. Sketch names can only consist\n" + - "of ASCII characters and numbers (but cannot start with a number).\n" + - "They should also be less than 64 characters long."); - System.out.println(msg); - } - return newName; - } - - } diff --git a/app/src/processing/app/SketchCodeDocument.java b/app/src/processing/app/SketchCodeDocument.java index 857a270abc2..1e2a7bab4b4 100644 --- a/app/src/processing/app/SketchCodeDocument.java +++ b/app/src/processing/app/SketchCodeDocument.java @@ -2,9 +2,16 @@ 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 Document document; @@ -12,7 +19,7 @@ public class SketchCodeDocument{ // 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; @@ -24,15 +31,15 @@ public SketchCodeDocument(SketchCode code) { this.code.setMetadata(this); } - public SketchCodeDocument(File file) { - this.code = new SketchCode(file, this); + public SketchCodeDocument(BaseSketch sketch, File file) { + this.code = new SketchCode(sketch, file, this); } - public LastUndoableEditAwareUndoManager getUndo() { + public UndoManager getUndo() { return undo; } - public void setUndo(LastUndoableEditAwareUndoManager undo) { + public void setUndo(UndoManager undo) { this.undo = undo; } @@ -74,6 +81,51 @@ public Document getDocument() { public void setDocument(Document document) { this.document = document; + document.addDocumentListener(this); + } + + /** + * Check for changes in text. + * This use UndoManager actions to track if has modifications. + */ + private void checkModifiedState(){ + if(undo != null){ + + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + // HACK: get menu state from RSyntaxTextArea + Action a = RTextArea.getAction(RTextArea.UNDO_ACTION); + if(a.isEnabled()){ + code.setModified(true); + }else{ + // TODO: need re-check libraries changes ?? + code.setModified(false); + } + } + }); + }else{ + code.setModified(true); + } } + @Override + public void insertUpdate(DocumentEvent e) { + checkModifiedState(); + } + + + @Override + public void removeUpdate(DocumentEvent e) { + checkModifiedState(); + } + + + @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 7f23d3c4602..5023658ab5a 100644 --- a/app/src/processing/app/Theme.java +++ b/app/src/processing/app/Theme.java @@ -27,9 +27,11 @@ import java.awt.Font; import java.awt.SystemColor; +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 @@ -103,17 +105,40 @@ static public Font getFont(String attr) { } return font; } + + /** + * Returns the default font for text areas. + * + * @return The default font. + */ + public static final Font getDefaultFont() { + + // Use StyleContext to get a composite font for better Asian language + // support; see Sun bug S282887. + StyleContext sc = StyleContext.getDefaultStyleContext(); + Font font = null; + + 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); + } + } - static public SyntaxStyle getStyle(String what) { - String split[] = get("editor." + what + ".style").split(","); - - Color color = PreferencesHelper.parseColor(split[0]); - - 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/helpers/ConsoleLogger.java b/app/src/processing/app/helpers/ConsoleLogger.java new file mode 100755 index 00000000000..ef7697a4849 --- /dev/null +++ b/app/src/processing/app/helpers/ConsoleLogger.java @@ -0,0 +1,30 @@ +package processing.app.helpers; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.util.logging.LogRecord; +import java.util.logging.StreamHandler; + +public class ConsoleLogger extends StreamHandler { + + public ConsoleLogger() { + setOutputStream(new PrintStream(new FileOutputStream(FileDescriptor.out))); + } + + + public void publish(LogRecord record) { + super.publish(record); + flush(); + } + + /** + * Override StreamHandler.close to do a flush but not + * to close the output stream. That is, we do not + * close FileDescriptor.out. + */ + public void close() { + flush(); + } + +} diff --git a/app/src/processing/app/helpers/DocumentationUtils.java b/app/src/processing/app/helpers/DocumentationUtils.java new file mode 100755 index 00000000000..e4123a97d1e --- /dev/null +++ b/app/src/processing/app/helpers/DocumentationUtils.java @@ -0,0 +1,73 @@ +package processing.app.helpers; + +import static processing.app.I18n._; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + +import processing.app.Base; +import processing.app.I18n; +import processing.app.syntax.PdeKeywords; + +public class DocumentationUtils { + + public static boolean useDefaultBigHtmlHelp = true; // TODO: remove latter ! + + /** + * + * @param Classname (optinal) + * @param function (optinal) + * @return + */ + public static String getDocumentationURL(String Classname, String function){ + + // get referencens indexed from keywords.txt + String reference = PdeKeywords.getReference(Classname); + if(reference == null) return null; + + reference = I18n.format(_("{0}.html"), reference); + + File referenceFolder = Base.getContentFile("reference"); + File referenceFile = new File(referenceFolder, reference); + + return referenceFile.getAbsolutePath(); + } + + public static String getDocumentation(String Classname, String function){ + String url = getDocumentationURL(Classname, function); + + if(url == null || url.trim().length() == 0) return null; + + System.out.println(url); + + Document doc; + try { + doc = Jsoup.parse(new File(url), "UTF-8"); + Elements docElemets = doc.select("#wikitext > table > tbody > tr > td:nth-child(1)"); + if(docElemets.isEmpty()) docElemets = doc.select("#wikitext"); + + Elements styles = doc.select("style"); + + String html = docElemets.html(); + StringBuilder sb = new StringBuilder(html.length()); + + // Load and display specified page. + sb.append("").append(styles.outerHtml()).append(""); + sb.append("").append(docElemets.html()).append(""); + return sb.toString(); + } catch (IOException e) { + e.printStackTrace(); + } + + + return null; + + } + + +} diff --git a/app/src/processing/app/helpers/LogFormatter.java b/app/src/processing/app/helpers/LogFormatter.java new file mode 100755 index 00000000000..f1bd59a36eb --- /dev/null +++ b/app/src/processing/app/helpers/LogFormatter.java @@ -0,0 +1,49 @@ +package processing.app.helpers; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +public class LogFormatter extends Formatter { + + public String format; + private final Date dat = new Date(); + + public LogFormatter(String logformat) { + format = logformat; + } + + @Override + public String format(LogRecord record) { + dat.setTime(record.getMillis()); + String source; + if (record.getSourceClassName() != null) { + source = record.getSourceClassName().substring(record.getSourceClassName().lastIndexOf('.') + 1); + if (record.getSourceMethodName() != null) { + source += "." + record.getSourceMethodName(); + } + } else { + source = record.getLoggerName(); + } + String message = formatMessage(record); + String throwable = ""; + if (record.getThrown() != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.println(); + record.getThrown().printStackTrace(pw); + pw.close(); + throwable = sw.toString(); + } + return String.format(format, + dat, + source, + record.getLoggerName(), + record.getLevel(), + message, + throwable); + } + +} diff --git a/app/src/processing/app/syntax/CTokenMarker.java b/app/src/processing/app/syntax/CTokenMarker.java deleted file mode 100644 index ccb9b0b4822..00000000000 --- a/app/src/processing/app/syntax/CTokenMarker.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * CTokenMarker.java - C token marker - * Copyright (C) 1998, 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.text.Segment; - -/** - * C token marker. - * - * @author Slava Pestov - */ -public class CTokenMarker extends TokenMarker -{ - public CTokenMarker() - { - this(true,getKeywords()); - } - - public CTokenMarker(boolean cpp, KeywordMap keywords) - { - this.cpp = cpp; - this.keywords = keywords; - } - - public byte markTokensImpl(byte token, Segment line, int lineIndex) - { - char[] array = line.array; - int offset = line.offset; - lastOffset = offset; - lastKeyword = offset; - int mlength = line.count + offset; - boolean backslash = false; - -loop: for(int i = offset; i < mlength; i++) - { - int i1 = (i+1); - - char c = array[i]; - if(c == '\\') - { - backslash = !backslash; - continue; - } - - switch(token) - { - case Token.NULL: - switch(c) - { - case '#': - if(backslash) - backslash = false; - else if(cpp) - { - if(doKeyword(line,i,c)) - break; - addToken(i - lastOffset,token); - addToken(mlength - i,Token.KEYWORD2); - lastOffset = lastKeyword = mlength; - break loop; - } - break; - case '"': - doKeyword(line,i,c); - if(backslash) - backslash = false; - else - { - addToken(i - lastOffset,token); - token = Token.LITERAL1; - lastOffset = lastKeyword = i; - } - break; - case '\'': - doKeyword(line,i,c); - if(backslash) - backslash = false; - else - { - addToken(i - lastOffset,token); - token = Token.LITERAL2; - lastOffset = lastKeyword = i; - } - break; - case ':': - if(lastKeyword == offset) - { - if(doKeyword(line,i,c)) - break; - backslash = false; - addToken(i1 - lastOffset,Token.LABEL); - lastOffset = lastKeyword = i1; - } - else if(doKeyword(line,i,c)) - break; - break; - case '/': - backslash = false; - doKeyword(line,i,c); - if(mlength - i > 1) - { - switch(array[i1]) - { - case '*': - addToken(i - lastOffset,token); - lastOffset = lastKeyword = i; - if(mlength - i > 2 && array[i+2] == '*') - token = Token.COMMENT2; - else - token = Token.COMMENT1; - break; - case '/': - addToken(i - lastOffset,token); - addToken(mlength - i,Token.COMMENT1); - lastOffset = lastKeyword = mlength; - break loop; - } - } - break; - default: - backslash = false; - if(!Character.isLetterOrDigit(c) - && c != '_') - doKeyword(line,i,c); - break; - } - break; - case Token.COMMENT1: - case Token.COMMENT2: - backslash = false; - if(c == '*' && mlength - i > 1) - { - if(array[i1] == '/') - { - i++; - addToken((i+1) - lastOffset,token); - token = Token.NULL; - lastOffset = lastKeyword = i+1; - } - } - break; - case Token.LITERAL1: - if(backslash) - backslash = false; - else if(c == '"') - { - addToken(i1 - lastOffset,token); - token = Token.NULL; - lastOffset = lastKeyword = i1; - } - break; - case Token.LITERAL2: - if(backslash) - backslash = false; - else if(c == '\'') - { - addToken(i1 - lastOffset,Token.LITERAL1); - token = Token.NULL; - lastOffset = lastKeyword = i1; - } - break; - default: - throw new InternalError("Invalid state: " - + token); - } - } - - if(token == Token.NULL) - doKeyword(line,mlength,'\0'); - - switch(token) - { - case Token.LITERAL1: - case Token.LITERAL2: - addToken(mlength - lastOffset,Token.INVALID); - token = Token.NULL; - break; - case Token.KEYWORD2: - addToken(mlength - lastOffset,token); - if (!backslash) token = Token.NULL; - addToken(mlength - lastOffset,token); - break; - default: - addToken(mlength - lastOffset,token); - break; - } - - return token; - } - - public static KeywordMap getKeywords() - { - if(cKeywords == null) - { - cKeywords = new KeywordMap(false); - cKeywords.add("char",Token.KEYWORD3); - cKeywords.add("double",Token.KEYWORD3); - cKeywords.add("enum",Token.KEYWORD3); - cKeywords.add("float",Token.KEYWORD3); - cKeywords.add("int",Token.KEYWORD3); - cKeywords.add("long",Token.KEYWORD3); - cKeywords.add("short",Token.KEYWORD3); - cKeywords.add("signed",Token.KEYWORD3); - cKeywords.add("struct",Token.KEYWORD3); - cKeywords.add("typedef",Token.KEYWORD3); - cKeywords.add("union",Token.KEYWORD3); - cKeywords.add("unsigned",Token.KEYWORD3); - cKeywords.add("void",Token.KEYWORD3); - cKeywords.add("auto",Token.KEYWORD1); - cKeywords.add("const",Token.KEYWORD1); - cKeywords.add("extern",Token.KEYWORD1); - cKeywords.add("register",Token.KEYWORD1); - cKeywords.add("static",Token.KEYWORD1); - cKeywords.add("volatile",Token.KEYWORD1); - cKeywords.add("break",Token.KEYWORD1); - cKeywords.add("case",Token.KEYWORD1); - cKeywords.add("continue",Token.KEYWORD1); - cKeywords.add("default",Token.KEYWORD1); - cKeywords.add("do",Token.KEYWORD1); - cKeywords.add("else",Token.KEYWORD1); - cKeywords.add("for",Token.KEYWORD1); - cKeywords.add("goto",Token.KEYWORD1); - cKeywords.add("if",Token.KEYWORD1); - cKeywords.add("return",Token.KEYWORD1); - cKeywords.add("sizeof",Token.KEYWORD1); - cKeywords.add("switch",Token.KEYWORD1); - cKeywords.add("while",Token.KEYWORD1); - cKeywords.add("asm",Token.KEYWORD2); - cKeywords.add("asmlinkage",Token.KEYWORD2); - cKeywords.add("far",Token.KEYWORD2); - cKeywords.add("huge",Token.KEYWORD2); - cKeywords.add("inline",Token.KEYWORD2); - cKeywords.add("near",Token.KEYWORD2); - cKeywords.add("pascal",Token.KEYWORD2); - cKeywords.add("true",Token.LITERAL2); - cKeywords.add("false",Token.LITERAL2); - cKeywords.add("NULL",Token.LITERAL2); - } - return cKeywords; - } - - // private members - private static KeywordMap cKeywords; - - private boolean cpp; - private KeywordMap keywords; - private int lastOffset; - private int lastKeyword; - - private boolean doKeyword(Segment line, int i, char c) - { - int i1 = i+1; - - int len = i - lastKeyword; - byte id = keywords.lookup(line,lastKeyword,len); - if(id != Token.NULL) - { - if(lastKeyword != lastOffset) - addToken(lastKeyword - lastOffset,Token.NULL); - addToken(len,id); - lastOffset = i; - } - lastKeyword = i1; - return false; - } -} diff --git a/app/src/processing/app/syntax/DefaultInputHandler.java b/app/src/processing/app/syntax/DefaultInputHandler.java deleted file mode 100644 index e9e23fea12d..00000000000 --- a/app/src/processing/app/syntax/DefaultInputHandler.java +++ /dev/null @@ -1,373 +0,0 @@ -/* - * DefaultInputHandler.java - Default implementation of an input handler - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.KeyStroke; -import java.awt.event.*; -import java.awt.Toolkit; -import java.util.Hashtable; -import java.util.StringTokenizer; - -/** - * The default input handler. It maps sequences of keystrokes into actions - * and inserts key typed events into the text area. - * @author Slava Pestov - */ -public class DefaultInputHandler extends InputHandler -{ - /** - * Creates a new input handler with no key bindings defined. - */ - public DefaultInputHandler() - { - bindings = currentBindings = new Hashtable(); - } - - /** - * Sets up the default key bindings. - */ - public void addDefaultKeyBindings() - { - addKeyBinding("BACK_SPACE",BACKSPACE); - addKeyBinding("C+BACK_SPACE",BACKSPACE_WORD); - addKeyBinding("DELETE",DELETE); - addKeyBinding("C+DELETE",DELETE_WORD); - - addKeyBinding("ENTER",INSERT_BREAK); - addKeyBinding("TAB",INSERT_TAB); - - addKeyBinding("INSERT",OVERWRITE); - addKeyBinding("C+\\",TOGGLE_RECT); - - addKeyBinding("HOME",HOME); - addKeyBinding("END",END); - addKeyBinding("S+HOME",SELECT_HOME); - addKeyBinding("S+END",SELECT_END); - addKeyBinding("C+HOME",DOCUMENT_HOME); - addKeyBinding("C+END",DOCUMENT_END); - addKeyBinding("CS+HOME",SELECT_DOC_HOME); - addKeyBinding("CS+END",SELECT_DOC_END); - - addKeyBinding("PAGE_UP",PREV_PAGE); - addKeyBinding("PAGE_DOWN",NEXT_PAGE); - addKeyBinding("S+PAGE_UP",SELECT_PREV_PAGE); - addKeyBinding("S+PAGE_DOWN",SELECT_NEXT_PAGE); - - addKeyBinding("LEFT",PREV_CHAR); - addKeyBinding("S+LEFT",SELECT_PREV_CHAR); - addKeyBinding("C+LEFT",PREV_WORD); - addKeyBinding("CS+LEFT",SELECT_PREV_WORD); - addKeyBinding("RIGHT",NEXT_CHAR); - addKeyBinding("S+RIGHT",SELECT_NEXT_CHAR); - addKeyBinding("C+RIGHT",NEXT_WORD); - addKeyBinding("CS+RIGHT",SELECT_NEXT_WORD); - addKeyBinding("UP",PREV_LINE); - addKeyBinding("S+UP",SELECT_PREV_LINE); - addKeyBinding("DOWN",NEXT_LINE); - addKeyBinding("S+DOWN",SELECT_NEXT_LINE); - - addKeyBinding("C+ENTER",REPEAT); - } - - /** - * Adds a key binding to this input handler. The key binding is - * a list of white space separated key strokes of the form - * [modifiers+]key where modifier is C for Control, A for Alt, - * or S for Shift, and key is either a character (a-z) or a field - * name in the KeyEvent class prefixed with VK_ (e.g., BACK_SPACE) - * @param keyBinding The key binding - * @param action The action - */ - public void addKeyBinding(String keyBinding, ActionListener action) - { - Hashtable current = bindings; - - StringTokenizer st = new StringTokenizer(keyBinding); - while(st.hasMoreTokens()) - { - KeyStroke keyStroke = parseKeyStroke(st.nextToken()); - if(keyStroke == null) - return; - - if(st.hasMoreTokens()) - { - Object o = current.get(keyStroke); - if(o instanceof Hashtable) - current = (Hashtable)o; - else - { - o = new Hashtable(); - current.put(keyStroke,o); - current = (Hashtable)o; - } - } - else - current.put(keyStroke,action); - } - } - - /** - * Removes a key binding from this input handler. This is not yet - * implemented. - * @param keyBinding The key binding - */ - public void removeKeyBinding(String keyBinding) - { - throw new InternalError("Not yet implemented"); - } - - /** - * Removes all key bindings from this input handler. - */ - public void removeAllKeyBindings() - { - bindings.clear(); - } - - /** - * Returns a copy of this input handler that shares the same - * key bindings. Setting key bindings in the copy will also - * set them in the original. - */ - public InputHandler copy() - { - return new DefaultInputHandler(this); - } - - /** - * Handle a key pressed event. This will look up the binding for - * the key stroke and execute it. - */ - public void keyPressed(KeyEvent evt) - { - int keyCode = evt.getKeyCode(); - int modifiers = evt.getModifiers(); - - // moved this earlier so it doesn't get random meta clicks - if (keyCode == KeyEvent.VK_CONTROL || - keyCode == KeyEvent.VK_SHIFT || - keyCode == KeyEvent.VK_ALT || - keyCode == KeyEvent.VK_META) { - return; - } - - // don't get command-s or other menu key equivs on mac - // unless it's something that's specifically bound (cmd-left or right) - //if ((modifiers & KeyEvent.META_MASK) != 0) return; - if ((modifiers & KeyEvent.META_MASK) != 0) { - KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers); - if (currentBindings.get(keyStroke) == null) { - return; - } - } - - /* - char keyChar = evt.getKeyChar(); - System.out.println("code=" + keyCode + " char=" + keyChar + - " charint=" + ((int)keyChar)); - System.out.println("other codes " + KeyEvent.VK_ALT + " " + - KeyEvent.VK_META); - */ - - if((modifiers & ~KeyEvent.SHIFT_MASK) != 0 - || evt.isActionKey() - || keyCode == KeyEvent.VK_BACK_SPACE - || keyCode == KeyEvent.VK_DELETE - || keyCode == KeyEvent.VK_ENTER - || keyCode == KeyEvent.VK_TAB - || keyCode == KeyEvent.VK_ESCAPE) - { - if(grabAction != null) - { - handleGrabAction(evt); - return; - } - - KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, - modifiers); - Object o = currentBindings.get(keyStroke); - if(o == null) - { - // Don't beep if the user presses some - // key we don't know about unless a - // prefix is active. Otherwise it will - // beep when caps lock is pressed, etc. - if(currentBindings != bindings) - { - Toolkit.getDefaultToolkit().beep(); - // F10 should be passed on, but C+e F10 - // shouldn't - repeatCount = 0; - repeat = false; - evt.consume(); - } - currentBindings = bindings; - return; - } - else if(o instanceof ActionListener) - { - currentBindings = bindings; - - executeAction(((ActionListener)o), - evt.getSource(),null); - - evt.consume(); - return; - } - else if(o instanceof Hashtable) - { - currentBindings = (Hashtable)o; - evt.consume(); - return; - } - } - } - - /** - * Handle a key typed event. This inserts the key into the text area. - */ - public void keyTyped(KeyEvent evt) - { - int modifiers = evt.getModifiers(); - char c = evt.getKeyChar(); - - // this is the apple/cmd key on macosx.. so menu commands - // were being passed through as legit keys.. added this line - // in an attempt to prevent. - if ((modifiers & KeyEvent.META_MASK) != 0) return; - - if (c != KeyEvent.CHAR_UNDEFINED) // && - // (modifiers & KeyEvent.ALT_MASK) == 0) - { - if(c >= 0x20 && c != 0x7f) - { - KeyStroke keyStroke = KeyStroke.getKeyStroke( - Character.toUpperCase(c)); - Object o = currentBindings.get(keyStroke); - - if(o instanceof Hashtable) - { - currentBindings = (Hashtable)o; - return; - } - else if(o instanceof ActionListener) - { - currentBindings = bindings; - executeAction((ActionListener)o, - evt.getSource(), - String.valueOf(c)); - return; - } - - currentBindings = bindings; - - if(grabAction != null) - { - handleGrabAction(evt); - return; - } - - // 0-9 adds another 'digit' to the repeat number - if(repeat && Character.isDigit(c)) - { - repeatCount *= 10; - repeatCount += (c - '0'); - return; - } - - executeAction(INSERT_CHAR,evt.getSource(), - String.valueOf(evt.getKeyChar())); - - repeatCount = 0; - repeat = false; - } - } - } - - /** - * Converts a string to a keystroke. The string should be of the - * form modifiers+shortcut where modifiers - * is any combination of A for Alt, C for Control, S for Shift - * or M for Meta, and shortcut is either a single character, - * or a keycode name from the KeyEvent class, without - * the VK_ prefix. - * @param keyStroke A string description of the key stroke - */ - public static KeyStroke parseKeyStroke(String keyStroke) - { - if(keyStroke == null) - return null; - int modifiers = 0; - int index = keyStroke.indexOf('+'); - if(index != -1) - { - for(int i = 0; i < index; i++) - { - switch(Character.toUpperCase(keyStroke - .charAt(i))) - { - case 'A': - modifiers |= InputEvent.ALT_MASK; - break; - case 'C': - modifiers |= InputEvent.CTRL_MASK; - break; - case 'M': - modifiers |= InputEvent.META_MASK; - break; - case 'S': - modifiers |= InputEvent.SHIFT_MASK; - break; - } - } - } - String key = keyStroke.substring(index + 1); - if(key.length() == 1) - { - char ch = Character.toUpperCase(key.charAt(0)); - if(modifiers == 0) - return KeyStroke.getKeyStroke(ch); - else - return KeyStroke.getKeyStroke(ch,modifiers); - } - else if(key.length() == 0) - { - System.err.println("Invalid key stroke: " + keyStroke); - return null; - } - else - { - int ch; - - try - { - ch = KeyEvent.class.getField("VK_".concat(key)) - .getInt(null); - } - catch(Exception e) - { - System.err.println("Invalid key stroke: " - + keyStroke); - return null; - } - - return KeyStroke.getKeyStroke(ch,modifiers); - } - } - - // private members - private Hashtable bindings; - private Hashtable currentBindings; - - private DefaultInputHandler(DefaultInputHandler copy) - { - bindings = currentBindings = copy.bindings; - } -} diff --git a/app/src/processing/app/syntax/InputHandler.java b/app/src/processing/app/syntax/InputHandler.java deleted file mode 100644 index e146713484b..00000000000 --- a/app/src/processing/app/syntax/InputHandler.java +++ /dev/null @@ -1,1135 +0,0 @@ -/* - * InputHandler.java - Manages key bindings and executes actions - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.text.*; -import javax.swing.JPopupMenu; -import java.awt.event.*; -import java.awt.Component; -import java.util.*; - -/** - * An input handler converts the user's key strokes into concrete actions. - * It also takes care of macro recording and action repetition.

- * - * This class provides all the necessary support code for an input - * handler, but doesn't actually do any key binding logic. It is up - * to the implementations of this class to do so. - * - * @author Slava Pestov - */ -public abstract class InputHandler extends KeyAdapter -{ - /** - * If this client property is set to Boolean.TRUE on the text area, - * the home/end keys will support 'smart' BRIEF-like behaviour - * (one press = start/end of line, two presses = start/end of - * viewscreen, three presses = start/end of document). By default, - * this property is not set. - */ - public static final String SMART_HOME_END_PROPERTY = "InputHandler.homeEnd"; - - public static final ActionListener BACKSPACE = new backspace(); - public static final ActionListener BACKSPACE_WORD = new backspace_word(); - public static final ActionListener DELETE = new delete(); - public static final ActionListener DELETE_WORD = new delete_word(); - public static final ActionListener END = new end(false); - public static final ActionListener DOCUMENT_END = new document_end(false); - public static final ActionListener SELECT_END = new end(true); - public static final ActionListener SELECT_DOC_END = new document_end(true); - public static final ActionListener INSERT_BREAK = new insert_break(); - public static final ActionListener INSERT_TAB = new insert_tab(); - public static final ActionListener HOME = new home(false); - public static final ActionListener DOCUMENT_HOME = new document_home(false); - public static final ActionListener SELECT_HOME = new home(true); - public static final ActionListener SELECT_DOC_HOME = new document_home(true); - public static final ActionListener NEXT_CHAR = new next_char(false); - public static final ActionListener NEXT_LINE = new next_line(false); - public static final ActionListener NEXT_PAGE = new next_page(false); - public static final ActionListener NEXT_WORD = new next_word(false); - public static final ActionListener SELECT_NEXT_CHAR = new next_char(true); - public static final ActionListener SELECT_NEXT_LINE = new next_line(true); - public static final ActionListener SELECT_NEXT_PAGE = new next_page(true); - public static final ActionListener SELECT_NEXT_WORD = new next_word(true); - public static final ActionListener OVERWRITE = new overwrite(); - public static final ActionListener PREV_CHAR = new prev_char(false); - public static final ActionListener PREV_LINE = new prev_line(false); - public static final ActionListener PREV_PAGE = new prev_page(false); - public static final ActionListener PREV_WORD = new prev_word(false); - public static final ActionListener SELECT_PREV_CHAR = new prev_char(true); - public static final ActionListener SELECT_PREV_LINE = new prev_line(true); - public static final ActionListener SELECT_PREV_PAGE = new prev_page(true); - public static final ActionListener SELECT_PREV_WORD = new prev_word(true); - public static final ActionListener REPEAT = new repeat(); - public static final ActionListener TOGGLE_RECT = new toggle_rect(); - public static final ActionListener CLIPBOARD_CUT = new clipboard_cut(); // [fry] - public static final ActionListener CLIPBOARD_COPY = new clipboard_copy(); - public static final ActionListener CLIPBOARD_PASTE = new clipboard_paste(); - - // Default action - public static final ActionListener INSERT_CHAR = new insert_char(); - - private static Hashtable actions; - - static - { - actions = new Hashtable(); - actions.put("backspace",BACKSPACE); - actions.put("backspace-word",BACKSPACE_WORD); - actions.put("delete",DELETE); - actions.put("delete-word",DELETE_WORD); - actions.put("end",END); - actions.put("select-end",SELECT_END); - actions.put("document-end",DOCUMENT_END); - actions.put("select-doc-end",SELECT_DOC_END); - actions.put("insert-break",INSERT_BREAK); - actions.put("insert-tab",INSERT_TAB); - actions.put("home",HOME); - actions.put("select-home",SELECT_HOME); - actions.put("document-home",DOCUMENT_HOME); - actions.put("select-doc-home",SELECT_DOC_HOME); - actions.put("next-char",NEXT_CHAR); - actions.put("next-line",NEXT_LINE); - actions.put("next-page",NEXT_PAGE); - actions.put("next-word",NEXT_WORD); - actions.put("select-next-char",SELECT_NEXT_CHAR); - actions.put("select-next-line",SELECT_NEXT_LINE); - actions.put("select-next-page",SELECT_NEXT_PAGE); - actions.put("select-next-word",SELECT_NEXT_WORD); - actions.put("overwrite",OVERWRITE); - actions.put("prev-char",PREV_CHAR); - actions.put("prev-line",PREV_LINE); - actions.put("prev-page",PREV_PAGE); - actions.put("prev-word",PREV_WORD); - actions.put("select-prev-char",SELECT_PREV_CHAR); - actions.put("select-prev-line",SELECT_PREV_LINE); - actions.put("select-prev-page",SELECT_PREV_PAGE); - actions.put("select-prev-word",SELECT_PREV_WORD); - actions.put("repeat",REPEAT); - actions.put("toggle-rect",TOGGLE_RECT); - actions.put("insert-char",INSERT_CHAR); - actions.put("clipboard-cut",CLIPBOARD_CUT); - actions.put("clipboard-copy",CLIPBOARD_COPY); - actions.put("clipboard-paste",CLIPBOARD_PASTE); - } - - /** - * Returns a named text area action. - * @param name The action name - */ - public static ActionListener getAction(String name) - { - return (ActionListener)actions.get(name); - } - - /** - * Returns the name of the specified text area action. - * @param listener The action - */ - public static String getActionName(ActionListener listener) - { - Enumeration en = getActions(); - while(en.hasMoreElements()) - { - String name = (String)en.nextElement(); - ActionListener _listener = getAction(name); - if(_listener == listener) { - return name; - } - } - return null; - } - - /** - * Returns an enumeration of all available actions. - */ - public static Enumeration getActions() - { - return actions.keys(); - } - - /** - * Adds the default key bindings to this input handler. - * This should not be called in the constructor of this - * input handler, because applications might load the - * key bindings from a file, etc. - */ - public abstract void addDefaultKeyBindings(); - - /** - * Adds a key binding to this input handler. - * @param keyBinding The key binding (the format of this is - * input-handler specific) - * @param action The action - */ - public abstract void addKeyBinding(String keyBinding, ActionListener action); - - /** - * Removes a key binding from this input handler. - * @param keyBinding The key binding - */ - public abstract void removeKeyBinding(String keyBinding); - - /** - * Removes all key bindings from this input handler. - */ - public abstract void removeAllKeyBindings(); - - /** - * Grabs the next key typed event and invokes the specified - * action with the key as a the action command. - */ - public void grabNextKeyStroke(ActionListener listener) - { - grabAction = listener; - } - - /** - * Returns if repeating is enabled. When repeating is enabled, - * actions will be executed multiple times. This is usually - * invoked with a special key stroke in the input handler. - */ - public boolean isRepeatEnabled() - { - return repeat; - } - - /** - * Enables repeating. When repeating is enabled, actions will be - * executed multiple times. Once repeating is enabled, the input - * handler should read a number from the keyboard. - */ - public void setRepeatEnabled(boolean repeat) - { - this.repeat = repeat; - } - - /** - * Returns the number of times the next action will be repeated. - */ - public int getRepeatCount() - { - return (repeat ? Math.max(1,repeatCount) : 1); - } - - /** - * Sets the number of times the next action will be repeated. - * @param repeatCount The repeat count - */ - public void setRepeatCount(int repeatCount) - { - this.repeatCount = repeatCount; - } - - /** - * Returns the macro recorder. If this is non-null, all executed - * actions should be forwarded to the recorder. - */ - public InputHandler.MacroRecorder getMacroRecorder() - { - return recorder; - } - - /** - * Sets the macro recorder. If this is non-null, all executed - * actions should be forwarded to the recorder. - * @param recorder The macro recorder - */ - public void setMacroRecorder(InputHandler.MacroRecorder recorder) - { - this.recorder = recorder; - } - - /** - * Returns a copy of this input handler that shares the same - * key bindings. Setting key bindings in the copy will also - * set them in the original. - */ - public abstract InputHandler copy(); - - /** - * Executes the specified action, repeating and recording it as - * necessary. - * @param listener The action listener - * @param source The event source - * @param actionCommand The action command - */ - public void executeAction(ActionListener listener, Object source, - String actionCommand) - { - // create event - ActionEvent evt = new ActionEvent(source, - ActionEvent.ACTION_PERFORMED, - actionCommand); - - // don't do anything if the action is a wrapper - // (like EditAction.Wrapper) - if(listener instanceof Wrapper) - { - listener.actionPerformed(evt); - return; - } - - // remember old values, in case action changes them - boolean _repeat = repeat; - int _repeatCount = getRepeatCount(); - - // execute the action - if(listener instanceof InputHandler.NonRepeatable) - listener.actionPerformed(evt); - else - { - for(int i = 0; i < Math.max(1,repeatCount); i++) - listener.actionPerformed(evt); - } - - // do recording. Notice that we do no recording whatsoever - // for actions that grab keys - if(grabAction == null) - { - if(recorder != null) - { - if(!(listener instanceof InputHandler.NonRecordable)) - { - if(_repeatCount != 1) - recorder.actionPerformed(REPEAT,String.valueOf(_repeatCount)); - - recorder.actionPerformed(listener,actionCommand); - } - } - - // If repeat was true originally, clear it - // Otherwise it might have been set by the action, etc - if(_repeat) - { - repeat = false; - repeatCount = 0; - } - } - } - - /** - * Returns the text area that fired the specified event. - * @param evt The event - */ - public static JEditTextArea getTextArea(EventObject evt) - { - if(evt != null) - { - Object o = evt.getSource(); - if(o instanceof Component) - { - // find the parent text area - Component c = (Component)o; - for(;;) - { - if(c instanceof JEditTextArea) - return (JEditTextArea)c; - else if(c == null) - break; - if(c instanceof JPopupMenu) - c = ((JPopupMenu)c) - .getInvoker(); - else - c = c.getParent(); - } - } - } - - // this shouldn't happen - System.err.println("BUG: getTextArea() returning null"); - System.err.println("Report this to Slava Pestov "); - return null; - } - - // protected members - - /** - * If a key is being grabbed, this method should be called with - * the appropriate key event. It executes the grab action with - * the typed character as the parameter. - */ - protected void handleGrabAction(KeyEvent evt) - { - // Clear it *before* it is executed so that executeAction() - // resets the repeat count - ActionListener _grabAction = grabAction; - grabAction = null; - executeAction(_grabAction,evt.getSource(), - String.valueOf(evt.getKeyChar())); - } - - // protected members - protected ActionListener grabAction; - protected boolean repeat; - protected int repeatCount; - protected InputHandler.MacroRecorder recorder; - - /** - * If an action implements this interface, it should not be repeated. - * Instead, it will handle the repetition itself. - */ - public interface NonRepeatable {} - - /** - * If an action implements this interface, it should not be recorded - * by the macro recorder. Instead, it will do its own recording. - */ - public interface NonRecordable {} - - /** - * For use by EditAction.Wrapper only. - * @since jEdit 2.2final - */ - public interface Wrapper {} - - /** - * Macro recorder. - */ - public interface MacroRecorder - { - void actionPerformed(ActionListener listener, - String actionCommand); - } - - public static class backspace implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - - if(!textArea.isEditable()) - { - textArea.getToolkit().beep(); - return; - } - - if(textArea.getSelectionStart() - != textArea.getSelectionStop()) - { - textArea.setSelectedText(""); - } - else - { - int caret = textArea.getCaretPosition(); - if(caret == 0) - { - textArea.getToolkit().beep(); - return; - } - try - { - textArea.getDocument().remove(caret - 1,1); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - } - } - } - } - - public static class backspace_word implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int start = textArea.getSelectionStart(); - if(start != textArea.getSelectionStop()) - { - textArea.setSelectedText(""); - } - - int line = textArea.getCaretLine(); - int lineStart = textArea.getLineStartOffset(line); - int caret = start - lineStart; - - String lineText = textArea.getLineText(textArea - .getCaretLine()); - - if(caret == 0) - { - if(lineStart == 0) - { - textArea.getToolkit().beep(); - return; - } - caret--; - } - else - { - String noWordSep = (String)textArea.getDocument().getProperty("noWordSep"); - caret = TextUtilities.findWordStart(lineText,caret,noWordSep); - } - - try - { - textArea.getDocument().remove( - caret + lineStart, - start - (caret + lineStart)); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - } - } - } - - public static class delete implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - - if(!textArea.isEditable()) - { - textArea.getToolkit().beep(); - return; - } - - if(textArea.getSelectionStart() - != textArea.getSelectionStop()) - { - textArea.setSelectedText(""); - } - else - { - int caret = textArea.getCaretPosition(); - if(caret == textArea.getDocumentLength()) - { - textArea.getToolkit().beep(); - return; - } - try - { - textArea.getDocument().remove(caret,1); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - } - } - } - } - - public static class delete_word implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int start = textArea.getSelectionStart(); - if(start != textArea.getSelectionStop()) - { - textArea.setSelectedText(""); - } - - int line = textArea.getCaretLine(); - int lineStart = textArea.getLineStartOffset(line); - int caret = start - lineStart; - - String lineText = textArea.getLineText(textArea - .getCaretLine()); - - if(caret == lineText.length()) - { - if(lineStart + caret == textArea.getDocumentLength()) - { - textArea.getToolkit().beep(); - return; - } - caret++; - } - else - { - String noWordSep = (String)textArea.getDocument().getProperty("noWordSep"); - caret = TextUtilities.findWordEnd(lineText,caret,noWordSep); - } - - try - { - textArea.getDocument().remove(start, - (caret + lineStart) - start); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - } - } - } - - public static class end implements ActionListener - { - private boolean select; - - public end(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - - int caret = textArea.getCaretPosition(); - - int lastOfLine = textArea.getLineStopOffset( - textArea.getCaretLine()) - 1; - int lastVisibleLine = textArea.getFirstLine() - + textArea.getVisibleLines(); - if(lastVisibleLine >= textArea.getLineCount()) - { - lastVisibleLine = Math.min(textArea.getLineCount() - 1, - lastVisibleLine); - } - else - lastVisibleLine -= (textArea.getElectricScroll() + 1); - - int lastVisible = textArea.getLineStopOffset(lastVisibleLine) - 1; - int lastDocument = textArea.getDocumentLength(); - - if(caret == lastDocument) - { - textArea.getToolkit().beep(); - return; - } - else if(!Boolean.TRUE.equals(textArea.getClientProperty( - SMART_HOME_END_PROPERTY))) - caret = lastOfLine; - else if(caret == lastVisible) - caret = lastDocument; - else if(caret == lastOfLine) - caret = lastVisible; - else - caret = lastOfLine; - - if(select) - textArea.select(textArea.getMarkPosition(),caret); - else - textArea.setCaretPosition(caret); - } - } - - public static class document_end implements ActionListener - { - private boolean select; - - public document_end(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - if(select) - textArea.select(textArea.getMarkPosition(), - textArea.getDocumentLength()); - else - textArea.setCaretPosition(textArea - .getDocumentLength()); - } - } - - public static class home implements ActionListener - { - private boolean select; - - public home(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - - int caret = textArea.getCaretPosition(); - - int firstLine = textArea.getFirstLine(); - - int firstOfLine = textArea.getLineStartOffset( - textArea.getCaretLine()); - int firstVisibleLine = (firstLine == 0 ? 0 : - firstLine + textArea.getElectricScroll()); - int firstVisible = textArea.getLineStartOffset( - firstVisibleLine); - - if(caret == 0) - { - textArea.getToolkit().beep(); - return; - } - else if(!Boolean.TRUE.equals(textArea.getClientProperty( - SMART_HOME_END_PROPERTY))) - caret = firstOfLine; - else if(caret == firstVisible) - caret = 0; - else if(caret == firstOfLine) - caret = firstVisible; - else - caret = firstOfLine; - - if(select) - textArea.select(textArea.getMarkPosition(),caret); - else - textArea.setCaretPosition(caret); - } - } - - public static class document_home implements ActionListener - { - private boolean select; - - public document_home(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - if(select) - textArea.select(textArea.getMarkPosition(),0); - else - textArea.setCaretPosition(0); - } - } - - public static class insert_break implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - - if(!textArea.isEditable()) - { - textArea.getToolkit().beep(); - return; - } - - textArea.setSelectedText("\n"); - } - } - - public static class insert_tab implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - - if(!textArea.isEditable()) - { - textArea.getToolkit().beep(); - return; - } - - textArea.overwriteSetSelectedText("\t"); - } - } - - public static class next_char implements ActionListener - { - private boolean select; - - public next_char(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int caret = textArea.getCaretPosition(); - - if(caret == textArea.getDocumentLength()) - { - if (textArea.getSelectionStart() != - textArea.getSelectionStop()) { - // just move to the end of the selection - textArea.select(caret, caret); - } else { - // beep at the user for being annoying - textArea.getToolkit().beep(); - } - - } else if (select) { - textArea.select(textArea.getMarkPosition(), caret+1); - - } else { - int start = textArea.getSelectionStart(); - int end = textArea.getSelectionStop(); - if (start != end) { - textArea.select(end, end); - } else { - textArea.setCaretPosition(caret + 1); - } - } - } - } - - public static class next_line implements ActionListener - { - private boolean select; - - public next_line(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int caret = textArea.getCaretPosition(); - int line = textArea.getCaretLine(); - - if(line == textArea.getLineCount() - 1) - { - //textArea.getToolkit().beep(); - int doc = textArea.getDocumentLength(); - if (select) { - textArea.select(textArea.getMarkPosition(), doc); - } else { - textArea.setCaretPosition(doc); - } - return; - } - - int magic = textArea.getMagicCaretPosition(); - if(magic == -1) - { - magic = textArea.offsetToX(line, - caret - textArea.getLineStartOffset(line)); - } - - caret = textArea.getLineStartOffset(line + 1) - + textArea.xToOffset(line + 1,magic); - if(select) - textArea.select(textArea.getMarkPosition(),caret); - else - textArea.setCaretPosition(caret); - textArea.setMagicCaretPosition(magic); - } - } - - public static class next_page implements ActionListener - { - private boolean select; - - public next_page(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int lineCount = textArea.getLineCount(); - int firstLine = textArea.getFirstLine(); - int visibleLines = textArea.getVisibleLines(); - int line = textArea.getCaretLine(); - - firstLine += visibleLines; - - if(firstLine + visibleLines >= lineCount - 1) - firstLine = lineCount - visibleLines; - - textArea.setFirstLine(firstLine); - - int caret = textArea.getLineStartOffset( - Math.min(textArea.getLineCount() - 1, - line + visibleLines)); - if(select) - textArea.select(textArea.getMarkPosition(),caret); - else - textArea.setCaretPosition(caret); - } - } - - public static class next_word implements ActionListener - { - private boolean select; - - public next_word(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int caret = textArea.getCaretPosition(); - int line = textArea.getCaretLine(); - int lineStart = textArea.getLineStartOffset(line); - caret -= lineStart; - - String lineText = textArea.getLineText(textArea - .getCaretLine()); - - if(caret == lineText.length()) - { - if(lineStart + caret == textArea.getDocumentLength()) - { - textArea.getToolkit().beep(); - return; - } - caret++; - } - else - { - String noWordSep = (String)textArea.getDocument().getProperty("noWordSep"); - caret = TextUtilities.findWordEnd(lineText,caret,noWordSep); - } - - if(select) - textArea.select(textArea.getMarkPosition(), - lineStart + caret); - else - textArea.setCaretPosition(lineStart + caret); - } - } - - public static class overwrite implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - textArea.setOverwriteEnabled( - !textArea.isOverwriteEnabled()); - } - } - - public static class prev_char implements ActionListener - { - private boolean select; - - public prev_char(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int caret = textArea.getCaretPosition(); - if(caret == 0) - { - textArea.getToolkit().beep(); - return; - } - - if (select) { - textArea.select(textArea.getMarkPosition(), caret-1); - } else { - int start = textArea.getSelectionStart(); - int end = textArea.getSelectionStop(); - if (start != end) { - textArea.select(start, start); - } else { - textArea.setCaretPosition(caret - 1); - } - } - } - } - - public static class prev_line implements ActionListener - { - private boolean select; - - public prev_line(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int caret = textArea.getCaretPosition(); - int line = textArea.getCaretLine(); - - if(line == 0) - { - if (select) { - if (textArea.getSelectionStart() != 0) { - textArea.select(textArea.getMarkPosition(), 0); - } - } else { - textArea.setCaretPosition(0); - } - //textArea.getToolkit().beep(); - return; - } - - int magic = textArea.getMagicCaretPosition(); - if(magic == -1) - { - magic = textArea.offsetToX(line, - caret - textArea.getLineStartOffset(line)); - } - - caret = textArea.getLineStartOffset(line - 1) - + textArea.xToOffset(line - 1,magic); - if(select) - textArea.select(textArea.getMarkPosition(),caret); - else - textArea.setCaretPosition(caret); - textArea.setMagicCaretPosition(magic); - } - } - - public static class prev_page implements ActionListener - { - private boolean select; - - public prev_page(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int firstLine = textArea.getFirstLine(); - int visibleLines = textArea.getVisibleLines(); - int line = textArea.getCaretLine(); - - if(firstLine < visibleLines) - firstLine = visibleLines; - - textArea.setFirstLine(firstLine - visibleLines); - - int caret = textArea.getLineStartOffset( - Math.max(0,line - visibleLines)); - if(select) - textArea.select(textArea.getMarkPosition(),caret); - else - textArea.setCaretPosition(caret); - } - } - - public static class prev_word implements ActionListener - { - private boolean select; - - public prev_word(boolean select) - { - this.select = select; - } - - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - int caret = textArea.getCaretPosition(); - int line = textArea.getCaretLine(); - int lineStart = textArea.getLineStartOffset(line); - caret -= lineStart; - - String lineText = textArea.getLineText(textArea - .getCaretLine()); - - if(caret == 0) - { - if(lineStart == 0) - { - textArea.getToolkit().beep(); - return; - } - caret--; - } - else - { - String noWordSep = (String)textArea.getDocument().getProperty("noWordSep"); - caret = TextUtilities.findWordStart(lineText,caret,noWordSep); - } - - if(select) - textArea.select(textArea.getMarkPosition(), - lineStart + caret); - else - textArea.setCaretPosition(lineStart + caret); - } - } - - public static class repeat implements ActionListener, - InputHandler.NonRecordable - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - textArea.getInputHandler().setRepeatEnabled(true); - String actionCommand = evt.getActionCommand(); - if(actionCommand != null) - { - textArea.getInputHandler().setRepeatCount( - Integer.parseInt(actionCommand)); - } - } - } - - public static class toggle_rect implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - textArea.setSelectionRectangular( - !textArea.isSelectionRectangular()); - } - } - - - public static class clipboard_cut implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - getTextArea(evt).cut(); - } - } - - - public static class clipboard_copy implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - getTextArea(evt).copy(); - } - } - - - public static class clipboard_paste implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - getTextArea(evt).paste(); - } - } - - - public static class insert_char implements ActionListener, - InputHandler.NonRepeatable - { - public void actionPerformed(ActionEvent evt) - { - JEditTextArea textArea = getTextArea(evt); - String str = evt.getActionCommand(); - int repeatCount = textArea.getInputHandler().getRepeatCount(); - - if(textArea.isEditable()) - { - StringBuffer buf = new StringBuffer(); - for(int i = 0; i < repeatCount; i++) - buf.append(str); - textArea.overwriteSetSelectedText(buf.toString()); - } - else - { - textArea.getToolkit().beep(); - } - } - } -} diff --git a/app/src/processing/app/syntax/JEditTextArea.java b/app/src/processing/app/syntax/JEditTextArea.java deleted file mode 100644 index c12102038c8..00000000000 --- a/app/src/processing/app/syntax/JEditTextArea.java +++ /dev/null @@ -1,2448 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - * JEditTextArea.java - jEdit's text component - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import processing.app.*; - -import javax.swing.event.*; -import javax.swing.text.*; -import javax.swing.undo.*; -import javax.swing.*; - -import java.awt.datatransfer.*; -import java.awt.event.*; -import java.awt.*; -import java.util.Enumeration; -import java.util.Vector; -import java.awt.im.InputMethodRequests; - -import processing.app.syntax.im.InputMethodSupport; - -/** - * jEdit's text area component. It is more suited for editing program - * source code than JEditorPane, because it drops the unnecessary features - * (images, variable-width lines, and so on) and adds a whole bunch of - * useful goodies such as: - *

    - *
  • More flexible key binding scheme - *
  • Supports macro recorders - *
  • Rectangular selection - *
  • Bracket highlighting - *
  • Syntax highlighting - *
  • Command repetition - *
  • Block caret can be enabled - *
- * It is also faster and doesn't have as many problems. It can be used - * in other applications; the only other part of jEdit it depends on is - * the syntax package.

- * - * To use it in your app, treat it like any other component, for example: - *

JEditTextArea ta = new JEditTextArea();
- * ta.setTokenMarker(new JavaTokenMarker());
- * ta.setText("public class Test {\n"
- *     + "    public static void main(String[] args) {\n"
- *     + "        System.out.println(\"Hello World\");\n"
- *     + "    }\n"
- *     + "}");
- * - * @author Slava Pestov - */ -public class JEditTextArea extends JComponent -{ - /** - * Adding components with this name to the text area will place - * them left of the horizontal scroll bar. In jEdit, the status - * bar is added this way. - */ - public static String LEFT_OF_SCROLLBAR = "los"; - - /** - * Creates a new JEditTextArea with the default settings. - */ - /* - public JEditTextArea() - { - this(TextAreaDefaults.getDefaults()); - } - */ - - /** - * Creates a new JEditTextArea with the specified settings. - * @param defaults The default settings - */ - public JEditTextArea(TextAreaDefaults defaults) - { - // Enable the necessary events - enableEvents(AWTEvent.KEY_EVENT_MASK); - - // Initialize some misc. stuff - painter = new TextAreaPainter(this,defaults); - editorLineNumbers = new TextAreaLineNumbers(this,defaults); - documentHandler = new DocumentHandler(); - eventListenerList = new EventListenerList(); - caretEvent = new MutableCaretEvent(); - lineSegment = new Segment(); - bracketLine = bracketPosition = -1; - blink = true; - - // Initialize the GUI - setLayout(new ScrollLayout()); - add(LEFT, editorLineNumbers); - add(CENTER, painter); - add(RIGHT, vertical = new JScrollBar(JScrollBar.VERTICAL)); - add(BOTTOM, horizontal = new JScrollBar(JScrollBar.HORIZONTAL)); - - // Add some event listeners - vertical.addAdjustmentListener(new AdjustHandler()); - horizontal.addAdjustmentListener(new AdjustHandler()); - painter.addComponentListener(new ComponentHandler()); - painter.addMouseListener(new MouseHandler()); - painter.addMouseMotionListener(new DragHandler()); - addFocusListener(new FocusHandler()); - // send tab keys through to the text area - // http://dev.processing.org/bugs/show_bug.cgi?id=1267 - setFocusTraversalKeysEnabled(false); - - // Load the defaults - setInputHandler(defaults.inputHandler); - setDocument(defaults.document); - editable = defaults.editable; - caretVisible = defaults.caretVisible; - caretBlinks = defaults.caretBlinks; - electricScroll = defaults.electricScroll; - - // We don't seem to get the initial focus event? - focusedComponent = this; - - addMouseWheelListener(new MouseWheelListener() { - public void mouseWheelMoved(MouseWheelEvent e) { - if (!scrollBarsInitialized) return; - int amt = e.getWheelRotation(); - vertical.setValue(vertical.getValue() + amt * 3); - } - }); - } - - /** - * Inline Input Method Support for Japanese. - */ - private InputMethodSupport inputMethodSupport = null; - public InputMethodRequests getInputMethodRequests() { - if (inputMethodSupport == null) { - inputMethodSupport = new InputMethodSupport(this); - } - return inputMethodSupport; - } - - /** - * Get current position of the vertical scroll bar. [fry] - */ - public int getScrollPosition() { - return vertical.getValue(); - } - - - /** - * Set position of the vertical scroll bar. [fry] - */ - public void setScrollPosition(int what) { - vertical.setValue(what); - } - - - /** - * Returns if this component can be traversed by pressing - * the Tab key. This returns false. - */ -// public final boolean isManagingFocus() { -// return true; -// } - - /** - * Returns the object responsible for painting this text area. - */ - public final TextAreaPainter getPainter() { - return painter; - } - - /** - * Returns the input handler. - */ - public final InputHandler getInputHandler() { - return inputHandler; - } - - /** - * Sets the input handler. - * @param inputHandler The new input handler - */ - public void setInputHandler(InputHandler inputHandler) { - this.inputHandler = inputHandler; - } - - /** - * Returns true if the caret is blinking, false otherwise. - */ - public final boolean isCaretBlinkEnabled() { - return caretBlinks; - } - - /** - * Toggles caret blinking. - * @param caretBlinks True if the caret should blink, false otherwise - */ - public void setCaretBlinkEnabled(boolean caretBlinks) { - this.caretBlinks = caretBlinks; - if(!caretBlinks) - blink = false; - - painter.invalidateSelectedLines(); - } - - /** - * Returns true if the caret is visible, false otherwise. - */ - public final boolean isCaretVisible() { - return (!caretBlinks || blink) && caretVisible; - } - - /** - * Sets if the caret should be visible. - * @param caretVisible True if the caret should be visible, false - * otherwise - */ - public void setCaretVisible(boolean caretVisible) { - this.caretVisible = caretVisible; - blink = true; - - painter.invalidateSelectedLines(); - } - - /** - * Blinks the caret. - */ - public final void blinkCaret() { - if (caretBlinks) { - blink = !blink; - painter.invalidateSelectedLines(); - } else { - blink = true; - } - } - - /** - * Returns the number of lines from the top and button of the - * text area that are always visible. - */ - public final int getElectricScroll() { - return electricScroll; - } - - /** - * Sets the number of lines from the top and bottom of the text - * area that are always visible - * @param electricScroll The number of lines always visible from - * the top or bottom - */ - public final void setElectricScroll(int electricScroll) { - this.electricScroll = electricScroll; - } - - - /** - * Updates the state of the scroll bars. This should be called - * if the number of lines in the document changes, or when the - * size of the text are changes. - */ - public void updateScrollBars() { - if (vertical != null && visibleLines != 0) { - vertical.setValues(firstLine,visibleLines,0,getLineCount()); - vertical.setUnitIncrement(2); - vertical.setBlockIncrement(visibleLines); - } - - //if (horizontal != null && width != 0) { - if ((horizontal != null) && (painter.getWidth() != 0)) { - //int value = horizontal.getValue(); - //System.out.println("updateScrollBars"); - //int width = painter.getWidth(); - int lineCount = getLineCount(); - int maxLineLength = 0; - for (int i = 0; i < lineCount; i++) { - int lineLength = getLineLength(i); - if (lineLength > maxLineLength) { - maxLineLength = lineLength; - } - } - int charWidth = painter.getFontMetrics().charWidth('w'); - int width = maxLineLength * charWidth; - int painterWidth = painter.getWidth(); - //System.out.println("max line len " + maxLineLength); - //System.out.println("width " + width); - //System.out.println("text area width " + painter.getWidth()); - - // this was the default, but it's enormous - //horizontal.setValues(-horizontalOffset,width,0,width * 5); - - // something more reasonable, though this is a bad solution - //horizontal.setValues(-horizontalOffset,width,0,width * 2); - - // in general.. time to start looking at that other syntax pkg - // since most code should fit the window horizontally, just use - // the default settings for the width, this is a nicer solution - // until a better update mechanism can be implemented [fry] - - //horizontal.setValues(0, width, 0, width); - //0, width - horizontalOffset); - // works, from pre-75 versions of p5 - //horizontal.setValues(-horizontalOffset, width, 0, width); - - // gets weird when writing to the end of lines - //horizontal.setValues(value, painterWidth, 0, width); - - // seems to work, implemented for 0075 - horizontal.setValues(-horizontalOffset, painterWidth, 0, width); - - //horizontal.setUnitIncrement(painter.getFontMetrics().charWidth('w')); - horizontal.setUnitIncrement(charWidth); - horizontal.setBlockIncrement(width / 2); - } - updateLineNumbers(); - } - - private void updateLineNumbers() { - if (editorLineNumbers != null) { - editorLineNumbers.updateLineNumbers(getFirstLine() + 1, Math.min(getFirstLine() + getVisibleLines() + 1, getLineCount())); - editorLineNumbers.updateWidthForNumDigits(String.valueOf(getLineCount()).length()); - } - } - - /** - * Returns the line displayed at the text area's origin. - */ - public final int getFirstLine() { - return firstLine; - } - - /** - * Sets the line displayed at the text area's origin without - * updating the scroll bars. - */ - public void setFirstLine(int firstLine) { - if (firstLine == this.firstLine) return; - - this.firstLine = firstLine; - if (firstLine != vertical.getValue()) { - updateScrollBars(); - } - repaintEditor(); - } - - /** - * Returns the number of lines visible in this text area. - */ - public final int getVisibleLines() { - return visibleLines; - } - - /** - * Recalculates the number of visible lines. This should not - * be called directly. - */ - public final void recalculateVisibleLines() { - if (painter == null) return; - - int height = painter.getHeight(); - int lineHeight = painter.getFontMetrics().getHeight(); - visibleLines = height / lineHeight; - updateScrollBars(); - } - - /** - * Returns the horizontal offset of drawn lines. - */ - public final int getHorizontalOffset() { - return horizontalOffset; - } - - /** - * Sets the horizontal offset of drawn lines. This can be used to - * implement horizontal scrolling. - * @param horizontalOffset offset The new horizontal offset - */ - public void setHorizontalOffset(int horizontalOffset) - { - if(horizontalOffset == this.horizontalOffset) - return; - this.horizontalOffset = horizontalOffset; - if(horizontalOffset != horizontal.getValue()) - updateScrollBars(); - repaintEditor(); - } - - /** - * A fast way of changing both the first line and horizontal - * offset. - * @param firstLine The new first line - * @param horizontalOffset The new horizontal offset - * @return True if any of the values were changed, false otherwise - */ - public boolean setOrigin(int firstLine, int horizontalOffset) - { - boolean changed = false; - //int oldFirstLine = this.firstLine; - - if(horizontalOffset != this.horizontalOffset) - { - this.horizontalOffset = horizontalOffset; - changed = true; - } - - if(firstLine != this.firstLine) - { - this.firstLine = firstLine; - changed = true; - } - - if(changed) - { - updateScrollBars(); - repaintEditor(); - } - - return changed; - } - - private void repaintEditor() { - painter.repaint(); - updateLineNumbers(); - } - - /** - * Ensures that the caret is visible by scrolling the text area if - * necessary. - * @return True if scrolling was actually performed, false if the - * caret was already visible - */ - public boolean scrollToCaret() - { - int line = getCaretLine(); - int lineStart = getLineStartOffset(line); - int offset = Math.max(0,Math.min(getLineLength(line) - 1, - getCaretPosition() - lineStart)); - - return scrollTo(line,offset); - } - - /** - * Ensures that the specified line and offset is visible by scrolling - * the text area if necessary. - * @param line The line to scroll to - * @param offset The offset in the line to scroll to - * @return True if scrolling was actually performed, false if the - * line and offset was already visible - */ - public boolean scrollTo(int line, int offset) - { - // visibleLines == 0 before the component is realized - // we can't do any proper scrolling then, so we have - // this hack... - if (visibleLines == 0) { - setFirstLine(Math.max(0,line - electricScroll)); - return true; - } - - int newFirstLine = firstLine; - int newHorizontalOffset = horizontalOffset; - - if(line < firstLine + electricScroll) { - newFirstLine = Math.max(0,line - electricScroll); - - } else if(line + electricScroll >= firstLine + visibleLines) { - newFirstLine = (line - visibleLines) + electricScroll + 1; - if(newFirstLine + visibleLines >= getLineCount()) - newFirstLine = getLineCount() - visibleLines; - if(newFirstLine < 0) - newFirstLine = 0; - } - - int x = _offsetToX(line,offset); - int width = painter.getFontMetrics().charWidth('w'); - - if(x < 0) { - newHorizontalOffset = Math.min(0,horizontalOffset - x + width + 5); - } else if(x + width >= painter.getWidth()) { - newHorizontalOffset = horizontalOffset + - (painter.getWidth() - x) - width - 5; - } - - return setOrigin(newFirstLine,newHorizontalOffset); - } - - /** - * Converts a line index to a y co-ordinate. - * @param line The line - */ - public int lineToY(int line) - { - FontMetrics fm = painter.getFontMetrics(); - return (line - firstLine) * fm.getHeight() - - (fm.getLeading() + fm.getMaxDescent()); - } - - /** - * Converts a y co-ordinate to a line index. - * @param y The y co-ordinate - */ - public int yToLine(int y) - { - FontMetrics fm = painter.getFontMetrics(); - int height = fm.getHeight(); - return Math.max(0,Math.min(getLineCount() - 1, - y / height + firstLine)); - } - - /** - * Converts an offset in a line into an x co-ordinate. This is a - * slow version that can be used any time. - * @param line The line - * @param offset The offset, from the start of the line - */ - public final int offsetToX(int line, int offset) - { - // don't use cached tokens - painter.currentLineTokens = null; - return _offsetToX(line,offset); - } - - /** - * Converts an offset in a line into an x co-ordinate. This is a - * fast version that should only be used if no changes were made - * to the text since the last repaint. - * @param line The line - * @param offset The offset, from the start of the line - */ - public int _offsetToX(int line, int offset) - { - TokenMarker tokenMarker = getTokenMarker(); - - /* Use painter's cached info for speed */ - FontMetrics fm = painter.getFontMetrics(); - - getLineText(line,lineSegment); - - int segmentOffset = lineSegment.offset; - int x = horizontalOffset; - - /* If syntax coloring is disabled, do simple translation */ - if(tokenMarker == null) - { - lineSegment.count = offset; - return x + Utilities.getTabbedTextWidth(lineSegment, - fm,x,painter,0); - } - /* If syntax coloring is enabled, we have to do this because - * tokens can vary in width */ - else - { - Token tokens; - if(painter.currentLineIndex == line - && painter.currentLineTokens != null) - tokens = painter.currentLineTokens; - else - { - painter.currentLineIndex = line; - tokens = painter.currentLineTokens - = tokenMarker.markTokens(lineSegment,line); - } - - //Toolkit toolkit = painter.getToolkit(); - Font defaultFont = painter.getFont(); - SyntaxStyle[] styles = painter.getStyles(); - - for(;;) - { - byte id = tokens.id; - if(id == Token.END) - { - return x; - } - - if(id == Token.NULL) - fm = painter.getFontMetrics(); - else - fm = styles[id].getFontMetrics(defaultFont, this); - - int length = tokens.length; - - if(offset + segmentOffset < lineSegment.offset + length) - { - lineSegment.count = offset - (lineSegment.offset - segmentOffset); - return x + Utilities.getTabbedTextWidth( - lineSegment,fm,x,painter,0); - } - else - { - lineSegment.count = length; - x += Utilities.getTabbedTextWidth( - lineSegment,fm,x,painter,0); - lineSegment.offset += length; - } - tokens = tokens.next; - } - } - } - - /** - * Converts an x co-ordinate to an offset within a line. - * @param line The line - * @param x The x co-ordinate - */ - public int xToOffset(int line, int x) - { - TokenMarker tokenMarker = getTokenMarker(); - - /* Use painter's cached info for speed */ - FontMetrics fm = painter.getFontMetrics(); - - getLineText(line,lineSegment); - - char[] segmentArray = lineSegment.array; - int segmentOffset = lineSegment.offset; - int segmentCount = lineSegment.count; - - int width = horizontalOffset; - - if(tokenMarker == null) - { - for(int i = 0; i < segmentCount; i++) - { - char c = segmentArray[i + segmentOffset]; - int charWidth; - if(c == '\t') - charWidth = (int)painter.nextTabStop(width,i) - - width; - else - charWidth = fm.charWidth(c); - - if(painter.isBlockCaretEnabled()) - { - if(x - charWidth <= width) - return i; - } - else - { - if(x - charWidth / 2 <= width) - return i; - } - - width += charWidth; - } - - return segmentCount; - } - else - { - Token tokens; - if(painter.currentLineIndex == line && painter - .currentLineTokens != null) - tokens = painter.currentLineTokens; - else - { - painter.currentLineIndex = line; - tokens = painter.currentLineTokens - = tokenMarker.markTokens(lineSegment,line); - } - - int offset = 0; - //Toolkit toolkit = painter.getToolkit(); - Font defaultFont = painter.getFont(); - SyntaxStyle[] styles = painter.getStyles(); - - for(;;) - { - byte id = tokens.id; - if(id == Token.END) - return offset; - - if(id == Token.NULL) - fm = painter.getFontMetrics(); - else - fm = styles[id].getFontMetrics(defaultFont, this); - - int length = tokens.length; - - for(int i = 0; i < length; i++) - { - char c = segmentArray[segmentOffset + offset + i]; - int charWidth; - if(c == '\t') - charWidth = (int)painter.nextTabStop(width,offset + i) - - width; - else - charWidth = fm.charWidth(c); - - if(painter.isBlockCaretEnabled()) - { - if(x - charWidth <= width) - return offset + i; - } - else - { - if(x - charWidth / 2 <= width) - return offset + i; - } - - width += charWidth; - } - - offset += length; - tokens = tokens.next; - } - } - } - - /** - * Converts a point to an offset, from the start of the text. - * @param x The x co-ordinate of the point - * @param y The y co-ordinate of the point - */ - public int xyToOffset(int x, int y) - { - int line = yToLine(y); - int start = getLineStartOffset(line); - return start + xToOffset(line,x); - } - - /** - * Returns the document this text area is editing. - */ - public final SyntaxDocument getDocument() - { - return document; - } - - /** - * Sets the document this text area is editing. - * @param document The document - */ - public void setDocument(SyntaxDocument document) { - if (this.document == document) - return; - if (this.document != null) - this.document.removeDocumentListener(documentHandler); - this.document = document; - - document.addDocumentListener(documentHandler); - - select(0, 0); - updateScrollBars(); - repaintEditor(); - } - - - /** - * Set document with a twist, includes the old caret - * and scroll positions, added for p5. [fry] - */ - public void setDocument(SyntaxDocument document, - int start, int stop, int scroll) { - if (this.document == document) - return; - if (this.document != null) - this.document.removeDocumentListener(documentHandler); - this.document = document; - - document.addDocumentListener(documentHandler); - - select(start, stop); - updateScrollBars(); - setScrollPosition(scroll); - repaintEditor(); - } - - - /** - * Returns the document's token marker. Equivalent to calling - * getDocument().getTokenMarker(). - */ - public final TokenMarker getTokenMarker() - { - return document.getTokenMarker(); - } - - /** - * Sets the document's token marker. Equivalent to caling - * getDocument().setTokenMarker(). - * @param tokenMarker The token marker - */ - public final void setTokenMarker(TokenMarker tokenMarker) - { - document.setTokenMarker(tokenMarker); - } - - /** - * Returns the length of the document. Equivalent to calling - * getDocument().getLength(). - */ - public final int getDocumentLength() - { - return document.getLength(); - } - - /** - * Returns the number of lines in the document. - */ - public final int getLineCount() - { - if (document != null) { - return document.getDefaultRootElement().getElementCount(); - } else { - return 0; - } - } - - /** - * Returns the line containing the specified offset. - * @param offset The offset - */ - public final int getLineOfOffset(int offset) - { - return document.getDefaultRootElement().getElementIndex(offset); - } - - /** - * Returns the start offset of the specified line. - * @param line The line - * @return The start offset of the specified line, or -1 if the line is - * invalid - */ - public int getLineStartOffset(int line) - { - Element lineElement = document.getDefaultRootElement() - .getElement(line); - if(lineElement == null) - return -1; - else - return lineElement.getStartOffset(); - } - - /** - * Returns the end offset of the specified line. - * @param line The line - * @return The end offset of the specified line, or -1 if the line is - * invalid. - */ - public int getLineStopOffset(int line) - { - Element lineElement = document.getDefaultRootElement() - .getElement(line); - if(lineElement == null) - return -1; - else - return lineElement.getEndOffset(); - } - - /** - * Returns the end offset of the specified line, but not past the end of the text - * @param line The line - * @return The end offset of the specified line, safe to use for a selection, or -1 if the line is - * invalid. - */ - public int getSafeLineStopOffset(int line) - { - return Math.min(getLineStopOffset(line),getDocumentLength()); - } - - /** - * Returns the length of the specified line. - * @param line The line - */ - public int getLineLength(int line) - { - Element lineElement = document.getDefaultRootElement() - .getElement(line); - if(lineElement == null) - return -1; - else - return lineElement.getEndOffset() - - lineElement.getStartOffset() - 1; - } - - /** - * Returns the entire text of this text area. - */ - public String getText() - { - try - { - return document.getText(0,document.getLength()); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - return null; - } - } - - - /** - * Sets the entire text of this text area. - */ - public void setText(String text) - { - try { - document.beginCompoundEdit(); - document.remove(0,document.getLength()); - document.insertString(0,text,null); - - } catch (BadLocationException bl) { - bl.printStackTrace(); - - } finally { - document.endCompoundEdit(); - } - } - - - /** - * Returns the specified substring of the document. - * @param start The start offset - * @param len The length of the substring - * @return The substring, or null if the offsets are invalid - */ - public final String getText(int start, int len) - { - try - { - return document.getText(start,len); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - return null; - } - } - - /** - * Copies the specified substring of the document into a segment. - * If the offsets are invalid, the segment will contain a null string. - * @param start The start offset - * @param len The length of the substring - * @param segment The segment - */ - public final void getText(int start, int len, Segment segment) - { - try - { - document.getText(start,len,segment); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - segment.offset = segment.count = 0; - } - } - - /** - * Returns the text on the specified line. - * @param lineIndex The line - * @return The text, or null if the line is invalid - */ - public final String getLineText(int lineIndex) - { - int start = getLineStartOffset(lineIndex); - return getText(start,getLineStopOffset(lineIndex) - start - 1); - } - - /** - * Copies the text on the specified line into a segment. If the line - * is invalid, the segment will contain a null string. - * @param lineIndex The line - */ - public final void getLineText(int lineIndex, Segment segment) - { - int start = getLineStartOffset(lineIndex); - getText(start,getLineStopOffset(lineIndex) - start - 1,segment); - } - - /** - * Returns the selection start offset. - */ - public final int getSelectionStart() - { - return selectionStart; - } - - /** - * Returns the offset where the selection starts on the specified - * line. - */ - public int getSelectionStart(int line) - { - if(line == selectionStartLine) - return selectionStart; - else if(rectSelect) - { - Element map = document.getDefaultRootElement(); - int start = selectionStart - map.getElement(selectionStartLine) - .getStartOffset(); - - Element lineElement = map.getElement(line); - int lineStart = lineElement.getStartOffset(); - int lineEnd = lineElement.getEndOffset() - 1; - return Math.min(lineEnd,lineStart + start); - } - else - return getLineStartOffset(line); - } - - /** - * Returns the selection start line. - */ - public final int getSelectionStartLine() - { - return selectionStartLine; - } - - /** - * Sets the selection start. The new selection will be the new - * selection start and the old selection end. - * @param selectionStart The selection start - * @see #select(int,int) - */ - public final void setSelectionStart(int selectionStart) - { - select(selectionStart,selectionEnd); - } - - /** - * Returns the selection end offset. - */ - public final int getSelectionStop() - { - return selectionEnd; - } - - /** - * Returns the offset where the selection ends on the specified - * line. - */ - public int getSelectionStop(int line) - { - if(line == selectionEndLine) - return selectionEnd; - else if(rectSelect) - { - Element map = document.getDefaultRootElement(); - int end = selectionEnd - map.getElement(selectionEndLine) - .getStartOffset(); - - Element lineElement = map.getElement(line); - int lineStart = lineElement.getStartOffset(); - int lineEnd = lineElement.getEndOffset() - 1; - return Math.min(lineEnd,lineStart + end); - } - else - return getLineStopOffset(line) - 1; - } - - /** - * Returns the selection end line. - */ - public final int getSelectionStopLine() - { - return selectionEndLine; - } - - /** - * Sets the selection end. The new selection will be the old - * selection start and the bew selection end. - * @param selectionEnd The selection end - * @see #select(int,int) - */ - public final void setSelectionEnd(int selectionEnd) - { - select(selectionStart,selectionEnd); - } - - - public final boolean isSelectionActive() - { - return(selectionStart != selectionEnd); - } - - /** - * Returns the caret position. This will either be the selection - * start or the selection end, depending on which direction the - * selection was made in. - */ - public final int getCaretPosition() - { - return (biasLeft ? selectionStart : selectionEnd); - } - - /** - * Returns the caret line. - */ - public final int getCaretLine() - { - return (biasLeft ? selectionStartLine : selectionEndLine); - } - - /** - * Returns the mark position. This will be the opposite selection - * bound to the caret position. - * @see #getCaretPosition() - */ - public final int getMarkPosition() - { - return (biasLeft ? selectionEnd : selectionStart); - } - - /** - * Returns the mark line. - */ - public final int getMarkLine() - { - return (biasLeft ? selectionEndLine : selectionStartLine); - } - - /** - * Sets the caret position. The new selection will consist of the - * caret position only (hence no text will be selected) - * @param caret The caret position - * @see #select(int,int) - */ - public final void setCaretPosition(int caret) - { - select(caret,caret); - } - - /** - * Selects all text in the document. - */ - public final void selectAll() - { - select(0,getDocumentLength()); - } - - /** - * Moves the mark to the caret position. - */ - public final void selectNone() - { - select(getCaretPosition(),getCaretPosition()); - } - - /** - * Selects from the start offset to the end offset. This is the - * general selection method used by all other selecting methods. - * The caret position will be start if start < end, and end - * if end > start. - * @param start The start offset - * @param end The end offset - */ - public void select(int start, int end) - { - int newStart, newEnd; - boolean newBias; - if(start <= end) - { - newStart = start; - newEnd = end; - newBias = false; - } - else - { - newStart = end; - newEnd = start; - newBias = true; - } - - if (newEnd > getDocumentLength()) { - newEnd = getDocumentLength(); - } - - if(newStart < 0) - { - throw new IllegalArgumentException("Bounds out of" - + " range: " + newStart + "," + - newEnd + " [" + getDocumentLength() + "]"); - } - - // If the new position is the same as the old, we don't - // do all this crap, however we still do the stuff at - // the end (clearing magic position, scrolling) - if(newStart != selectionStart || newEnd != selectionEnd - || newBias != biasLeft) - { - int newStartLine = getLineOfOffset(newStart); - int newEndLine = getLineOfOffset(newEnd); - - if(painter.isBracketHighlightEnabled()) - { - if(bracketLine != -1) - painter.invalidateLine(bracketLine); - updateBracketHighlight(end); - if(bracketLine != -1) - painter.invalidateLine(bracketLine); - } - - painter.invalidateLineRange(selectionStartLine,selectionEndLine); - painter.invalidateLineRange(newStartLine,newEndLine); - - document.addUndoableEdit(new CaretUndo(selectionStart,selectionEnd)); - - selectionStart = newStart; - selectionEnd = newEnd; - selectionStartLine = newStartLine; - selectionEndLine = newEndLine; - biasLeft = newBias; - - if (newStart != newEnd) { - Clipboard unixclipboard = getToolkit().getSystemSelection(); - if (unixclipboard != null) { - String selection = getSelectedText(); - if (selection != null) { - unixclipboard.setContents(new StringSelection(selection), null); - } - } - } - - fireCaretEvent(); - } - - // When the user is typing, etc, we don't want the caret - // to blink - blink = true; - caretTimer.restart(); - - // Disable rectangle select if selection start = selection end - if(selectionStart == selectionEnd) - rectSelect = false; - - // Clear the `magic' caret position used by up/down - magicCaret = -1; - - scrollToCaret(); - - // notify the line number feller - if (editorLineStatus != null) { - editorLineStatus.set(selectionStartLine, selectionEndLine); - //System.out.println("why " + selectionStartLine + " " + selectionEndLine); - //System.out.println(getLineOfOffset(start) + " " + - // getLineOfOffset(end)); - } - } - - private boolean isWordCharacter( char ch, String noWordSep ) - { - return Character.isLetterOrDigit(ch) || ch=='_' || noWordSep.indexOf(ch) != -1; - } - - protected void setNewSelectionWord( int line, int offset ) - { - if (getLineLength(line) == 0) { - newSelectionStart = getLineStartOffset(line); - newSelectionEnd = newSelectionStart; - return; - } - - String noWordSep = (String)document.getProperty("noWordSep"); - if(noWordSep == null) - noWordSep = ""; - - String lineText = getLineText(line); - - int wordStart = 0; - int wordEnd = lineText.length(); - - char ch = lineText.charAt(Math.max(0,offset - 1)); - - // special case for whitespace (fry 0122, bug #348) - // this is really nasty.. turns out that double-clicking any non-letter - // or digit char gets lumped together.. sooo, this quickly gets messy, - // because really it needs to check whether the chars are of the same - // type.. so a double space or double - might be grouped together, - // but what about a +=1? do + and - get grouped but not the 1? blech, - // coming back to this later. it's not a difficult fix, just a - // time-consuming one to track down all the proper cases. - /* - if (ch == ' ') { - //System.out.println("yeehaa"); - - for(int i = offset - 1; i >= 0; i--) { - if (lineText.charAt(i) == ' ') { - wordStart = i; - } else { - break; - } - } - for(int i = offset; i < lineText.length(); i++) { - if (lineText.charAt(i) == ' ') { - wordEnd = i + 1; - } else { - break; - } - } - - } else { - */ - - // If the user clicked on a non-letter char, - // we select the surrounding non-letters - boolean selectNoLetter = !isWordCharacter(ch,noWordSep); - - for(int i = offset - 1; i >= 0; i--) { - ch = lineText.charAt(i); - if (selectNoLetter ^ !isWordCharacter(ch,noWordSep)) { - wordStart = i + 1; - break; - } - } - - for(int i = offset; i < lineText.length(); i++) { - ch = lineText.charAt(i); - if(selectNoLetter ^ !isWordCharacter(ch,noWordSep)) { - wordEnd = i; - break; - } - } - //} - int lineStart = getLineStartOffset(line); - - newSelectionStart = lineStart + wordStart; - newSelectionEnd = lineStart + wordEnd; - } - - - /** - * Returns the selected text, or null if no selection is active. - */ - public final String getSelectedText() - { - if(selectionStart == selectionEnd) - return null; - - if(rectSelect) - { - // Return each row of the selection on a new line - - Element map = document.getDefaultRootElement(); - - int start = selectionStart - map.getElement(selectionStartLine) - .getStartOffset(); - int end = selectionEnd - map.getElement(selectionEndLine) - .getStartOffset(); - - // Certain rectangles satisfy this condition... - if(end < start) - { - int tmp = end; - end = start; - start = tmp; - } - - StringBuffer buf = new StringBuffer(); - Segment seg = new Segment(); - - for(int i = selectionStartLine; i <= selectionEndLine; i++) - { - Element lineElement = map.getElement(i); - int lineStart = lineElement.getStartOffset(); - int lineEnd = lineElement.getEndOffset() - 1; - int lineLen = lineEnd - lineStart; - - lineStart = Math.min(lineStart + start,lineEnd); - lineLen = Math.min(end - start,lineEnd - lineStart); - - getText(lineStart,lineLen,seg); - buf.append(seg.array,seg.offset,seg.count); - - if(i != selectionEndLine) - buf.append('\n'); - } - - return buf.toString(); - } - else - { - return getText(selectionStart, - selectionEnd - selectionStart); - } - } - - /** - * Replaces the selection with the specified text. - * @param selectedText The replacement text for the selection - */ - public void setSelectedText(String selectedText) - { - if(!editable) - { - throw new InternalError("Text component" - + " read only"); - } - - document.beginCompoundEdit(); - - try - { - if(rectSelect) - { - Element map = document.getDefaultRootElement(); - - int start = selectionStart - map.getElement(selectionStartLine) - .getStartOffset(); - int end = selectionEnd - map.getElement(selectionEndLine) - .getStartOffset(); - - // Certain rectangles satisfy this condition... - if(end < start) - { - int tmp = end; - end = start; - start = tmp; - } - - int lastNewline = 0; - int currNewline = 0; - - for(int i = selectionStartLine; i <= selectionEndLine; i++) - { - Element lineElement = map.getElement(i); - int lineStart = lineElement.getStartOffset(); - int lineEnd = lineElement.getEndOffset() - 1; - int rectStart = Math.min(lineEnd,lineStart + start); - - document.remove(rectStart,Math.min(lineEnd - rectStart, - end - start)); - - if(selectedText == null) - continue; - - currNewline = selectedText.indexOf('\n',lastNewline); - if(currNewline == -1) - currNewline = selectedText.length(); - - document.insertString(rectStart,selectedText - .substring(lastNewline,currNewline),null); - - lastNewline = Math.min(selectedText.length(), - currNewline + 1); - } - - if(selectedText != null && - currNewline != selectedText.length()) - { - int offset = map.getElement(selectionEndLine) - .getEndOffset() - 1; - document.insertString(offset,"\n",null); - document.insertString(offset + 1,selectedText - .substring(currNewline + 1),null); - } - } - else - { - document.remove(selectionStart, - selectionEnd - selectionStart); - if(selectedText != null) - { - document.insertString(selectionStart, - selectedText,null); - } - } - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - throw new InternalError("Cannot replace" - + " selection"); - } - // No matter what happends... stops us from leaving document - // in a bad state - finally - { - document.endCompoundEdit(); - } - - setCaretPosition(selectionEnd); - } - - /** - * Returns true if this text area is editable, false otherwise. - */ - public final boolean isEditable() - { - return editable; - } - - /** - * Sets if this component is editable. - * @param editable True if this text area should be editable, - * false otherwise - */ - public final void setEditable(boolean editable) - { - this.editable = editable; - } - - /** - * Returns the right click popup menu. - */ - public final JPopupMenu getRightClickPopup() - { - return popup; - } - - /** - * Sets the right click popup menu. - * @param popup The popup - */ - //public final void setRightClickPopup(EditPopupMenu popup) - public final void setRightClickPopup(JPopupMenu popup) - { - this.popup = popup; - } - - - /** - * Returns the `magic' caret position. This can be used to preserve - * the column position when moving up and down lines. - */ - public final int getMagicCaretPosition() - { - return magicCaret; - } - - /** - * Sets the `magic' caret position. This can be used to preserve - * the column position when moving up and down lines. - * @param magicCaret The magic caret position - */ - public final void setMagicCaretPosition(int magicCaret) - { - this.magicCaret = magicCaret; - } - - /** - * Similar to setSelectedText(), but overstrikes the - * appropriate number of characters if overwrite mode is enabled. - * @param str The string - * @see #setSelectedText(String) - * @see #isOverwriteEnabled() - */ - public void overwriteSetSelectedText(String str) - { - // Don't overstrike if there is a selection - if(!overwrite || selectionStart != selectionEnd) - { - setSelectedText(str); - return; - } - - // Don't overstrike if we're on the end of - // the line - int caret = getCaretPosition(); - int caretLineEnd = getLineStopOffset(getCaretLine()); - if(caretLineEnd - caret <= str.length()) - { - setSelectedText(str); - return; - } - - document.beginCompoundEdit(); - - try - { - document.remove(caret,str.length()); - document.insertString(caret,str,null); - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - } - finally - { - document.endCompoundEdit(); - } - } - - /** - * Returns true if overwrite mode is enabled, false otherwise. - */ - public final boolean isOverwriteEnabled() - { - return overwrite; - } - - /** - * Sets if overwrite mode should be enabled. - * @param overwrite True if overwrite mode should be enabled, - * false otherwise. - */ - public final void setOverwriteEnabled(boolean overwrite) - { - this.overwrite = overwrite; - painter.invalidateSelectedLines(); - } - - /** - * Returns true if the selection is rectangular, false otherwise. - */ - public final boolean isSelectionRectangular() - { - return rectSelect; - } - - /** - * Sets if the selection should be rectangular. - * @param rectSelect True if the selection should be rectangular, - * false otherwise. - */ - public final void setSelectionRectangular(boolean rectSelect) - { - this.rectSelect = rectSelect; - painter.invalidateSelectedLines(); - } - - /** - * Returns the position of the highlighted bracket (the bracket - * matching the one before the caret) - */ - public final int getBracketPosition() - { - return bracketPosition; - } - - /** - * Returns the line of the highlighted bracket (the bracket - * matching the one before the caret) - */ - public final int getBracketLine() - { - return bracketLine; - } - - /** - * Adds a caret change listener to this text area. - * @param listener The listener - */ - public final void addCaretListener(CaretListener listener) - { - eventListenerList.add(CaretListener.class,listener); - } - - /** - * Removes a caret change listener from this text area. - * @param listener The listener - */ - public final void removeCaretListener(CaretListener listener) - { - eventListenerList.remove(CaretListener.class,listener); - } - - /** - * Deletes the selected text from the text area and places it - * into the clipboard. - */ - public void cut() - { - if(editable) - { - copy(); - setSelectedText(""); - } - } - - /** - * Places the selected text into the clipboard. - */ - public void copy() - { - if(selectionStart != selectionEnd) - { - Clipboard clipboard = getToolkit().getSystemClipboard(); - - String selection = getSelectedText(); - - int repeatCount = inputHandler.getRepeatCount(); - StringBuffer buf = new StringBuffer(); - for(int i = 0; i < repeatCount; i++) - buf.append(selection); - - Transferable t = new StringSelection(buf.toString()); - clipboard.setContents(t, null); - - Clipboard unixclipboard = getToolkit().getSystemSelection(); - if (unixclipboard != null) unixclipboard.setContents(t, null); - } - } - - /** - * Inserts the clipboard contents into the text. - */ - public void paste() { - if (editable) { - Clipboard clipboard = getToolkit().getSystemClipboard(); - try { - // The MacOS MRJ doesn't convert \r to \n, so do it here - String selection = ((String)clipboard.getContents(this).getTransferData(DataFlavor.stringFlavor)).replace('\r','\n'); - - // particularly on macosx when pasting from safari, - // replace unicode x00A0 (non-breaking space) - // with just a plain space. [fry 030929] - selection = selection.replace('\u00A0', ' '); - - int repeatCount = inputHandler.getRepeatCount(); - StringBuffer buf = new StringBuffer(); - for (int i = 0; i < repeatCount; i++) - buf.append(selection); - selection = buf.toString(); - setSelectedText(selection); - - } catch(Exception e) { - getToolkit().beep(); - System.err.println("Clipboard does not contain a string"); - } - } - } - - /** - * Called by the AWT when this component is removed from it's parent. - * This stops clears the currently focused component. - */ - public void removeNotify() - { - super.removeNotify(); - if(focusedComponent == this) - focusedComponent = null; - } - - /** - * Forwards key events directly to the input handler. - * This is slightly faster than using a KeyListener - * because some Swing overhead is avoided. - */ - public EditorListener editorListener; - - /** - * The component that tracks the current line number. - */ - public EditorLineStatus editorLineStatus; - - - public void processKeyEvent(KeyEvent evt) { - // this had to be added in Processing 007X, because the menu key - // events weren't making it up to the frame. - super.processKeyEvent(evt); - - //System.out.println("jedittextarea: " + evt); - //System.out.println(); - if (inputHandler == null) return; - - switch(evt.getID()) { - case KeyEvent.KEY_TYPED: - if ((editorListener == null) || !editorListener.keyTyped(evt)) { - inputHandler.keyTyped(evt); - } - break; - case KeyEvent.KEY_PRESSED: - if ((editorListener == null) || !editorListener.keyPressed(evt)) { - inputHandler.keyPressed(evt); - } - break; - case KeyEvent.KEY_RELEASED: - inputHandler.keyReleased(evt); - break; - } - } - - // protected members - protected static String LEFT = "left"; - protected static String CENTER = "center"; - protected static String RIGHT = "right"; - protected static String BOTTOM = "bottom"; - - protected static JEditTextArea focusedComponent; - protected static Timer caretTimer; - - protected TextAreaPainter painter; - protected TextAreaLineNumbers editorLineNumbers; - - //protected EditPopupMenu popup; - protected JPopupMenu popup; - - protected EventListenerList eventListenerList; - protected MutableCaretEvent caretEvent; - - protected boolean caretBlinks; - protected boolean caretVisible; - protected boolean blink; - - protected boolean editable; - - protected int firstLine; - protected int visibleLines; - protected int electricScroll; - - protected int horizontalOffset; - - protected JScrollBar vertical; - protected JScrollBar horizontal; - protected boolean scrollBarsInitialized; - - protected InputHandler inputHandler; - protected SyntaxDocument document; - protected DocumentHandler documentHandler; - - protected Segment lineSegment; - - protected int selectionStart; - protected int selectionStartLine; - protected int selectionEnd; - protected int selectionEndLine; - protected boolean biasLeft; - - protected int newSelectionStart; // hack to get around lack of multiple returns in Java - protected int newSelectionEnd; - - protected boolean selectWord; - protected boolean selectLine; - protected int selectionAncorStart; - protected int selectionAncorEnd; - - protected int bracketPosition; - protected int bracketLine; - - protected int magicCaret; - protected boolean overwrite; - protected boolean rectSelect; - - - protected void fireCaretEvent() - { - Object[] listeners = eventListenerList.getListenerList(); - for(int i = listeners.length - 2; i >= 0; i--) - { - if(listeners[i] == CaretListener.class) - { - ((CaretListener)listeners[i+1]).caretUpdate(caretEvent); - } - } - } - - protected void updateBracketHighlight(int newCaretPosition) - { - if(newCaretPosition == 0) - { - bracketPosition = bracketLine = -1; - return; - } - - try - { - int offset = TextUtilities.findMatchingBracket( - document,newCaretPosition - 1); - if(offset != -1) - { - bracketLine = getLineOfOffset(offset); - bracketPosition = offset - getLineStartOffset(bracketLine); - return; - } - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - } - - bracketLine = bracketPosition = -1; - } - - protected void documentChanged(DocumentEvent evt) - { - DocumentEvent.ElementChange ch = - evt.getChange(document.getDefaultRootElement()); - - int count; - if(ch == null) - count = 0; - else - count = ch.getChildrenAdded().length - - ch.getChildrenRemoved().length; - - int line = getLineOfOffset(evt.getOffset()); - if(count == 0) - { - painter.invalidateLine(line); - } - // do magic stuff - else if(line < firstLine) - { - setFirstLine(firstLine + count); - } - // end of magic stuff - else - { - painter.invalidateLineRange(line,firstLine + visibleLines); - updateScrollBars(); - } - } - - class ScrollLayout implements LayoutManager - { - //final int LEFT_EXTRA = 5; - - public void addLayoutComponent(String name, Component comp) - { - if(name.equals(LEFT)) - left = comp; - else if(name.equals(CENTER)) - center = comp; - else if(name.equals(RIGHT)) - right = comp; - else if(name.equals(BOTTOM)) - bottom = comp; - else if(name.equals(LEFT_OF_SCROLLBAR)) - leftOfScrollBar.addElement(comp); - } - - public void removeLayoutComponent(Component comp) - { - if(left == comp) - left = null; - if(center == comp) - center = null; - if(right == comp) - right = null; - if(bottom == comp) - bottom = null; - else - leftOfScrollBar.removeElement(comp); - } - - public Dimension preferredLayoutSize(Container parent) - { - Dimension dim = new Dimension(); - Insets insets = getInsets(); - dim.width = insets.left + insets.right; - dim.height = insets.top + insets.bottom; - - Dimension centerPref = center.getPreferredSize(); - dim.width += centerPref.width; - dim.height += centerPref.height; - Dimension leftPref = left.getPreferredSize(); - dim.width += leftPref.width; - Dimension rightPref = right.getPreferredSize(); - dim.width += rightPref.width; - Dimension bottomPref = bottom.getPreferredSize(); - dim.height += bottomPref.height; - - return dim; - } - - public Dimension minimumLayoutSize(Container parent) - { - Dimension dim = new Dimension(); - Insets insets = getInsets(); - dim.width = insets.left + insets.right; - dim.height = insets.top + insets.bottom; - - Dimension centerPref = center.getMinimumSize(); - dim.width += centerPref.width; - dim.height += centerPref.height; - Dimension leftPref = left.getMinimumSize(); - dim.width += leftPref.width; - Dimension rightPref = right.getMinimumSize(); - dim.width += rightPref.width; - Dimension bottomPref = bottom.getMinimumSize(); - dim.height += bottomPref.height; - - dim.height += 5; - - return dim; - } - - public void layoutContainer(Container parent) - { - Dimension size = parent.getSize(); - Insets insets = parent.getInsets(); - int itop = insets.top; - int ileft = insets.left; - int ibottom = insets.bottom; - int iright = insets.right; - - int leftWidth = left.getSize().width; - int rightWidth = right.getPreferredSize().width; - int bottomHeight = bottom.getPreferredSize().height; - int centerWidth = size.width - leftWidth - rightWidth - ileft - iright; - int centerHeight = size.height - bottomHeight - itop - ibottom; - - left.setBounds(ileft, - itop, - leftWidth, - centerHeight); - - ileft += leftWidth; - - center.setBounds(ileft, // + LEFT_EXTRA, - itop, - centerWidth, // - LEFT_EXTRA, - centerHeight); - - right.setBounds(ileft + centerWidth, - itop, - rightWidth, - centerHeight); - - // Lay out all status components, in order - Enumeration status = leftOfScrollBar.elements(); - while (status.hasMoreElements()) { - Component comp = (Component)status.nextElement(); - Dimension dim = comp.getPreferredSize(); - comp.setBounds(ileft, - itop + centerHeight, - dim.width, - bottomHeight); - ileft += dim.width; - } - - bottom.setBounds(ileft, - itop + centerHeight, - size.width - rightWidth - ileft - iright, - bottomHeight); - } - - // private members - private Component left; - private Component center; - private Component right; - private Component bottom; - private Vector leftOfScrollBar = new Vector(); - } - - static class CaretBlinker implements ActionListener - { - public void actionPerformed(ActionEvent evt) - { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if(focusedComponent != null - && focusedComponent.hasFocus()) - focusedComponent.blinkCaret(); - } - }); - } - } - - class MutableCaretEvent extends CaretEvent - { - MutableCaretEvent() - { - super(JEditTextArea.this); - } - - public int getDot() - { - return getCaretPosition(); - } - - public int getMark() - { - return getMarkPosition(); - } - } - -/* -#ifdef JDK14 - class WheelHandler implements MouseWheelListener { - - public void mouseWheelMoved(MouseWheelEvent e) { - if (!scrollBarsInitialized) return; - - int amt = e.getWheelRotation(); - //System.out.println(amt); - vertical.setValue(vertical.getValue() + amt * wheelMultiplier); - } - } -#endif -*/ - - class AdjustHandler implements AdjustmentListener - { - public void adjustmentValueChanged(final AdjustmentEvent evt) - { - if(!scrollBarsInitialized) - return; - - // If this is not done, mousePressed events accumilate - // and the result is that scrolling doesn't stop after - // the mouse is released - SwingUtilities.invokeLater(new Runnable() { - public void run() - { - if(evt.getAdjustable() == vertical) - setFirstLine(vertical.getValue()); - else - setHorizontalOffset(-horizontal.getValue()); - } - }); - } - } - - class ComponentHandler extends ComponentAdapter - { - public void componentResized(ComponentEvent evt) - { - recalculateVisibleLines(); - scrollBarsInitialized = true; - } - } - - class DocumentHandler implements DocumentListener - { - public void insertUpdate(DocumentEvent evt) - { - documentChanged(evt); - - int offset = evt.getOffset(); - int length = evt.getLength(); - - int newStart; - int newEnd; - - if (selectionStart > offset || - (selectionStart == selectionEnd && selectionStart == offset)) - newStart = selectionStart + length; - else - newStart = selectionStart; - - if(selectionEnd >= offset) - newEnd = selectionEnd + length; - else - newEnd = selectionEnd; - - select(newStart,newEnd); - } - - public void removeUpdate(DocumentEvent evt) - { - documentChanged(evt); - - int offset = evt.getOffset(); - int length = evt.getLength(); - - int newStart; - int newEnd; - - if(selectionStart > offset) - { - if(selectionStart > offset + length) - newStart = selectionStart - length; - else - newStart = offset; - } - else - newStart = selectionStart; - - if(selectionEnd > offset) - { - if(selectionEnd > offset + length) - newEnd = selectionEnd - length; - else - newEnd = offset; - } - else - newEnd = selectionEnd; - - select(newStart,newEnd); - } - - public void changedUpdate(DocumentEvent evt) - { - } - } - - class DragHandler implements MouseMotionListener - { - public void mouseDragged(MouseEvent evt) - { - if (popup != null && popup.isVisible()) return; - - if ( !selectWord && !selectLine ) { - setSelectionRectangular((evt.getModifiers() - & InputEvent.CTRL_MASK) != 0); - select(getMarkPosition(),xyToOffset(evt.getX(),evt.getY())); - } else { - int line = yToLine(evt.getY()); - if ( selectWord ) { - setNewSelectionWord( line, xToOffset(line,evt.getX()) ); - } else { - newSelectionStart = getLineStartOffset(line); - newSelectionEnd = getSafeLineStopOffset(line); - } - if ( newSelectionStart < selectionAncorStart ) { - select(newSelectionStart,selectionAncorEnd); - } else if ( newSelectionEnd > selectionAncorEnd ) { - select(selectionAncorStart,newSelectionEnd); - } else { - select(newSelectionStart,newSelectionEnd); - } - } - } - - final Cursor normalCursor = new Cursor(Cursor.DEFAULT_CURSOR); - final Cursor handCursor = new Cursor(Cursor.HAND_CURSOR); - - public void mouseMoved(MouseEvent evt) { - int line = yToLine(evt.getY()); - int offset = xToOffset(line, evt.getX()); - boolean wantHandCursor = checkClickedURL(getLineText(line), offset) != null; - JComponent src = (JComponent) evt.getSource(); - if (wantHandCursor) - src.setCursor(handCursor); - else - src.setCursor(normalCursor); - } - } - - class FocusHandler implements FocusListener - { - public void focusGained(FocusEvent evt) - { - //System.out.println("JEditTextArea: focusGained"); - setCaretVisible(true); - focusedComponent = JEditTextArea.this; - } - - public void focusLost(FocusEvent evt) - { - //System.out.println("JEditTextArea: focusLost"); - setCaretVisible(false); - focusedComponent = null; - } - } - - public String checkClickedURL(String line, int offset) { - String[] parse = SyntaxUtilities.parseCommentUrls(line); - if (parse==null) - return null; - int start = parse[0].length(); - int stop = start + parse[1].length(); - if (offsetstop) - return null; - return parse[1]; - } - - class MouseHandler extends MouseAdapter - { - public void mousePressed(MouseEvent evt) - { - requestFocus(); - - // Focus events not fired sometimes? - setCaretVisible(true); - focusedComponent = JEditTextArea.this; - - // isPopupTrigger wasn't working for danh on windows - boolean trigger = (evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0; - // but it's required for macosx, since control-click does - // the same thing as a right-mouse click - if (!trigger && evt.isPopupTrigger()) trigger = true; - - if (trigger && (popup != null)) { - popup.show(painter,evt.getX(),evt.getY()); - return; - } - - // on Linux, middle button pastes selected text - if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0) { - Clipboard unixclipboard = getToolkit().getSystemSelection(); - if (unixclipboard != null) { - Transferable t = unixclipboard.getContents(null); - if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) { - try { - String s = (String)t.getTransferData(DataFlavor.stringFlavor); - s = s.replace('\u00A0', ' '); - if (editable) setSelectedText(s); - } catch (Exception e) { - System.err.println(e); - e.printStackTrace(); - } - } - return; - } - } - - int line = yToLine(evt.getY()); - int offset = xToOffset(line,evt.getX()); - int dot = getLineStartOffset(line) + offset; - - selectLine = false; - selectWord = false; - - switch(evt.getClickCount()) { - - case 1: - doSingleClick(evt,line,offset,dot); - break; - - case 2: - // It uses the bracket matching stuff, so - // it can throw a BLE - try { - doDoubleClick(evt,line,offset,dot); - } catch(BadLocationException bl) { - bl.printStackTrace(); - } - break; - - case 3: - doTripleClick(evt,line,offset,dot); - break; - } - } - - - private void doSingleClick(MouseEvent evt, int line, - int offset, int dot) { - // Check for click on urls - String clickedURL = checkClickedURL(getLineText(line), offset); - if (clickedURL != null) { - Base.openURL(clickedURL); - return; - } - - if ((evt.getModifiers() & InputEvent.SHIFT_MASK) != 0) { - rectSelect = (evt.getModifiers() & InputEvent.CTRL_MASK) != 0; - select(getMarkPosition(),dot); - } else { - setCaretPosition(dot); - } - } - - - private void doDoubleClick(MouseEvent evt, int line, - int offset, int dot) throws BadLocationException - { - // Ignore empty lines - if (getLineLength(line) == 0) - return; - - try { - int bracket = TextUtilities.findMatchingBracket(document, - Math.max(0,dot - 1)); - if (bracket != -1) { - int mark = getMarkPosition(); - // Hack - if (bracket > mark) { - bracket++; - mark--; - } - select(mark,bracket); - return; - } - } catch(BadLocationException bl) { - bl.printStackTrace(); - } - - setNewSelectionWord( line, offset ); - select(newSelectionStart,newSelectionEnd); - selectWord = true; - selectionAncorStart = selectionStart; - selectionAncorEnd = selectionEnd; - - /* - String lineText = getLineText(line); - String noWordSep = (String)document.getProperty("noWordSep"); - int wordStart = TextUtilities.findWordStart(lineText,offset,noWordSep); - int wordEnd = TextUtilities.findWordEnd(lineText,offset,noWordSep); - - int lineStart = getLineStartOffset(line); - select(lineStart + wordStart,lineStart + wordEnd); - */ - } - - private void doTripleClick(MouseEvent evt, int line, - int offset, int dot) - { - selectLine = true; - select(getLineStartOffset(line),getSafeLineStopOffset(line)); - selectionAncorStart = selectionStart; - selectionAncorEnd = selectionEnd; - } - } - - class CaretUndo extends AbstractUndoableEdit - { - private int start; - private int end; - - CaretUndo(int start, int end) - { - this.start = start; - this.end = end; - } - - public boolean isSignificant() - { - return false; - } - - public String getPresentationName() - { - return "caret move"; - } - - public void undo() throws CannotUndoException - { - super.undo(); - - select(start,end); - } - - public void redo() throws CannotRedoException - { - super.redo(); - - select(start,end); - } - - public boolean addEdit(UndoableEdit edit) - { - if(edit instanceof CaretUndo) - { - CaretUndo cedit = (CaretUndo)edit; - start = cedit.start; - end = cedit.end; - cedit.die(); - - return true; - } - else - return false; - } - } - - static - { - caretTimer = new Timer(500,new CaretBlinker()); - caretTimer.setInitialDelay(500); - caretTimer.start(); - } - - public void setDisplayLineNumbers(boolean displayLineNumbers) { - editorLineNumbers.setDisplayLineNumbers(displayLineNumbers); - } -} diff --git a/app/src/processing/app/syntax/KeywordMap.java b/app/src/processing/app/syntax/KeywordMap.java deleted file mode 100644 index 27d225f8c0d..00000000000 --- a/app/src/processing/app/syntax/KeywordMap.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * KeywordMap.java - Fast keyword->id map - * Copyright (C) 1998, 1999 Slava Pestov - * Copyright (C) 1999 Mike Dillon - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.text.Segment; - -/** - * A KeywordMap is similar to a hashtable in that it maps keys - * to values. However, the `keys' are Swing segments. This allows lookups of - * text substrings without the overhead of creating a new string object. - *

- * This class is used by CTokenMarker to map keywords to ids. - * - * @author Slava Pestov, Mike Dillon - */ -public class KeywordMap -{ - /** - * Creates a new KeywordMap. - * @param ignoreCase True if keys are case insensitive - */ - public KeywordMap(boolean ignoreCase) - { - this(ignoreCase, 52); - this.ignoreCase = ignoreCase; - } - - /** - * Creates a new KeywordMap. - * @param ignoreCase True if the keys are case insensitive - * @param mapLength The number of `buckets' to create. - * A value of 52 will give good performance for most maps. - */ - public KeywordMap(boolean ignoreCase, int mapLength) - { - this.mapLength = mapLength; - this.ignoreCase = ignoreCase; - map = new Keyword[mapLength]; - } - - /** - * Looks up a key. - * @param text The text segment - * @param offset The offset of the substring within the text segment - * @param length The length of the substring - */ - public byte lookup(Segment text, int offset, int length) - { - if(length == 0) - return Token.NULL; - Keyword k = map[getSegmentMapKey(text, offset, length)]; - while(k != null) - { - if(length != k.keyword.length) - { - k = k.next; - continue; - } - if(SyntaxUtilities.regionMatches(ignoreCase,text,offset, - k.keyword)) - return k.id; - k = k.next; - } - return Token.NULL; - } - - /** - * Adds a key-value mapping. - * @param keyword The key - * @param id The value - */ - public void add(String keyword, byte id) - { - int key = getStringMapKey(keyword); - map[key] = new Keyword(keyword.toCharArray(),id,map[key]); - } - - /** - * Returns true if the keyword map is set to be case insensitive, - * false otherwise. - */ - public boolean getIgnoreCase() - { - return ignoreCase; - } - - /** - * Sets if the keyword map should be case insensitive. - * @param ignoreCase True if the keyword map should be case - * insensitive, false otherwise - */ - public void setIgnoreCase(boolean ignoreCase) - { - this.ignoreCase = ignoreCase; - } - - // protected members - protected int mapLength; - - protected int getStringMapKey(String s) - { - return (Character.toUpperCase(s.charAt(0)) + - Character.toUpperCase(s.charAt(s.length()-1))) - % mapLength; - } - - protected int getSegmentMapKey(Segment s, int off, int len) - { - return (Character.toUpperCase(s.array[off]) + - Character.toUpperCase(s.array[off + len - 1])) - % mapLength; - } - - // private members - class Keyword - { - public Keyword(char[] keyword, byte id, Keyword next) - { - this.keyword = keyword; - this.id = id; - this.next = next; - } - - public char[] keyword; - public byte id; - public Keyword next; - } - - private Keyword[] map; - private boolean ignoreCase; -} diff --git a/app/src/processing/app/syntax/PdeKeywords.java b/app/src/processing/app/syntax/PdeKeywords.java old mode 100644 new mode 100755 index ccf52531cfd..84868ee7382 --- a/app/src/processing/app/syntax/PdeKeywords.java +++ b/app/src/processing/app/syntax/PdeKeywords.java @@ -24,58 +24,59 @@ package processing.app.syntax; -import processing.app.*; -import processing.app.legacy.PApplet; -import processing.app.packages.Library; - -import java.io.*; -import java.util.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import org.fife.ui.rsyntaxtextarea.TokenTypes; -public class PdeKeywords extends CTokenMarker { +import processing.app.Base; +import processing.app.legacy.PApplet; +import processing.app.packages.Library; - // lookup table for the TokenMarker subclass, handles coloring - static KeywordMap keywordColoring; - // lookup table that maps keywords to their html reference pages - static Hashtable keywordToReference; +public class PdeKeywords { - public PdeKeywords() { - super(false, getKeywords()); + // Value is org.fife.ui.rsyntaxtextarea.TokenTypes + private static HashMap keywords = new HashMap(); + + private static HashMap keywordToReference = new HashMap(); + + + public static HashMap reload(){ + keywords.clear(); + keywordToReference.clear(); + return 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) { - try { - keywordColoring = new KeywordMap(false); - keywordToReference = new Hashtable(); - getKeywords(Base.getLibStream("keywords.txt")); - for (Library lib : Base.getLibraries()) { - File keywords = new File(lib.getFolder(), "keywords.txt"); - if (keywords.exists()) getKeywords(new FileInputStream(keywords)); + + public static HashMap get(){ + + if(keywords.isEmpty()){ + try { + load(Base.getLibStream("keywords.txt")); + if(Base.getLibraries() != null){ + for (Library lib : Base.getLibraries()) { + File keywords = new File(lib.getFolder(), "keywords.txt"); + if (keywords.exists()) load(new FileInputStream(keywords)); + } + } + } catch (Exception e) { + Base.showError("Problem loading keywords", + "Could not load keywords.txt,\n" + + "please re-install Arduino.", e); + System.exit(1); } - } catch (Exception e) { - Base.showError("Problem loading keywords", - "Could not load keywords.txt,\n" + - "please re-install Arduino.", e); - System.exit(1); - } } - return keywordColoring; + + return keywords; } - static private void getKeywords(InputStream input) throws Exception { + + static private void load(InputStream input) throws Exception { InputStreamReader isr = new InputStreamReader(input); BufferedReader reader = new BufferedReader(isr); @@ -109,7 +110,35 @@ static private void getKeywords(InputStream input) throws Exception { ((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; + +// if("setup".equals(keyword) || "loop".equals(keyword) || "void".equals(keyword) || "true".equals(keyword) ){ +// System.out.println(keyword + " -> " + coloring); +// } + + switch (id) { + case Token.KEYWORD1: // Datatypes + tokenType = TokenTypes.VARIABLE; + break; + case Token.KEYWORD2: // Methods and Functions + tokenType = TokenTypes.FUNCTION; + break; + case Token.KEYWORD3: // Class + tokenType = TokenTypes.DATA_TYPE; + break; + case Token.LITERAL1: // Constants + tokenType = TokenTypes.PREPROCESSOR; + break; + default: + break; + } + + if("setup".equals(keyword) || "loop".equals(keyword)){ + tokenType = TokenTypes.RESERVED_WORD; + } + + keywords.put(keyword, tokenType); } if (pieces.length >= 3) { String htmlFilename = pieces[2].trim(); @@ -121,9 +150,9 @@ static private void getKeywords(InputStream input) throws Exception { } reader.close(); } - - - 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/PdeTextAreaDefaults.java b/app/src/processing/app/syntax/PdeTextAreaDefaults.java deleted file mode 100644 index 2ff65afa8bc..00000000000 --- a/app/src/processing/app/syntax/PdeTextAreaDefaults.java +++ /dev/null @@ -1,211 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - PdeTextAreaDefaults - grabs font/color settings for the editor - Part of the Processing project - http://processing.org - - Copyright (c) 2004-06 Ben Fry and Casey Reas - Copyright (c) 2001-03 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.syntax; - -import processing.app.*; -import processing.app.helpers.OSUtils; - - -public class PdeTextAreaDefaults extends TextAreaDefaults { - - public PdeTextAreaDefaults() { - - inputHandler = new DefaultInputHandler(); - //inputHandler.addDefaultKeyBindings(); // 0122 - - // use option on mac for text edit controls that are ctrl on windows/linux - String mod = OSUtils.isMacOS() ? "A" : "C"; - - // right now, ctrl-up/down is select up/down, but mod should be - // used instead, because the mac expects it to be option(alt) - - inputHandler.addKeyBinding("BACK_SPACE", InputHandler.BACKSPACE); - // for 0122, shift-backspace is delete, for 0176, it's now a preference, - // to prevent holy warriors from attacking me for it. - if (Preferences.getBoolean("editor.keys.shift_backspace_is_delete")) { - inputHandler.addKeyBinding("S+BACK_SPACE", InputHandler.DELETE); - } else { - inputHandler.addKeyBinding("S+BACK_SPACE", InputHandler.BACKSPACE); - } - - inputHandler.addKeyBinding("DELETE", InputHandler.DELETE); - inputHandler.addKeyBinding("S+DELETE", InputHandler.DELETE); - - // the following two were changing for 0122 for better mac/pc compatability - inputHandler.addKeyBinding(mod+"+BACK_SPACE", InputHandler.BACKSPACE_WORD); - inputHandler.addKeyBinding(mod+"+DELETE", InputHandler.DELETE_WORD); - - // handled by listener, don't bother here - //inputHandler.addKeyBinding("ENTER", InputHandler.INSERT_BREAK); - //inputHandler.addKeyBinding("TAB", InputHandler.INSERT_TAB); - - inputHandler.addKeyBinding("INSERT", InputHandler.OVERWRITE); - - // http://dev.processing.org/bugs/show_bug.cgi?id=162 - // added for 0176, though the bindings do not appear relevant for osx - if (Preferences.getBoolean("editor.keys.alternative_cut_copy_paste")) { - inputHandler.addKeyBinding("C+INSERT", InputHandler.CLIPBOARD_COPY); - inputHandler.addKeyBinding("S+INSERT", InputHandler.CLIPBOARD_PASTE); - inputHandler.addKeyBinding("S+DELETE", InputHandler.CLIPBOARD_CUT); - } - - // disabling for 0122, not sure what this does - //inputHandler.addKeyBinding("C+\\", InputHandler.TOGGLE_RECT); - - // for 0122, these have been changed for better compatibility - // HOME and END now mean the beginning/end of the document - // for 0176 changed this to a preference so that the Mac OS X people - // can get the "normal" behavior as well if they prefer. - if (Preferences.getBoolean("editor.keys.home_and_end_travel_far")) { - inputHandler.addKeyBinding("HOME", InputHandler.DOCUMENT_HOME); - inputHandler.addKeyBinding("END", InputHandler.DOCUMENT_END); - inputHandler.addKeyBinding("S+HOME", InputHandler.SELECT_DOC_HOME); - inputHandler.addKeyBinding("S+END", InputHandler.SELECT_DOC_END); - } else { - // for 0123 added the proper windows defaults - inputHandler.addKeyBinding("HOME", InputHandler.HOME); - inputHandler.addKeyBinding("END", InputHandler.END); - inputHandler.addKeyBinding("S+HOME", InputHandler.SELECT_HOME); - inputHandler.addKeyBinding("S+END", InputHandler.SELECT_END); - inputHandler.addKeyBinding("C+HOME", InputHandler.DOCUMENT_HOME); - inputHandler.addKeyBinding("C+END", InputHandler.DOCUMENT_END); - inputHandler.addKeyBinding("CS+HOME", InputHandler.SELECT_DOC_HOME); - inputHandler.addKeyBinding("CS+END", InputHandler.SELECT_DOC_END); - } - - if (OSUtils.isMacOS()) { - inputHandler.addKeyBinding("M+LEFT", InputHandler.HOME); - inputHandler.addKeyBinding("M+RIGHT", InputHandler.END); - inputHandler.addKeyBinding("MS+LEFT", InputHandler.SELECT_HOME); // 0122 - inputHandler.addKeyBinding("MS+RIGHT", InputHandler.SELECT_END); // 0122 - } else { - inputHandler.addKeyBinding("C+LEFT", InputHandler.HOME); // 0122 - inputHandler.addKeyBinding("C+RIGHT", InputHandler.END); // 0122 - inputHandler.addKeyBinding("CS+HOME", InputHandler.SELECT_HOME); // 0122 - inputHandler.addKeyBinding("CS+END", InputHandler.SELECT_END); // 0122 - } - - inputHandler.addKeyBinding("PAGE_UP", InputHandler.PREV_PAGE); - inputHandler.addKeyBinding("PAGE_DOWN", InputHandler.NEXT_PAGE); - inputHandler.addKeyBinding("S+PAGE_UP", InputHandler.SELECT_PREV_PAGE); - inputHandler.addKeyBinding("S+PAGE_DOWN", InputHandler.SELECT_NEXT_PAGE); - - inputHandler.addKeyBinding("LEFT", InputHandler.PREV_CHAR); - inputHandler.addKeyBinding("S+LEFT", InputHandler.SELECT_PREV_CHAR); - inputHandler.addKeyBinding(mod + "+LEFT", InputHandler.PREV_WORD); - inputHandler.addKeyBinding(mod + "S+LEFT", InputHandler.SELECT_PREV_WORD); - inputHandler.addKeyBinding("RIGHT", InputHandler.NEXT_CHAR); - inputHandler.addKeyBinding("S+RIGHT", InputHandler.SELECT_NEXT_CHAR); - inputHandler.addKeyBinding(mod + "+RIGHT", InputHandler.NEXT_WORD); - inputHandler.addKeyBinding(mod + "S+RIGHT", InputHandler.SELECT_NEXT_WORD); - - inputHandler.addKeyBinding("UP", InputHandler.PREV_LINE); - inputHandler.addKeyBinding(mod + "+UP", InputHandler.PREV_LINE); // p5 - inputHandler.addKeyBinding("S+UP", InputHandler.SELECT_PREV_LINE); - inputHandler.addKeyBinding("DOWN", InputHandler.NEXT_LINE); - inputHandler.addKeyBinding(mod + "+DOWN", InputHandler.NEXT_LINE); // p5 - inputHandler.addKeyBinding("S+DOWN", InputHandler.SELECT_NEXT_LINE); - - inputHandler.addKeyBinding("MS+UP", InputHandler.SELECT_DOC_HOME); - inputHandler.addKeyBinding("CS+UP", InputHandler.SELECT_DOC_HOME); - inputHandler.addKeyBinding("MS+DOWN", InputHandler.SELECT_DOC_END); - inputHandler.addKeyBinding("CS+DOWN", InputHandler.SELECT_DOC_END); - - inputHandler.addKeyBinding(mod + "+ENTER", InputHandler.REPEAT); - - document = new SyntaxDocument(); - editable = true; - electricScroll = 3; - - cols = 80; - rows = 15; - - - // moved from SyntaxUtilities - //DEFAULTS.styles = SyntaxUtilities.getDefaultSyntaxStyles(); - - styles = new SyntaxStyle[Token.ID_COUNT]; - - // comments - styles[Token.COMMENT1] = Theme.getStyle("comment1"); - styles[Token.COMMENT2] = Theme.getStyle("comment2"); - - // abstract, final, private - styles[Token.KEYWORD1] = Theme.getStyle("keyword1"); - - // beginShape, point, line - styles[Token.KEYWORD2] = Theme.getStyle("keyword2"); - - // byte, char, short, color - styles[Token.KEYWORD3] = Theme.getStyle("keyword3"); - - // constants: null, true, this, RGB, TWO_PI - styles[Token.LITERAL1] = Theme.getStyle("literal1"); - - // p5 built in variables: mouseX, width, pixels - styles[Token.LITERAL2] = Theme.getStyle("literal2"); - - // ?? - styles[Token.LABEL] = Theme.getStyle("label"); - - // http://arduino.cc/ - styles[Token.URL] = Theme.getStyle("url"); - - // + - = / - styles[Token.OPERATOR] = Theme.getStyle("operator"); - - // area that's not in use by the text (replaced with tildes) - styles[Token.INVALID] = Theme.getStyle("invalid"); - - - // moved from TextAreaPainter - - font = Preferences.getFont("editor.font"); - - fgcolor = Theme.getColor("editor.fgcolor"); - bgcolor = Theme.getColor("editor.bgcolor"); - - caretVisible = true; - caretBlinks = Preferences.getBoolean("editor.caret.blink"); - caretColor = Theme.getColor("editor.caret.color"); - - selectionColor = Theme.getColor("editor.selection.color"); - - lineHighlight = - Theme.getBoolean("editor.linehighlight"); - lineHighlightColor = - Theme.getColor("editor.linehighlight.color"); - - bracketHighlight = - Theme.getBoolean("editor.brackethighlight"); - bracketHighlightColor = - Theme.getColor("editor.brackethighlight.color"); - - eolMarkers = Theme.getBoolean("editor.eolmarkers"); - eolMarkerColor = Theme.getColor("editor.eolmarkers.color"); - - paintInvalid = Theme.getBoolean("editor.invalid"); - } -} diff --git a/app/src/processing/app/syntax/SketchTextArea.java b/app/src/processing/app/syntax/SketchTextArea.java new file mode 100755 index 00000000000..44f7b063859 --- /dev/null +++ b/app/src/processing/app/syntax/SketchTextArea.java @@ -0,0 +1,338 @@ +package processing.app.syntax; + +import static processing.app.I18n._; + +import java.awt.AWTKeyStroke; +import java.awt.Dimension; +import java.awt.KeyboardFocusManager; +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +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.event.HyperlinkListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Segment; +import javax.swing.undo.UndoManager; + +import org.fife.ui.autocomplete.*; +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.EditorLineStatus; +import processing.app.I18n; +import processing.app.helpers.DocumentationUtils; +import processing.app.packages.Library; +import br.com.criativasoft.cpluslibparser.metadata.TAttribute; +import br.com.criativasoft.cpluslibparser.metadata.TFunction; +import cc.arduino.packages.autocomplete.CompletionsRenderer; +import cc.arduino.packages.autocomplete.RealtimeCompletionsListener; +import cc.arduino.packages.autocomplete.SketchCompletionProvider; +import cc.arduino.packages.autocomplete.TDynamicLocation; +import cc.arduino.packages.autocomplete.template.GenerateVarTemplate; +import cc.arduino.packages.autocomplete.template.IncludeTemplate; + +public class SketchTextArea extends RSyntaxTextArea { + + private final static Logger LOG = Logger.getLogger(SketchTextArea.class.getName()); + + private SketchCompletionProvider completionProvider; + + /** The last docTooltip displayed. */ + private FocusableTip docTooltip; + + /** + * The component that tracks the current line number. + */ + protected EditorLineStatus editorLineStatus; + + public SketchTextArea() { + super(); + installFeatures(); + } + + protected void installFeatures(){ + try { + Theme theme = Theme.load(Base.getLibStream("theme/syntax/default.xml")); + theme.apply(this); + } catch (IOException e) { + e.printStackTrace(); + } + + LinkGenerator generator = new LinkGenerator() { + + @Override + public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, final int offs) { + + // FIXME: This part is still in testing phase + + final Token token = textArea.modelToToken(offs); + + if(token != null && (token.getType() == TokenTypes.DATA_TYPE || token.getType() == TokenTypes.VARIABLE || token.getType() == TokenTypes.FUNCTION) ){ + + LinkGeneratorResult generatorResult = new LinkGeneratorResult() { + + @Override + public int getSourceOffset() { + LOG.finest("Linking: " + token); + return offs; + } + + @Override + public HyperlinkEvent execute() { + + String keyword = token.getLexeme(); + + String referce = PdeKeywords.getReference(keyword); + + LOG.fine("Open Reference: " + referce); + + Base.showReference(I18n.format(_("{0}.html"), referce)); + + return null; + } + }; + + return generatorResult; + } + + return null; + } + }; + + + setLinkGenerator(generator); + + 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 setupAutoComplete(SketchCompletionProvider provider) { + + if(this.completionProvider != null){ + completionProvider.uninstall(); + } + + this.completionProvider = provider; + AutoCompletion ac = new AutoCompletion(provider); + provider.setAutoCompletion(ac); + + ac.setShowDescWindow(true); + ac.setListCellRenderer(new CompletionsRenderer()); + // ac.setParamChoicesRenderer(r); + ac.setAutoCompleteSingleChoices(true); + ac.setParameterAssistanceEnabled(true); + + // NOTE: Monitor changes. On 'SketchCompletionProvider.onSketchInserted' was installed a DocumentListener + this.addKeyListener(new RealtimeCompletionsListener(provider.getSketch())); + + ac.addAutoCompletionListener(new AutoCompletionListener() { + @Override + public void autoCompleteUpdate(AutoCompletionEvent e) { + + if(e instanceof ParameterizedCompletionEvent){ + ParameterizedCompletionEvent event = (ParameterizedCompletionEvent) e; + + ParameterizedCompletion completion = event.getCompletion(); + + if(e.getEventType() == AutoCompletionEvent.Type.PARAMETER_COMPLETION_FINISH){ + List parameterValues = event.getContext().getParameterValues(); + + // Force parser/load new Lib for autocomplete. + if(completion instanceof IncludeTemplate && !parameterValues.isEmpty()){ + Library library = Base.getLibraries().getByName(parameterValues.get(0)); + completionProvider.getSketch().getData().addLibrary(library); + } + + if(completion instanceof GenerateVarTemplate && !parameterValues.isEmpty()){ + GenerateVarTemplate varTemplate = (GenerateVarTemplate) completion; + TAttribute var = new TAttribute(varTemplate.getType(), parameterValues.iterator().next()); + + // Insert new var in current function scope + TFunction functionScope = completionProvider.getCurrentFunctionScope(SketchTextArea.this); + if(functionScope != null){ + TDynamicLocation location = new TDynamicLocation(SketchTextArea.this.getDocument(), functionScope.getLocation()); + var.setLocation(location); + functionScope.addLocalVariable(var); + } + + } + + } + + if(e.getEventType() == AutoCompletionEvent.Type.PARAMETER_COMPLETION_SELECT){ + + } + } + } + }); + ac.install(this); + } + + public SketchCompletionProvider getCompletionProvider() { + return completionProvider; +} + + 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; + } + } + + + @Override + protected String getToolTipTextImpl(MouseEvent e) { + + // FIXME: This part is still in testing phase + // NOTA(R.R) - Já tem no : org.fife.ui.autocomplete.Completion.getToolTipText() , nãos sei como vai funcionar... + + if (e.isControlDown()) { + + Point p = e.getPoint(); + + final Token token = viewToToken(p); + + if(token != null && (token.getType() == TokenTypes.DATA_TYPE || token.getType() == TokenTypes.VARIABLE || token.getType() == TokenTypes.FUNCTION) ){ + + // System.out.println("getLexeme:" + token.getLexeme()); + + // Token paintableToken = getTokenListForLine(token.) + // System.out.println("getLastPaintableToken: " + paintableToken.getLexeme()); + + + String text = DocumentationUtils.getDocumentation(token.getLexeme(), null); + + // Do we want to use "focusable" tips? + if (getUseFocusableTips()) { + if (text!=null) { + if (docTooltip==null) { + docTooltip = new FocusableTip(this, new HyperlinkListener() { + + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + System.out.println("link clicked !"); + + } + }); + } + //docTooltip.setImageBase(imageBase); + docTooltip.toolTipRequested(e, text); + docTooltip.setMaxSize(new Dimension(500, 300)); + } + // No tool tip text at new location - hide tip window if one is + // currently visible + else if (docTooltip!=null) { + docTooltip.possiblyDisposeOfTipWindow(); + } + return null; + } + + } + + } + + return super.getToolTipTextImpl(e); + } + + +} diff --git a/app/src/processing/app/syntax/SketchTokenMaker.java b/app/src/processing/app/syntax/SketchTokenMaker.java new file mode 100755 index 00000000000..5ec2c1ebce3 --- /dev/null +++ b/app/src/processing/app/syntax/SketchTokenMaker.java @@ -0,0 +1,107 @@ +package processing.app.syntax; + +import org.fife.ui.rsyntaxtextarea.TokenMap; +import org.fife.ui.rsyntaxtextarea.TokenTypes; +import org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker; + +import processing.app.Base; + +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) { + 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); + +/* + 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/syntax/SyntaxDocument.java b/app/src/processing/app/syntax/SyntaxDocument.java deleted file mode 100644 index 2a1c3103ce9..00000000000 --- a/app/src/processing/app/syntax/SyntaxDocument.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * SyntaxDocument.java - Document that can be tokenized - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.event.*; -import javax.swing.text.*; -import javax.swing.undo.UndoableEdit; - -/** - * A document implementation that can be tokenized by the syntax highlighting - * system. - * - * @author Slava Pestov - */ -public class SyntaxDocument extends PlainDocument -{ - /** - * Returns the token marker that is to be used to split lines - * of this document up into tokens. May return null if this - * document is not to be colorized. - */ - public TokenMarker getTokenMarker() - { - return tokenMarker; - } - - /** - * Sets the token marker that is to be used to split lines of - * this document up into tokens. May throw an exception if - * this is not supported for this type of document. - * @param tm The new token marker - */ - public void setTokenMarker(TokenMarker tm) - { - tokenMarker = tm; - if(tm == null) - return; - tokenMarker.insertLines(0,getDefaultRootElement() - .getElementCount()); - tokenizeLines(); - } - - /** - * Reparses the document, by passing all lines to the token - * marker. This should be called after the document is first - * loaded. - */ - public void tokenizeLines() - { - tokenizeLines(0,getDefaultRootElement().getElementCount()); - } - - /** - * Reparses the document, by passing the specified lines to the - * token marker. This should be called after a large quantity of - * text is first inserted. - * @param start The first line to parse - * @param len The number of lines, after the first one to parse - */ - public void tokenizeLines(int start, int len) - { - if(tokenMarker == null || !tokenMarker.supportsMultilineTokens()) - return; - - Segment lineSegment = new Segment(); - Element map = getDefaultRootElement(); - - len += start; - - try - { - for(int i = start; i < len; i++) - { - Element lineElement = map.getElement(i); - int lineStart = lineElement.getStartOffset(); - getText(lineStart,lineElement.getEndOffset() - - lineStart - 1,lineSegment); - tokenMarker.markTokens(lineSegment,i); - } - } - catch(BadLocationException bl) - { - bl.printStackTrace(); - } - } - - /** - * Starts a compound edit that can be undone in one operation. - * Subclasses that implement undo should override this method; - * this class has no undo functionality so this method is - * empty. - */ - public void beginCompoundEdit() {} - - /** - * Ends a compound edit that can be undone in one operation. - * Subclasses that implement undo should override this method; - * this class has no undo functionality so this method is - * empty. - */ - public void endCompoundEdit() {} - - /** - * Adds an undoable edit to this document's undo list. The edit - * should be ignored if something is currently being undone. - * @param edit The undoable edit - * - * @since jEdit 2.2pre1 - */ - public void addUndoableEdit(UndoableEdit edit) {} - - // protected members - protected TokenMarker tokenMarker; - - /** - * We overwrite this method to update the token marker - * state immediately so that any event listeners get a - * consistent token marker. - */ - protected void fireInsertUpdate(DocumentEvent evt) - { - if(tokenMarker != null) - { - DocumentEvent.ElementChange ch = evt.getChange( - getDefaultRootElement()); - if(ch != null) - { - tokenMarker.insertLines(ch.getIndex() + 1, - ch.getChildrenAdded().length - - ch.getChildrenRemoved().length); - } - } - - super.fireInsertUpdate(evt); - } - - /** - * We overwrite this method to update the token marker - * state immediately so that any event listeners get a - * consistent token marker. - */ - protected void fireRemoveUpdate(DocumentEvent evt) - { - if(tokenMarker != null) - { - DocumentEvent.ElementChange ch = evt.getChange( - getDefaultRootElement()); - if(ch != null) - { - tokenMarker.deleteLines(ch.getIndex() + 1, - ch.getChildrenRemoved().length - - ch.getChildrenAdded().length); - } - } - - super.fireRemoveUpdate(evt); - } -} diff --git a/app/src/processing/app/syntax/SyntaxErrorMarker.java b/app/src/processing/app/syntax/SyntaxErrorMarker.java new file mode 100755 index 00000000000..f6c21cb9697 --- /dev/null +++ b/app/src/processing/app/syntax/SyntaxErrorMarker.java @@ -0,0 +1,154 @@ +package processing.app.syntax; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; + +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.rsyntaxtextarea.parser.*; +import org.fife.ui.rtextarea.Gutter; +import org.fife.ui.rtextarea.GutterIconInfo; + +import processing.app.Sketch; +import br.com.criativasoft.cpluslibparser.LibraryIndex; +import br.com.criativasoft.cpluslibparser.LibraryIndexListener; +import br.com.criativasoft.cpluslibparser.metadata.TError; +import br.com.criativasoft.cpluslibparser.metadata.TLibrary; +import cc.arduino.packages.autocomplete.CompletionType; +import cc.arduino.packages.autocomplete.CompletionsRenderer; +import cc.arduino.packages.autocomplete.SketchCodeScanner; +import cc.arduino.packages.autocomplete.SketchCompletionProvider; + + +/** + * Shows the result of the errors found after running the parser ({@link LibraryIndex}, {@link SketchCodeScanner}).
+ * Only well ugly syntax errors will be caught (because of the nature of the analysis to be made). + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public class SyntaxErrorMarker extends AbstractParser implements LibraryIndexListener { + + private TLibrary sketchLibrary; + private DefaultParseResult result; + private SketchTextArea textarea; + + // TODO: this can be removed on fix: https://github.com/bobbylight/RSyntaxTextArea/issues/87 + private Gutter gutter; + private List errorIcons = new ArrayList(); + + public SyntaxErrorMarker(SketchTextArea textarea) { + this.textarea = textarea; + this.gutter = RSyntaxUtilities.getGutter(textarea); + LibraryIndex.addListener(this); + result = new DefaultParseResult(this); + } + + @Override + public ParseResult parse(RSyntaxDocument doc, String style) { + + Element root = doc.getDefaultRootElement(); + int lineCount = root.getElementCount(); + + if(sketchLibrary != null){ + result.setParsedLines(0, lineCount-1); + } + + return result; + } + + + @Override + public void onLoadLibrary(TLibrary library) { + if(library.name().startsWith(Sketch.SKETCH_LIB_PREFIX)){ // Sketch indexing finish + sketchLibrary = library; + collectErrors(); + } + } + + + @Override + public void onUnloadLibrary(TLibrary library) { + if(library.name().startsWith(Sketch.SKETCH_LIB_PREFIX)){ // Sketch indexing finish + removeAll(); + } + } + + @Override + public void onReloadLibrary(TLibrary library) { + if(library.name().startsWith(SketchCompletionProvider.SKETCH_LIB_PREFIX)){ // Sketch indexing finish + sketchLibrary = library; + collectErrors(); + } + } + + private void collectErrors(){ + + if(sketchLibrary != null){ + + Set errors = sketchLibrary.getErrors(); + + result.clearNotices(); + + for (GutterIconInfo gutterIconInfo : errorIcons) { + gutter.removeTrackingIcon(gutterIconInfo); + } + + if(!errors.isEmpty()){ + for (TError error : errors) { + addError(error); + } + } + textarea.forceReparsing(this); + } + + } + + public void addError(TError error){ + ErrorParserNotice notice = new ErrorParserNotice(this, error); + result.addNotice(notice); + // TODO: This(gutter) can be removed on fix: RSyntaxTextArea/issues/87 + if(gutter != null){ + try { + GutterIconInfo errorIcon = gutter.addOffsetTrackingIcon(error.getStartOffset(), CompletionsRenderer.getIcon(CompletionType.ERROR), error.name()); + errorIcons.add(errorIcon); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + } + + public static class ErrorParserNotice extends DefaultParserNotice{ + + private TError error; + + public ErrorParserNotice(Parser parser, TError error) { + super(parser, error.getName(), -1, error.getStartOffset() , error.getLength()); + this.error = error; + setColor(Color.RED); + setShowInEditor(true); + } + + public TError getError() { + return error; + } + + @Override + public String getToolTipText() { + return error.getName(); + } + + } + + public void removeAll() { + result.clearNotices(); + textarea.forceReparsing(this); + for (GutterIconInfo gutterIconInfo : errorIcons) { + gutter.removeTrackingIcon(gutterIconInfo); + } + } + +} diff --git a/app/src/processing/app/syntax/SyntaxStyle.java b/app/src/processing/app/syntax/SyntaxStyle.java deleted file mode 100644 index 23a7c71b0a0..00000000000 --- a/app/src/processing/app/syntax/SyntaxStyle.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * SyntaxStyle.java - A simple text style class - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import java.awt.*; -import java.awt.font.TextAttribute; -import java.util.Hashtable; -import java.util.Map; - -import javax.swing.JComponent; - - -/** - * A simple text style class. It can specify the color, italic flag, - * and bold flag of a run of text. - * @author Slava Pestov - */ -public class SyntaxStyle -{ - /** - * Creates a new SyntaxStyle. - * @param color The text color - * @param italic True if the text should be italics - * @param bold True if the text should be bold - */ - public SyntaxStyle(Color color, boolean italic, boolean bold, boolean underlined) - { - this.color = color; - this.italic = italic; - this.bold = bold; - this.underlined = underlined; - } - - /** - * Returns the color specified in this style. - */ - public Color getColor() - { - return color; - } - - /** - * Returns true if no font styles are enabled. - */ - public boolean isPlain() - { - return !(bold || italic || underlined); - } - - /** - * Returns true if italics is enabled for this style. - */ - public boolean isItalic() - { - return italic; - } - - /** - * Returns true if boldface is enabled for this style. - */ - public boolean isBold() - { - return bold; - } - - /** - * @return true if underline is enabled for this style. - */ - public boolean isUnderlined() { - return underlined; - } - - /** - * Returns the specified font, but with the style's bold, underline and - * italic flags applied. - */ - public Font getStyledFont(Font font) - { - if(font == null) - throw new NullPointerException("font param must not" - + " be null"); - if(font.equals(lastFont)) - return lastStyledFont; - lastFont = font; - - lastStyledFont = new Font(font.getFamily(), - (bold ? Font.BOLD : 0) - | (italic ? Font.ITALIC : 0), - font.getSize()); - if (underlined) { - Map attr = new Hashtable(); - attr.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); - lastStyledFont = lastStyledFont.deriveFont(attr); - } - return lastStyledFont; - } - - /** - * Returns the font metrics for the styled font. - */ - public FontMetrics getFontMetrics(Font font, JComponent comp) - { - if(font == null) - throw new NullPointerException("font param must not" - + " be null"); - if(font.equals(lastFont) && fontMetrics != null) - return fontMetrics; - lastFont = font; - lastStyledFont = new Font(font.getFamily(), - (bold ? Font.BOLD : 0) - | (italic ? Font.ITALIC : 0), - font.getSize()); - if (underlined) { - Map attr = new Hashtable(); - attr.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); - lastStyledFont = lastStyledFont.deriveFont(attr); - } - //fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(lastStyledFont); - fontMetrics = comp.getFontMetrics(lastStyledFont); - return fontMetrics; - } - - /** - * Sets the foreground color and font of the specified graphics - * context to that specified in this style. - * @param gfx The graphics context - * @param font The font to add the styles to - */ - public void setGraphicsFlags(Graphics gfx, Font font) - { - Font _font = getStyledFont(font); - gfx.setFont(_font); - gfx.setColor(color); - } - - /** - * Returns a string representation of this object. - */ - public String toString() - { - return getClass().getName() + "[color=" + color + - (italic ? ",italic" : "") + - (bold ? ",bold" : "") + - (underlined ? ",underlined" : "") + - "]"; - } - - // private members - private Color color; - private boolean italic; - private boolean bold; - private boolean underlined; - private Font lastFont; - private Font lastStyledFont; - private FontMetrics fontMetrics; -} diff --git a/app/src/processing/app/syntax/SyntaxUtilities.java b/app/src/processing/app/syntax/SyntaxUtilities.java deleted file mode 100644 index 6eef977a08b..00000000000 --- a/app/src/processing/app/syntax/SyntaxUtilities.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * SyntaxUtilities.java - Utility functions used by syntax colorizing - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.text.*; -import java.awt.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - -/** - * Class with several utility functions used by jEdit's syntax colorizing - * subsystem. - * - * @author Slava Pestov - */ -public class SyntaxUtilities -{ - /** - * Checks if a subregion of a Segment is equal to a - * string. - * @param ignoreCase True if case should be ignored, false otherwise - * @param text The segment - * @param offset The offset into the segment - * @param match The string to match - */ - public static boolean regionMatches(boolean ignoreCase, Segment text, - int offset, String match) - { - int length = offset + match.length(); - char[] textArray = text.array; - if(length > text.offset + text.count) - return false; - for(int i = offset, j = 0; i < length; i++, j++) - { - char c1 = textArray[i]; - char c2 = match.charAt(j); - if(ignoreCase) - { - c1 = Character.toUpperCase(c1); - c2 = Character.toUpperCase(c2); - } - if(c1 != c2) - return false; - } - return true; - } - - - /** - * Checks if a subregion of a Segment is equal to a - * character array. - * @param ignoreCase True if case should be ignored, false otherwise - * @param text The segment - * @param offset The offset into the segment - * @param match The character array to match - */ - public static boolean regionMatches(boolean ignoreCase, Segment text, - int offset, char[] match) - { - int length = offset + match.length; - char[] textArray = text.array; - if(length > text.offset + text.count) - return false; - for(int i = offset, j = 0; i < length; i++, j++) - { - char c1 = textArray[i]; - char c2 = match[j]; - if(ignoreCase) - { - c1 = Character.toUpperCase(c1); - c2 = Character.toUpperCase(c2); - } - if(c1 != c2) - return false; - } - return true; - } - - - /** - * Returns the default style table. This can be passed to the - * setStyles() method of SyntaxDocument - * to use the default syntax styles. - */ - public static SyntaxStyle[] getDefaultSyntaxStyles() - { - SyntaxStyle[] styles = new SyntaxStyle[Token.ID_COUNT]; - - styles[Token.COMMENT1] = new SyntaxStyle(Color.black,true,false,false); - styles[Token.COMMENT2] = new SyntaxStyle(new Color(0x990033),true,false,false); - styles[Token.KEYWORD1] = new SyntaxStyle(Color.black,false,true,false); - styles[Token.KEYWORD2] = new SyntaxStyle(Color.magenta,false,false,false); - styles[Token.KEYWORD3] = new SyntaxStyle(new Color(0x009600),false,false,false); - styles[Token.LITERAL1] = new SyntaxStyle(new Color(0x650099),false,false,false); - styles[Token.LITERAL2] = new SyntaxStyle(new Color(0x650099),false,true,false); - styles[Token.LABEL] = new SyntaxStyle(new Color(0x990033),false,true,false); - styles[Token.OPERATOR] = new SyntaxStyle(Color.black,false,true,false); - styles[Token.URL] = new SyntaxStyle(Color.blue,true,false,false); - styles[Token.INVALID] = new SyntaxStyle(Color.red,false,true,false); - - return styles; - } - - - /** - * Paints the specified line onto the graphics context. Note that this - * method munges the offset and count values of the segment. - * @param line The line segment - * @param tokens The token list for the line - * @param styles The syntax style list - * @param expander The tab expander used to determine tab stops. May - * be null - * @param gfx The graphics context - * @param x The x co-ordinate - * @param y The y co-ordinate - * @return The x co-ordinate, plus the width of the painted string - */ - public static int paintSyntaxLine(Segment line, Token tokens, - SyntaxStyle[] styles, - TabExpander expander, Graphics gfx, - int x, int y) - { - Font defaultFont = gfx.getFont(); - Color defaultColor = gfx.getColor(); - - for(;;) - { - byte id = tokens.id; - if(id == Token.END) - break; - - int length = tokens.length; - if(id == Token.NULL) - { - if(!defaultColor.equals(gfx.getColor())) - gfx.setColor(defaultColor); - if(!defaultFont.equals(gfx.getFont())) - gfx.setFont(defaultFont); - } - else - styles[id].setGraphicsFlags(gfx,defaultFont); - - line.count = length; - if (id == Token.COMMENT1 || id == Token.COMMENT2) - x = drawTabbedCommentsText(line, x, y, gfx, expander, styles, styles[id]); - else - x = Utilities.drawTabbedText(line, x, y, gfx, expander, 0); - line.offset += length; - - tokens = tokens.next; - } - - return x; - } - - /** - * Parse comments and identify "@schematics <something>" pattern. - * - * @param line - * A string to parse - * @return null if the pattern is not found, otherwise an array of - * String is returned: the elements with index 0, 1 and 2 are - * respectively the preamble, the <something> stuff, and - * the remaining part of the string. - */ - public static String[] parseCommentUrls(String line) { - Matcher m = urlPattern.matcher(line.toString()); - if (!m.find()) - return null; - - String res[] = new String[3]; - res[0] = line.substring(0, m.start(1)); - res[1] = line.substring(m.start(1), m.end(1)); - res[2] = line.substring(m.end(1)); - // System.out.println("0 =>"+res[0]+"<\n1 =>"+res[1]+"< \n2 =>"+res[2]+"<"); - return res; - } - - static private Pattern urlPattern = Pattern.compile( - "((?:https?|ftp)://" + // ( Protocol - "(?:(?:[\\w_\\-]+:)?[\\w_\\-]+@)?" + // Username and password - "(?:[\\w_\\-]+\\.)+[\\w_\\-]+" + // Domain name - "(?::[0-9]{1,5})?" + // Port - "(?:/[\\w_\\-./?%&=+]*)?)" + // Path ) - "(?:\\s|$)"); // whitespace or EOL - - public static Segment stringToSegment(String v) { - return new Segment(v.toCharArray(), 0, v.length()); - } - - private static int drawTabbedCommentsText(Segment line, int x, int y, - Graphics gfx, TabExpander expander, SyntaxStyle[] styles, - SyntaxStyle commentStyle) { - - String parse[] = parseCommentUrls(line.toString()); - if (parse == null) - // Revert to plain writing. - return Utilities.drawTabbedText(line, x, y, gfx, expander, 0); - Segment pre = stringToSegment(parse[0]); - Segment tag = stringToSegment(parse[1]); - Segment post = stringToSegment(parse[2]); - - if (pre.count>0) - x = Utilities.drawTabbedText(pre, x, y, gfx, expander, 0); - - Font f = gfx.getFont(); - styles[Token.URL].setGraphicsFlags(gfx, f); - x = Utilities.drawTabbedText(tag, x, y, gfx, expander, 0); - - commentStyle.setGraphicsFlags(gfx, f); - if (post.count>0) - x = Utilities.drawTabbedText(post, x, y, gfx, expander, 0); - return x; - } - - // private members - private SyntaxUtilities() {} -} diff --git a/app/src/processing/app/syntax/TextAreaDefaults.java b/app/src/processing/app/syntax/TextAreaDefaults.java deleted file mode 100644 index c2e878578db..00000000000 --- a/app/src/processing/app/syntax/TextAreaDefaults.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * TextAreaDefaults.java - Encapsulates default values for various settings - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import java.awt.*; -//import javax.swing.JPopupMenu; - -/** - * Encapsulates default settings for a text area. This can be passed - * to the constructor once the necessary fields have been filled out. - * The advantage of doing this over calling lots of set() methods after - * creating the text area is that this method is faster. - */ -public class TextAreaDefaults -{ - private static TextAreaDefaults DEFAULTS; - - public InputHandler inputHandler; - public SyntaxDocument document; - public boolean editable; - - public boolean caretVisible; - public boolean caretBlinks; - public boolean blockCaret; - public int electricScroll; - - public int cols; - public int rows; - public SyntaxStyle[] styles; - public Color caretColor; - public Color selectionColor; - public Color lineHighlightColor; - public boolean lineHighlight; - public Color bracketHighlightColor; - public boolean bracketHighlight; - public Color eolMarkerColor; - public boolean eolMarkers; - public boolean paintInvalid; - - - // moved from TextAreaPainter [fry] - public Font font; - public Color fgcolor; - public Color bgcolor; - - //public JPopupMenu popup; - - - /** - * Returns a new TextAreaDefaults object with the default values filled - * in. - */ - public static TextAreaDefaults getDefaults() - { - if (DEFAULTS == null) { - DEFAULTS = new TextAreaDefaults(); - - DEFAULTS.inputHandler = new DefaultInputHandler(); - DEFAULTS.inputHandler.addDefaultKeyBindings(); - DEFAULTS.document = new SyntaxDocument(); - DEFAULTS.editable = true; - - DEFAULTS.caretVisible = true; - DEFAULTS.caretBlinks = true; - DEFAULTS.electricScroll = 3; - - DEFAULTS.cols = 80; - DEFAULTS.rows = 25; - DEFAULTS.styles = SyntaxUtilities.getDefaultSyntaxStyles(); - DEFAULTS.caretColor = Color.red; - DEFAULTS.selectionColor = new Color(0xccccff); - DEFAULTS.lineHighlightColor = new Color(0xe0e0e0); - DEFAULTS.lineHighlight = true; - DEFAULTS.bracketHighlightColor = Color.black; - DEFAULTS.bracketHighlight = true; - DEFAULTS.eolMarkerColor = new Color(0x009999); - DEFAULTS.eolMarkers = true; - DEFAULTS.paintInvalid = true; - } - - return DEFAULTS; - } -} diff --git a/app/src/processing/app/syntax/TextAreaLineNumbers.java b/app/src/processing/app/syntax/TextAreaLineNumbers.java deleted file mode 100644 index 39f7438f281..00000000000 --- a/app/src/processing/app/syntax/TextAreaLineNumbers.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * TextAreaLineNumbers.java - Show line numbers for the open file in the editor - * Copyright (C) 2013 Cayci Gorlitsky - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Rectangle; - -import javax.swing.border.MatteBorder; - -public class TextAreaLineNumbers extends TextAreaPainter { - - private final int LEFT_INDENT = 6; - private final int RIGHT_INDENT = 6; - private final int RIGHT_BORDER_WIDTH = 1; - private final int PADDING_WIDTH = LEFT_INDENT + RIGHT_INDENT + RIGHT_BORDER_WIDTH; - - private final int MIN_WIDTH; - private final int DIGIT_WIDTH; - private final int MIN_NUM_DIGITS = 2; - - private int currStartNum = 0; - private int currEndNum = 0; - private int currNumDigits = MIN_NUM_DIGITS; - - - - public TextAreaLineNumbers(JEditTextArea textArea, TextAreaDefaults defaults) { - super(textArea, defaults); - DIGIT_WIDTH = getFontMetrics(getFont()).stringWidth("0"); - MIN_WIDTH = DIGIT_WIDTH * MIN_NUM_DIGITS + PADDING_WIDTH; - setEnabled(false); - setBorder(new MatteBorder(0, 0, 0, RIGHT_BORDER_WIDTH, new Color(240, 240, 240))); - } - - public void updateLineNumbers(int startNum, int endNum) { - if (currStartNum == startNum && currEndNum == endNum) { - return; - } - currStartNum = startNum; - currEndNum = endNum; - - invalidate(); - repaint(); - } - - @Override - public void paint(Graphics gfx) { - super.paint(gfx); - getBorder().paintBorder(this, gfx, 0, 0, getSize().width, getSize().height); - } - - @Override - protected void paintLine(Graphics gfx, TokenMarker tokenMarker, - int line, int x) - { - currentLineIndex = line; - gfx.setFont(getFont()); - gfx.setColor(Color.GRAY); - int y = textArea.lineToY(line); - int startX = getBounds().x + getBounds().width; - if (line >= 0 && line < textArea.getLineCount()) { - String lineNumberString = String.valueOf(line+1); - int lineStartX = startX - RIGHT_BORDER_WIDTH - RIGHT_INDENT - fm.stringWidth(lineNumberString); - gfx.drawString(lineNumberString,lineStartX,y + fm.getHeight()); - } - } - - public void updateWidthForNumDigits(int numDigits) { - if (currNumDigits == numDigits) { - return; - } - currNumDigits = numDigits; - - if (isVisible()) { - updateBounds(); - invalidate(); - repaint(); - } - } - - public void setDisplayLineNumbers(boolean displayLineNumbers) { - setVisible(displayLineNumbers); - if (displayLineNumbers) { - updateBounds(); - } else { - setBounds(new Rectangle(0, getHeight())); - } - invalidate(); - repaint(); - } - - private void updateBounds() { - if (isVisible()) { - setBounds(new Rectangle(Math.max(MIN_WIDTH, DIGIT_WIDTH * currNumDigits + PADDING_WIDTH), getHeight())); - textArea.validate(); - } - } -} diff --git a/app/src/processing/app/syntax/TextAreaPainter.java b/app/src/processing/app/syntax/TextAreaPainter.java deleted file mode 100644 index e9932952604..00000000000 --- a/app/src/processing/app/syntax/TextAreaPainter.java +++ /dev/null @@ -1,787 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - * TextAreaPainter.java - Paints the text area - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import processing.app.*; -import processing.app.syntax.im.CompositionTextPainter; - -import javax.swing.ToolTipManager; -import javax.swing.text.*; -import javax.swing.JComponent; -import java.awt.event.MouseEvent; -import java.awt.*; -import java.awt.print.*; - -/** - * The text area repaint manager. It performs double buffering and paints - * lines of text. - * @author Slava Pestov - */ -public class TextAreaPainter extends JComponent -implements TabExpander, Printable -{ - /** True if inside printing, will handle disabling the highlight */ - boolean printing; - /** Current setting for editor.antialias preference */ - boolean antialias; - - /** A specific painter composed by the InputMethod.*/ - protected CompositionTextPainter compositionTextPainter; - - /** - * Creates a new repaint manager. This should be not be called - * directly. - */ - public TextAreaPainter(JEditTextArea textArea, TextAreaDefaults defaults) - { - this.textArea = textArea; - - setAutoscrolls(true); - setDoubleBuffered(true); - setOpaque(true); - - ToolTipManager.sharedInstance().registerComponent(this); - - currentLine = new Segment(); - currentLineIndex = -1; - - setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); - - setFont(defaults.font); - setForeground(defaults.fgcolor); - setBackground(defaults.bgcolor); - - antialias = Preferences.getBoolean("editor.antialias"); - - blockCaret = defaults.blockCaret; - styles = defaults.styles; - cols = defaults.cols; - rows = defaults.rows; - caretColor = defaults.caretColor; - selectionColor = defaults.selectionColor; - lineHighlightColor = defaults.lineHighlightColor; - lineHighlight = defaults.lineHighlight; - bracketHighlightColor = defaults.bracketHighlightColor; - bracketHighlight = defaults.bracketHighlight; - paintInvalid = defaults.paintInvalid; - eolMarkerColor = defaults.eolMarkerColor; - eolMarkers = defaults.eolMarkers; - } - - /** - * Get CompositionTextPainter. if CompositionTextPainter is not created, create it. - */ - public CompositionTextPainter getCompositionTextpainter(){ - if(compositionTextPainter == null){ - compositionTextPainter = new CompositionTextPainter(textArea); - } - return compositionTextPainter; - } - - /** - * Returns if this component can be traversed by pressing the - * Tab key. This returns false. - */ -// public final boolean isManagingFocus() -// { -// return false; -// } - - /** - * Returns the syntax styles used to paint colorized text. Entry n - * will be used to paint tokens with id = n. - * @see processing.app.syntax.Token - */ - public final SyntaxStyle[] getStyles() - { - return styles; - } - - /** - * Sets the syntax styles used to paint colorized text. Entry n - * will be used to paint tokens with id = n. - * @param styles The syntax styles - * @see processing.app.syntax.Token - */ - public final void setStyles(SyntaxStyle[] styles) - { - this.styles = styles; - repaint(); - } - - /** - * Returns the caret color. - */ - public final Color getCaretColor() - { - return caretColor; - } - - /** - * Sets the caret color. - * @param caretColor The caret color - */ - public final void setCaretColor(Color caretColor) - { - this.caretColor = caretColor; - invalidateSelectedLines(); - } - - /** - * Returns the selection color. - */ - public final Color getSelectionColor() - { - return selectionColor; - } - - /** - * Sets the selection color. - * @param selectionColor The selection color - */ - public final void setSelectionColor(Color selectionColor) - { - this.selectionColor = selectionColor; - invalidateSelectedLines(); - } - - /** - * Returns the line highlight color. - */ - public final Color getLineHighlightColor() - { - return lineHighlightColor; - } - - /** - * Sets the line highlight color. - * @param lineHighlightColor The line highlight color - */ - public final void setLineHighlightColor(Color lineHighlightColor) - { - this.lineHighlightColor = lineHighlightColor; - invalidateSelectedLines(); - } - - /** - * Returns true if line highlight is enabled, false otherwise. - */ - public final boolean isLineHighlightEnabled() - { - return lineHighlight; - } - - /** - * Enables or disables current line highlighting. - * @param lineHighlight True if current line highlight - * should be enabled, false otherwise - */ - public final void setLineHighlightEnabled(boolean lineHighlight) - { - this.lineHighlight = lineHighlight; - invalidateSelectedLines(); - } - - /** - * Returns the bracket highlight color. - */ - public final Color getBracketHighlightColor() - { - return bracketHighlightColor; - } - - /** - * Sets the bracket highlight color. - * @param bracketHighlightColor The bracket highlight color - */ - public final void setBracketHighlightColor(Color bracketHighlightColor) - { - this.bracketHighlightColor = bracketHighlightColor; - invalidateLine(textArea.getBracketLine()); - } - - /** - * Returns true if bracket highlighting is enabled, false otherwise. - * When bracket highlighting is enabled, the bracket matching the - * one before the caret (if any) is highlighted. - */ - public final boolean isBracketHighlightEnabled() - { - return bracketHighlight; - } - - /** - * Enables or disables bracket highlighting. - * When bracket highlighting is enabled, the bracket matching the - * one before the caret (if any) is highlighted. - * @param bracketHighlight True if bracket highlighting should be - * enabled, false otherwise - */ - public final void setBracketHighlightEnabled(boolean bracketHighlight) - { - this.bracketHighlight = bracketHighlight; - invalidateLine(textArea.getBracketLine()); - } - - /** - * Returns true if the caret should be drawn as a block, false otherwise. - */ - public final boolean isBlockCaretEnabled() - { - return blockCaret; - } - - /** - * Sets if the caret should be drawn as a block, false otherwise. - * @param blockCaret True if the caret should be drawn as a block, - * false otherwise. - */ - public final void setBlockCaretEnabled(boolean blockCaret) - { - this.blockCaret = blockCaret; - invalidateSelectedLines(); - } - - /** - * Returns the EOL marker color. - */ - public final Color getEOLMarkerColor() - { - return eolMarkerColor; - } - - /** - * Sets the EOL marker color. - * @param eolMarkerColor The EOL marker color - */ - public final void setEOLMarkerColor(Color eolMarkerColor) - { - this.eolMarkerColor = eolMarkerColor; - repaint(); - } - - /** - * Returns true if EOL markers are drawn, false otherwise. - */ - public final boolean getEOLMarkersPainted() - { - return eolMarkers; - } - - /** - * Sets if EOL markers are to be drawn. - * @param eolMarkers True if EOL markers should be drawn, false otherwise - */ - public final void setEOLMarkersPainted(boolean eolMarkers) - { - this.eolMarkers = eolMarkers; - repaint(); - } - - /** - * Returns true if invalid lines are painted as red tildes (~), - * false otherwise. - */ - public boolean getInvalidLinesPainted() - { - return paintInvalid; - } - - /** - * Sets if invalid lines are to be painted as red tildes. - * @param paintInvalid True if invalid lines should be drawn, false otherwise - */ - public void setInvalidLinesPainted(boolean paintInvalid) - { - this.paintInvalid = paintInvalid; - } - - /** - * Adds a custom highlight painter. - * @param highlight The highlight - */ - public void addCustomHighlight(Highlight highlight) - { - highlight.init(textArea,highlights); - highlights = highlight; - } - - /** - * Highlight interface. - */ - public interface Highlight - { - /** - * Called after the highlight painter has been added. - * @param textArea The text area - * @param next The painter this one should delegate to - */ - void init(JEditTextArea textArea, Highlight next); - - /** - * This should paint the highlight and delgate to the - * next highlight painter. - * @param gfx The graphics context - * @param line The line number - * @param y The y co-ordinate of the line - */ - void paintHighlight(Graphics gfx, int line, int y); - - /** - * Returns the tool tip to display at the specified - * location. If this highlighter doesn't know what to - * display, it should delegate to the next highlight - * painter. - * @param evt The mouse event - */ - String getToolTipText(MouseEvent evt); - } - - /** - * Returns the tool tip to display at the specified location. - * @param evt The mouse event - */ - public String getToolTipText(MouseEvent evt) - { - if(highlights != null) - return highlights.getToolTipText(evt); - else - return null; - } - - /** - * Returns the font metrics used by this component. - */ - public FontMetrics getFontMetrics() - { - return fm; - } - - /** - * Sets the font for this component. This is overridden to update the - * cached font metrics and to recalculate which lines are visible. - * @param font The font - */ - public void setFont(Font font) - { - super.setFont(font); - fm = super.getFontMetrics(font); - textArea.recalculateVisibleLines(); - } - - /** - * Repaints the text. - * @param gfx The graphics context - */ - public void paint(Graphics gfx) - { - Graphics2D g2 = (Graphics2D) gfx; - g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - antialias ? - RenderingHints.VALUE_TEXT_ANTIALIAS_ON : - RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); - - tabSize = fm.charWidth(' ') * ((Integer)textArea.getDocument().getProperty(PlainDocument.tabSizeAttribute)).intValue(); - - Rectangle clipRect = gfx.getClipBounds(); - - gfx.setColor(getBackground()); - gfx.fillRect(clipRect.x,clipRect.y,clipRect.width,clipRect.height); - - // We don't use yToLine() here because that method doesn't - // return lines past the end of the document - int height = fm.getHeight(); - int firstLine = textArea.getFirstLine(); - int firstInvalid = firstLine + clipRect.y / height; - // Because the clipRect's height is usually an even multiple - // of the font height, we subtract 1 from it, otherwise one - // too many lines will always be painted. - int lastInvalid = firstLine + (clipRect.y + clipRect.height - 1) / height; - - try { - TokenMarker tokenMarker = textArea.getDocument().getTokenMarker(); - int x = textArea.getHorizontalOffset(); - - for (int line = firstInvalid; line <= lastInvalid; line++) { - paintLine(gfx,tokenMarker,line,x); - } - - if (tokenMarker != null && tokenMarker.isNextLineRequested()) { - int h = clipRect.y + clipRect.height; - repaint(0,h,getWidth(),getHeight() - h); - } - } catch (Exception e) { - System.err.println("Error repainting line" - + " range {" + firstInvalid + "," - + lastInvalid + "}:"); - e.printStackTrace(); - } - } - - - public int print(Graphics g, PageFormat pageFormat, int pageIndex) { - int lineHeight = fm.getHeight(); - int linesPerPage = (int) (pageFormat.getImageableHeight() / lineHeight); - int lineCount = textArea.getLineCount(); - int lastPage = lineCount / linesPerPage; - - if (pageIndex > lastPage) { - return NO_SUCH_PAGE; - - } else { - Graphics2D g2d = (Graphics2D)g; - TokenMarker tokenMarker = textArea.getDocument().getTokenMarker(); - int firstLine = pageIndex*linesPerPage; - g2d.translate(Math.max(54, pageFormat.getImageableX()), - pageFormat.getImageableY() - firstLine*lineHeight); - printing = true; - for (int line = firstLine; line < firstLine + linesPerPage; line++) { - paintLine(g2d, tokenMarker, line, 0); - } - printing = false; - return PAGE_EXISTS; - } - } - - - /** - * Marks a line as needing a repaint. - * @param line The line to invalidate - */ - public final void invalidateLine(int line) - { - repaint(0,textArea.lineToY(line) + fm.getMaxDescent() + fm.getLeading(), - getWidth(),fm.getHeight()); - } - - /** - * Marks a range of lines as needing a repaint. - * @param firstLine The first line to invalidate - * @param lastLine The last line to invalidate - */ - public final void invalidateLineRange(int firstLine, int lastLine) - { - repaint(0,textArea.lineToY(firstLine) + - fm.getMaxDescent() + fm.getLeading(), - getWidth(),(lastLine - firstLine + 1) * fm.getHeight()); - } - - /** - * Repaints the lines containing the selection. - */ - public final void invalidateSelectedLines() - { - invalidateLineRange(textArea.getSelectionStartLine(), - textArea.getSelectionStopLine()); - } - - /** - * Implementation of TabExpander interface. Returns next tab stop after - * a specified point. - * @param x The x co-ordinate - * @param tabOffset Ignored - * @return The next tab stop after x - */ - public float nextTabStop(float x, int tabOffset) - { - int offset = textArea.getHorizontalOffset(); - int ntabs = ((int)x - offset) / tabSize; - return (ntabs + 1) * tabSize + offset; - } - - /** - * Returns the painter's preferred size. - */ - public Dimension getPreferredSize() - { - Dimension dim = new Dimension(); - dim.width = fm.charWidth('w') * cols; - dim.height = fm.getHeight() * rows; - return dim; - } - - - /** - * Returns the painter's minimum size. - */ - public Dimension getMinimumSize() - { - Dimension dim = new Dimension(); - dim.width = fm.charWidth('w') * 10; - dim.height = fm.getHeight() * 4; - return dim; - } - - // package-private members - int currentLineIndex; - Token currentLineTokens; - Segment currentLine; - - /** - * Accessor used by tools that want to hook in and grab the formatting. - */ - public int getCurrentLineIndex() { - return currentLineIndex; - } - - /** - * Accessor used by tools that want to hook in and grab the formatting. - */ - public void setCurrentLineIndex(int what) { - currentLineIndex = what; - } - - /** - * Accessor used by tools that want to hook in and grab the formatting. - */ - public Token getCurrentLineTokens() { - return currentLineTokens; - } - - /** - * Accessor used by tools that want to hook in and grab the formatting. - */ - public void setCurrentLineTokens(Token tokens) { - currentLineTokens = tokens; - } - - /** - * Accessor used by tools that want to hook in and grab the formatting. - */ - public Segment getCurrentLine() { - return currentLine; - } - - - // protected members - protected JEditTextArea textArea; - - protected SyntaxStyle[] styles; - protected Color caretColor; - protected Color selectionColor; - protected Color lineHighlightColor; - protected Color bracketHighlightColor; - protected Color eolMarkerColor; - - protected boolean blockCaret; - protected boolean lineHighlight; - protected boolean bracketHighlight; - protected boolean paintInvalid; - protected boolean eolMarkers; - protected int cols; - protected int rows; - - protected int tabSize; - protected FontMetrics fm; - - protected Highlight highlights; - - protected void paintLine(Graphics gfx, TokenMarker tokenMarker, - int line, int x) - { - Font defaultFont = getFont(); - Color defaultColor = getForeground(); - - currentLineIndex = line; - int y = textArea.lineToY(line); - - if (line < 0 || line >= textArea.getLineCount()) { - if (paintInvalid) { - paintHighlight(gfx,line,y); - styles[Token.INVALID].setGraphicsFlags(gfx,defaultFont); - gfx.drawString("~",0,y + fm.getHeight()); - } - } else if(tokenMarker == null) { - paintPlainLine(gfx,line,defaultFont,defaultColor,x,y); - } else { - paintSyntaxLine(gfx,tokenMarker,line,defaultFont, - defaultColor,x,y); - } - } - - protected void paintPlainLine(Graphics gfx, int line, Font defaultFont, - Color defaultColor, int x, int y) - { - paintHighlight(gfx,line,y); - textArea.getLineText(line,currentLine); - - gfx.setFont(defaultFont); - gfx.setColor(defaultColor); - - y += fm.getHeight(); - x = Utilities.drawTabbedText(currentLine,x,y,gfx,this,0); - /* - * Draw characters via input method. - */ - if (compositionTextPainter != null && compositionTextPainter.hasComposedTextLayout()) { - compositionTextPainter.draw(gfx, lineHighlightColor); - } - if (eolMarkers) { - gfx.setColor(eolMarkerColor); - gfx.drawString(".",x,y); - } - } - - protected void paintSyntaxLine(Graphics gfx, TokenMarker tokenMarker, - int line, Font defaultFont, - Color defaultColor, int x, int y) - { - textArea.getLineText(currentLineIndex,currentLine); - currentLineTokens = tokenMarker.markTokens(currentLine, - currentLineIndex); - - paintHighlight(gfx,line,y); - - gfx.setFont(defaultFont); - gfx.setColor(defaultColor); - y += fm.getHeight(); - x = SyntaxUtilities.paintSyntaxLine(currentLine, - currentLineTokens, - styles, this, gfx, x, y); - /* - * Draw characters via input method. - */ - if (compositionTextPainter != null && compositionTextPainter.hasComposedTextLayout()) { - compositionTextPainter.draw(gfx, lineHighlightColor); - } - if (eolMarkers) { - gfx.setColor(eolMarkerColor); - gfx.drawString(".",x,y); - } - } - - protected void paintHighlight(Graphics gfx, int line, int y) - { - if (!printing) { - if (line >= textArea.getSelectionStartLine() - && line <= textArea.getSelectionStopLine()) - paintLineHighlight(gfx,line,y); - - if (highlights != null) - highlights.paintHighlight(gfx,line,y); - - if (bracketHighlight && line == textArea.getBracketLine()) - paintBracketHighlight(gfx,line,y); - - if (line == textArea.getCaretLine()) - paintCaret(gfx,line,y); - } - } - - protected void paintLineHighlight(Graphics gfx, int line, int y) - { - int height = fm.getHeight(); - y += fm.getLeading() + fm.getMaxDescent(); - - int selectionStart = textArea.getSelectionStart(); - int selectionEnd = textArea.getSelectionStop(); - - if (selectionStart == selectionEnd) { - if (lineHighlight) { - gfx.setColor(lineHighlightColor); - gfx.fillRect(0,y,getWidth(),height); - } - } else { - gfx.setColor(selectionColor); - - int selectionStartLine = textArea.getSelectionStartLine(); - int selectionEndLine = textArea.getSelectionStopLine(); - int lineStart = textArea.getLineStartOffset(line); - - int x1, x2; - if (textArea.isSelectionRectangular()) { - int lineLen = textArea.getLineLength(line); - x1 = textArea._offsetToX(line,Math.min(lineLen, selectionStart - textArea.getLineStartOffset(selectionStartLine))); - x2 = textArea._offsetToX(line,Math.min(lineLen, selectionEnd - textArea.getLineStartOffset(selectionEndLine))); - if (x1 == x2) - x2++; - } else if(selectionStartLine == selectionEndLine) { - x1 = textArea._offsetToX(line, selectionStart - lineStart); - x2 = textArea._offsetToX(line, selectionEnd - lineStart); - } else if(line == selectionStartLine) { - x1 = textArea._offsetToX(line, selectionStart - lineStart); - x2 = getWidth(); - } else if(line == selectionEndLine) { - //x1 = 0; - // hack from stendahl to avoid doing weird side selection thing - x1 = textArea._offsetToX(line, 0); - // attempt at getting the gutter too, but doesn't seem to work - //x1 = textArea._offsetToX(line, -textArea.getHorizontalOffset()); - x2 = textArea._offsetToX(line, selectionEnd - lineStart); - } else { - //x1 = 0; - // hack from stendahl to avoid doing weird side selection thing - x1 = textArea._offsetToX(line, 0); - // attempt at getting the gutter too, but doesn't seem to work - //x1 = textArea._offsetToX(line, -textArea.getHorizontalOffset()); - x2 = getWidth(); - } - - // "inlined" min/max() - gfx.fillRect(x1 > x2 ? x2 : x1,y,x1 > x2 ? - (x1 - x2) : (x2 - x1),height); - } - - } - - protected void paintBracketHighlight(Graphics gfx, int line, int y) - { - int position = textArea.getBracketPosition(); - if(position == -1) - return; - y += fm.getLeading() + fm.getMaxDescent(); - int x = textArea._offsetToX(line,position); - gfx.setColor(bracketHighlightColor); - // Hack!!! Since there is no fast way to get the character - // from the bracket matching routine, we use ( since all - // brackets probably have the same width anyway - gfx.drawRect(x,y,fm.charWidth('(') - 1, - fm.getHeight() - 1); - } - - protected void paintCaret(Graphics gfx, int line, int y) - { - //System.out.println("painting caret " + line + " " + y); - if (textArea.isCaretVisible()) { - //System.out.println("caret is visible"); - int offset = - textArea.getCaretPosition() - textArea.getLineStartOffset(line); - int caretX = textArea._offsetToX(line, offset); - int caretWidth = ((blockCaret || - textArea.isOverwriteEnabled()) ? - fm.charWidth('w') : 1); - y += fm.getLeading() + fm.getMaxDescent(); - int height = fm.getHeight(); - - //System.out.println("caretX, width = " + caretX + " " + caretWidth); - - gfx.setColor(caretColor); - - if (textArea.isOverwriteEnabled()) { - gfx.fillRect(caretX,y + height - 1, caretWidth,1); - - } else { - // some machines don't like the drawRect for the single - // pixel caret.. this caused a lot of hell because on that - // minority of machines, the caret wouldn't show up past - // the first column. the fix is to use drawLine() in - // those cases, as a workaround. - if (caretWidth == 1) { - gfx.drawLine(caretX, y, caretX, y + height - 1); - } else { - gfx.drawRect(caretX, y, caretWidth - 1, height - 1); - } - //gfx.drawRect(caretX, y, caretWidth, height - 1); - } - } - } -} diff --git a/app/src/processing/app/syntax/TextUtilities.java b/app/src/processing/app/syntax/TextUtilities.java deleted file mode 100644 index f009cd0513d..00000000000 --- a/app/src/processing/app/syntax/TextUtilities.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * TextUtilities.java - Utility functions used by the text area classes - * Copyright (C) 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.text.*; - -/** - * Class with several utility functions used by the text area component. - * @author Slava Pestov - */ -public class TextUtilities -{ - /** - * Returns the offset of the bracket matching the one at the - * specified offset of the document, or -1 if the bracket is - * unmatched (or if the character is not a bracket). - * @param doc The document - * @param offset The offset - * @exception BadLocationException If an out-of-bounds access - * was attempted on the document text - */ - public static int findMatchingBracket(Document doc, int offset) - throws BadLocationException - { - if(doc.getLength() == 0) - return -1; - char c = doc.getText(offset,1).charAt(0); - char cprime; // c` - corresponding character - boolean direction; // true = back, false = forward - - switch(c) - { - case '(': cprime = ')'; direction = false; break; - case ')': cprime = '('; direction = true; break; - case '[': cprime = ']'; direction = false; break; - case ']': cprime = '['; direction = true; break; - case '{': cprime = '}'; direction = false; break; - case '}': cprime = '{'; direction = true; break; - default: return -1; - } - - int count; - - // How to merge these two cases is left as an exercise - // for the reader. - - // Go back or forward - if(direction) - { - // Count is 1 initially because we have already - // `found' one closing bracket - count = 1; - - // Get text[0,offset-1]; - String text = doc.getText(0,offset); - - // Scan backwards - for(int i = offset - 1; i >= 0; i--) - { - // If text[i] == c, we have found another - // closing bracket, therefore we will need - // two opening brackets to complete the - // match. - char x = text.charAt(i); - if(x == c) - count++; - - // If text[i] == cprime, we have found a - // opening bracket, so we return i if - // --count == 0 - else if(x == cprime) - { - if(--count == 0) - return i; - } - } - } - else - { - // Count is 1 initially because we have already - // `found' one opening bracket - count = 1; - - // So we don't have to + 1 in every loop - offset++; - - // Number of characters to check - int len = doc.getLength() - offset; - - // Get text[offset+1,len]; - String text = doc.getText(offset,len); - - // Scan forwards - for(int i = 0; i < len; i++) - { - // If text[i] == c, we have found another - // opening bracket, therefore we will need - // two closing brackets to complete the - // match. - char x = text.charAt(i); - - if(x == c) - count++; - - // If text[i] == cprime, we have found an - // closing bracket, so we return i if - // --count == 0 - else if(x == cprime) - { - if(--count == 0) - return i + offset; - } - } - } - - // Nothing found - return -1; - } - - /** - * Locates the start of the word at the specified position. - * @param line The text - * @param pos The position - */ - public static int findWordStart(String line, int pos, String noWordSep) - { - char ch = line.charAt(pos - 1); - - if(noWordSep == null) - noWordSep = ""; - boolean selectNoLetter = (!Character.isLetterOrDigit(ch) - && noWordSep.indexOf(ch) == -1); - - int wordStart = 0; - for(int i = pos - 1; i >= 0; i--) - { - ch = line.charAt(i); - if(selectNoLetter ^ (!Character.isLetterOrDigit(ch) && - noWordSep.indexOf(ch) == -1)) - { - wordStart = i + 1; - break; - } - } - - return wordStart; - } - - /** - * Locates the end of the word at the specified position. - * @param line The text - * @param pos The position - */ - public static int findWordEnd(String line, int pos, String noWordSep) - { - char ch = line.charAt(pos); - - if(noWordSep == null) - noWordSep = ""; - boolean selectNoLetter = (!Character.isLetterOrDigit(ch) - && noWordSep.indexOf(ch) == -1); - - int wordEnd = line.length(); - for(int i = pos; i < line.length(); i++) - { - ch = line.charAt(i); - if(selectNoLetter ^ (!Character.isLetterOrDigit(ch) && - noWordSep.indexOf(ch) == -1)) - { - wordEnd = i; - break; - } - } - return wordEnd; - } -} diff --git a/app/src/processing/app/syntax/TokenErrorMarker.java b/app/src/processing/app/syntax/TokenErrorMarker.java new file mode 100755 index 00000000000..914240bd426 --- /dev/null +++ b/app/src/processing/app/syntax/TokenErrorMarker.java @@ -0,0 +1,91 @@ +package processing.app.syntax; + +import java.awt.Color; + +import javax.swing.text.Element; + +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.TokenMaker; +import org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker; +import org.fife.ui.rsyntaxtextarea.parser.AbstractParser; +import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult; +import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice; +import org.fife.ui.rsyntaxtextarea.parser.ParseResult; +import org.fife.ui.rsyntaxtextarea.parser.ParserNotice; + +/** + * Displays Syntax errors at the level of tokens, these errors are generated by {@link TokenMaker} like {@link CPlusPlusTokenMaker} + * TODO: Currently he is the parser of the entire document, I am waiting for the resolution of the bug below. (maybe this class is to removed) + * https://github.com/bobbylight/RSyntaxTextArea/issues/86 + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public class TokenErrorMarker extends AbstractParser { + + private RSyntaxTextArea textarea; + private DefaultParseResult result; + + public TokenErrorMarker(RSyntaxTextArea textarea) { + setEnabled(true); + this.textarea = textarea; + result = new DefaultParseResult(this); + } + + @Override + public ParseResult parse(RSyntaxDocument doc, String style) { + + Element root = doc.getDefaultRootElement(); + int lineCount = root.getElementCount(); + + result.clearNotices(); + result.setParsedLines(0, lineCount-1); + + for (int line=0; line 0 && end == -1) end = t.getOffset(); + } + t = t.getNextToken(); + } + + if (start>-1) { + DefaultParserNotice pn = new DefaultParserNotice(this, "syntax error", line, start, (end-start)); + pn.setLevel(ParserNotice.Level.ERROR); + pn.setColor(Color.RED); + result.addNotice(pn); + found = true; + } + + return found; + } + + private boolean isError(Token token){ + + switch (token.getType()) { + case Token.ERROR_CHAR: + case Token.ERROR_IDENTIFIER: + case Token.ERROR_NUMBER_FORMAT: + case Token.ERROR_STRING_DOUBLE: + return true; + } + + return false; + } + +} diff --git a/app/src/processing/app/syntax/TokenMarker.java b/app/src/processing/app/syntax/TokenMarker.java deleted file mode 100644 index 9244556d3c7..00000000000 --- a/app/src/processing/app/syntax/TokenMarker.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * TokenMarker.java - Generic token marker - * Copyright (C) 1998, 1999 Slava Pestov - * - * You may use and modify this package for any purpose. Redistribution is - * permitted, in both source and binary form, provided that this notice - * remains intact in all source distributions of this package. - */ - -package processing.app.syntax; - -import javax.swing.text.Segment; - -/** - * A token marker that splits lines of text into tokens. Each token carries - * a length field and an indentification tag that can be mapped to a color - * for painting that token.

- * - * For performance reasons, the linked list of tokens is reused after each - * line is tokenized. Therefore, the return value of markTokens - * should only be used for immediate painting. Notably, it cannot be - * cached. - * - * @author Slava Pestov - */ -public abstract class TokenMarker -{ - /** - * A wrapper for the lower-level markTokensImpl method - * that is called to split a line up into tokens. - * @param line The line - * @param lineIndex The line number - */ - public Token markTokens(Segment line, int lineIndex) - { - if(lineIndex >= length) - { - throw new IllegalArgumentException("Tokenizing invalid line: " - + lineIndex); - } - - lastToken = null; - - LineInfo info = lineInfo[lineIndex]; - LineInfo prev; - if(lineIndex == 0) - prev = null; - else - prev = lineInfo[lineIndex - 1]; - - byte oldToken = info.token; - byte token = markTokensImpl(prev == null ? - Token.NULL : prev.token,line,lineIndex); - - info.token = token; - - /* - * This is a foul hack. It stops nextLineRequested - * from being cleared if the same line is marked twice. - * - * Why is this necessary? It's all JEditTextArea's fault. - * When something is inserted into the text, firing a - * document event, the insertUpdate() method shifts the - * caret (if necessary) by the amount inserted. - * - * All caret movement is handled by the select() method, - * which eventually pipes the new position to scrollTo() - * and calls repaint(). - * - * Note that at this point in time, the new line hasn't - * yet been painted; the caret is moved first. - * - * scrollTo() calls offsetToX(), which tokenizes the line - * unless it is being called on the last line painted - * (in which case it uses the text area's painter cached - * token list). What scrollTo() does next is irrelevant. - * - * After scrollTo() has done it's job, repaint() is - * called, and eventually we end up in paintLine(), whose - * job is to paint the changed line. It, too, calls - * markTokens(). - * - * The problem was that if the line started a multiline - * token, the first markTokens() (done in offsetToX()) - * would set nextLineRequested (because the line end - * token had changed) but the second would clear it - * (because the line was the same that time) and therefore - * paintLine() would never know that it needed to repaint - * subsequent lines. - * - * This bug took me ages to track down, that's why I wrote - * all the relevant info down so that others wouldn't - * duplicate it. - */ - if(!(lastLine == lineIndex && nextLineRequested)) - nextLineRequested = (oldToken != token); - - lastLine = lineIndex; - - addToken(0,Token.END); - - return firstToken; - } - - /** - * An abstract method that splits a line up into tokens. It - * should parse the line, and call addToken() to - * add syntax tokens to the token list. Then, it should return - * the initial token type for the next line.

- * - * For example if the current line contains the start of a - * multiline comment that doesn't end on that line, this method - * should return the comment token type so that it continues on - * the next line. - * - * @param token The initial token type for this line - * @param line The line to be tokenized - * @param lineIndex The index of the line in the document, - * starting at 0 - * @return The initial token type for the next line - */ - protected abstract byte markTokensImpl(byte token, Segment line, - int lineIndex); - - /** - * Returns if the token marker supports tokens that span multiple - * lines. If this is true, the object using this token marker is - * required to pass all lines in the document to the - * markTokens() method (in turn).

- * - * The default implementation returns true; it should be overridden - * to return false on simpler token markers for increased speed. - */ - public boolean supportsMultilineTokens() - { - return true; - } - - /** - * Informs the token marker that lines have been inserted into - * the document. This inserts a gap in the lineInfo - * array. - * @param index The first line number - * @param lines The number of lines - */ - public void insertLines(int index, int lines) - { - if(lines <= 0) - return; - length += lines; - ensureCapacity(length); - int len = index + lines; - System.arraycopy(lineInfo,index,lineInfo,len, - lineInfo.length - len); - - for(int i = index + lines - 1; i >= index; i--) - { - lineInfo[i] = new LineInfo(); - } - } - - /** - * Informs the token marker that line have been deleted from - * the document. This removes the lines in question from the - * lineInfo array. - * @param index The first line number - * @param lines The number of lines - */ - public void deleteLines(int index, int lines) - { - if (lines <= 0) - return; - int len = index + lines; - length -= lines; - System.arraycopy(lineInfo,len,lineInfo, - index,lineInfo.length - len); - } - - /** - * Returns the number of lines in this token marker. - */ - public int getLineCount() - { - return length; - } - - /** - * Returns true if the next line should be repainted. This - * will return true after a line has been tokenized that starts - * a multiline token that continues onto the next line. - */ - public boolean isNextLineRequested() - { - return nextLineRequested; - } - - // protected members - - /** - * The first token in the list. This should be used as the return - * value from markTokens(). - */ - protected Token firstToken; - - /** - * The last token in the list. New tokens are added here. - * This should be set to null before a new line is to be tokenized. - */ - protected Token lastToken; - - /** - * An array for storing information about lines. It is enlarged and - * shrunk automatically by the insertLines() and - * deleteLines() methods. - */ - protected LineInfo[] lineInfo; - - /** - * The number of lines in the model being tokenized. This can be - * less than the length of the lineInfo array. - */ - protected int length; - - /** - * The last tokenized line. - */ - protected int lastLine; - - /** - * True if the next line should be painted. - */ - protected boolean nextLineRequested; - - /** - * Creates a new TokenMarker. This DOES NOT create - * a lineInfo array; an initial call to insertLines() - * does that. - */ - protected TokenMarker() - { - lastLine = -1; - } - - /** - * Ensures that the lineInfo array can contain the - * specified index. This enlarges it if necessary. No action is - * taken if the array is large enough already.

- * - * It should be unnecessary to call this under normal - * circumstances; insertLine() should take care of - * enlarging the line info array automatically. - * - * @param index The array index - */ - protected void ensureCapacity(int index) - { - if(lineInfo == null) - lineInfo = new LineInfo[index + 1]; - else if(lineInfo.length <= index) - { - LineInfo[] lineInfoN = new LineInfo[(index + 1) * 2]; - System.arraycopy(lineInfo,0,lineInfoN,0, - lineInfo.length); - lineInfo = lineInfoN; - } - } - - /** - * Adds a token to the token list. - * @param length The length of the token - * @param id The id of the token - */ - protected void addToken(int length, byte id) - { - if(id >= Token.INTERNAL_FIRST && id <= Token.INTERNAL_LAST) - throw new InternalError("Invalid id: " + id); - - if(length == 0 && id != Token.END) - return; - - if(firstToken == null) - { - firstToken = new Token(length,id); - lastToken = firstToken; - } - else if(lastToken == null) - { - lastToken = firstToken; - firstToken.length = length; - firstToken.id = id; - } - else if(lastToken.next == null) - { - lastToken.next = new Token(length,id); - lastToken = lastToken.next; - } - else - { - lastToken = lastToken.next; - lastToken.length = length; - lastToken.id = id; - } - } - - /** - * Inner class for storing information about tokenized lines. - */ - public class LineInfo - { - /** - * Creates a new LineInfo object with token = Token.NULL - * and obj = null. - */ - public LineInfo() - { - } - - /** - * Creates a new LineInfo object with the specified - * parameters. - */ - public LineInfo(byte token, Object obj) - { - this.token = token; - this.obj = obj; - } - - /** - * The id of the last token of the line. - */ - public byte token; - - /** - * This is for use by the token marker implementations - * themselves. It can be used to store anything that - * is an object and that needs to exist on a per-line - * basis. - */ - public Object obj; - } -} diff --git a/app/src/processing/app/syntax/im/CompositionTextManager.java b/app/src/processing/app/syntax/im/CompositionTextManager.java deleted file mode 100644 index ba9ee155f7f..00000000000 --- a/app/src/processing/app/syntax/im/CompositionTextManager.java +++ /dev/null @@ -1,198 +0,0 @@ -package processing.app.syntax.im; - -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.font.FontRenderContext; -import java.awt.font.TextAttribute; -import java.awt.font.TextLayout; -import java.text.AttributedCharacterIterator; -import java.text.AttributedString; - -import javax.swing.text.BadLocationException; - -import processing.app.syntax.JEditTextArea; -import processing.app.syntax.TextAreaPainter; - -/** - * This class Manage texts from input method - * by begin-process-end steps. - * - * First, if a user start inputing via input method, - * beginCompositionText is called from InputMethodSupport. - * Second, the user continues from input method, processCompositionText is called - * and reflect user inputs to text area. - * Finally the user try to commit text, endCompositionText is called. - * - * @author Takashi Maekawa (takachin@generative.info) - */ - -public class CompositionTextManager { - private JEditTextArea textArea; - private String prevComposeString; - private int prevCommittedCount; - private boolean isInputProcess; - private int initialCaretPosition; - public static final int COMPOSING_UNDERBAR_HEIGHT = 5; - - /** - * Create text manager class with a textarea. - * @param textArea texarea component for PDE. - */ - public CompositionTextManager(JEditTextArea textArea) { - this.textArea = textArea; - prevComposeString = ""; - isInputProcess = false; - prevCommittedCount = 0; - } - - /** - * Get this text manager is whether in input process or not. - */ - public boolean getIsInputProcess() { - return isInputProcess; - } - /** - * Insert full width space - */ - public void insertFullWidthSpace() { - initialCaretPosition = textArea.getCaretPosition(); - int layoutCaretPosition = initialCaretPosition; - try { - textArea.getDocument().insertString(layoutCaretPosition, "\u3000", null); - } catch (BadLocationException e) { - e.printStackTrace(); - } - } - - /** - * Called when a user begins input from input method. - * This method initializes text manager. - * - * @param text Text from InputMethodEvent. - * @param commited_count Numbers of committed characters in text. - */ - public void beginCompositionText(AttributedCharacterIterator text, int committed_count) { - isInputProcess = true; - prevComposeString = ""; - initialCaretPosition = textArea.getCaretPosition(); - processCompositionText(text, committed_count); - } - - /** - * Called when a user processing input characters and - * select candidates from input method. - * - * @param text Text from InputMethodEvent. - * @param commited_count Numbers of committed characters in text. - */ - public void processCompositionText(AttributedCharacterIterator text, int committed_count) { - int layoutCaretPosition = initialCaretPosition + committed_count; - CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter(); - compositionPainter.setComposedTextLayout(getTextLayout(text, committed_count), layoutCaretPosition); - int textLength = text.getEndIndex() - text.getBeginIndex() - committed_count; - StringBuffer unCommitedStringBuf = new StringBuffer(textLength); - char c; - for (c = text.setIndex(committed_count); c != AttributedCharacterIterator.DONE - && textLength > 0; c = text.next(), --textLength) { - unCommitedStringBuf.append(c); - } - String unCommittedString = unCommitedStringBuf.toString(); - try { - if(canRemovePreviousInput(committed_count)){ - textArea.getDocument().remove(layoutCaretPosition, prevComposeString.length()); - } - textArea.getDocument().insertString(layoutCaretPosition, unCommittedString, null); - if(committed_count > 0){ - initialCaretPosition = initialCaretPosition + committed_count; - } - prevComposeString = unCommittedString; - prevCommittedCount = committed_count; - } catch (BadLocationException e) { - e.printStackTrace(); - } - } - - private boolean canRemovePreviousInput(int committed_count){ - return (prevCommittedCount == committed_count || prevCommittedCount > committed_count); - } - - /** - * Called when a user fixed text from input method or delete all - * composition text. This method resets CompositionTextPainter. - * - * @param text Text from InputMethodEvent. - * @param commited_count Numbers of committed characters in text. - */ - public void endCompositionText(AttributedCharacterIterator text, int committed_count) { - /* - * If there are no committed characters, remove it all from textarea. - * This case will happen if a user delete all composing characters by backspace or delete key. - * If it does, these previous characters are needed to be deleted. - */ - if(committed_count == 0){ - removeNotCommittedText(text); - } - CompositionTextPainter compositionPainter = textArea.getPainter().getCompositionTextpainter(); - compositionPainter.invalidateComposedTextLayout(initialCaretPosition + committed_count); - prevComposeString = ""; - isInputProcess = false; - } - - private void removeNotCommittedText(AttributedCharacterIterator text){ - if (prevComposeString.length() == 0) { - return; - } - try { - textArea.getDocument().remove(initialCaretPosition, prevComposeString.length()); - } catch (BadLocationException e) { - e.printStackTrace(); - } - } - - private TextLayout getTextLayout(AttributedCharacterIterator text, int committed_count) { - AttributedString composed = new AttributedString(text, committed_count, text.getEndIndex()); - Font font = textArea.getPainter().getFont(); - FontRenderContext context = ((Graphics2D) (textArea.getPainter().getGraphics())).getFontRenderContext(); - composed.addAttribute(TextAttribute.FONT, font); - TextLayout layout = new TextLayout(composed.getIterator(), context); - return layout; - } - - private Point getCaretLocation() { - Point loc = new Point(); - TextAreaPainter painter = textArea.getPainter(); - FontMetrics fm = painter.getFontMetrics(); - int offsetY = fm.getHeight() - COMPOSING_UNDERBAR_HEIGHT; - int lineIndex = textArea.getCaretLine(); - loc.y = lineIndex * fm.getHeight() + offsetY; - int offsetX = textArea.getCaretPosition() - - textArea.getLineStartOffset(lineIndex); - loc.x = textArea.offsetToX(lineIndex, offsetX); - return loc; - } - - public Rectangle getTextLocation() { - Point caret = getCaretLocation(); - return getCaretRectangle(caret.x, caret.y); - } - - private Rectangle getCaretRectangle(int x, int y) { - TextAreaPainter painter = textArea.getPainter(); - Point origin = painter.getLocationOnScreen(); - int height = painter.getFontMetrics().getHeight(); - return new Rectangle(origin.x + x, origin.y + y, 0, height); - } - - public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex) { - int length = endIndex - beginIndex; - String textAreaString = textArea.getText(beginIndex, length); - return new AttributedString(textAreaString).getIterator(); - } - - public int getInsertPositionOffset() { - return textArea.getCaretPosition() * -1; - } -} diff --git a/app/src/processing/app/syntax/im/CompositionTextPainter.java b/app/src/processing/app/syntax/im/CompositionTextPainter.java deleted file mode 100644 index 0084f491f92..00000000000 --- a/app/src/processing/app/syntax/im/CompositionTextPainter.java +++ /dev/null @@ -1,124 +0,0 @@ -package processing.app.syntax.im; - -import java.awt.Color; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.font.TextLayout; - -import processing.app.syntax.JEditTextArea; -import processing.app.syntax.TextAreaPainter; - -/** - * Paint texts from input method. Text via input method are transmitted by - * AttributedCaharacterIterator. This class helps the PDE's TextAreaPainter - * to handle AttributedCaharacterIterator. - * - * For practical purposes, paint to textarea is done by TextLayout class. - * Because TextLayout class is easy to draw composing texts. (For example, - * draw underline composing texts, focus when select from candidates text.) - * - * @author Takashi Maekawa (takachin@generative.info) - */ -public class CompositionTextPainter { - private TextLayout composedTextLayout; - private int composedBeginCaretPosition = 0; - private JEditTextArea textArea; - - /** - * Constructor for painter. - * @param textarea textarea used by PDE. - */ - public CompositionTextPainter(JEditTextArea textArea) { - this.textArea = textArea; - composedTextLayout = null; - } - - /** - * Check the painter has TextLayout. - * If a user input via InputMethod, this result will return true. - * @param textarea textarea used by PDE. - */ - public boolean hasComposedTextLayout() { - return (composedTextLayout != null); - } - - /** - * Set TextLayout to the painter. - * TextLayout will be created and set by CompositionTextManager. - * - * @see CompositionTextManager - * @param textarea textarea used by PDE. - */ - public void setComposedTextLayout(TextLayout composedTextLayout, int composedStartCaretPosition) { - this.composedTextLayout = composedTextLayout; - this.composedBeginCaretPosition = composedStartCaretPosition; - } - - /** - * Invalidate this TextLayout to set null. - * If a user end input via InputMethod, this method will called from CompositionTextManager.endCompositionText - */ - public void invalidateComposedTextLayout(int composedEndCaretPosition) { - this.composedTextLayout = null; - this.composedBeginCaretPosition = composedEndCaretPosition; - //this.composedBeginCaretPosition = textArea.getCaretPosition(); - } - - /** - * Draw text via input method with composed text information. - * This method can draw texts with some underlines to illustrate converting characters. - * - * This method is workaround for TextAreaPainter. - * Because, TextAreaPainter can't treat AttributedCharacterIterator directly. - * AttributedCharacterIterator has very important information when composing text. - * It has a map where are converted characters and committed characters. - * Ideally, changing TextAreaPainter method can treat AttributedCharacterIterator is better. But it's very tough!! - * So I choose to write some code as a workaround. - * - * This draw method is proceeded with the following steps. - * 1. Original TextAreaPainter draws characters. - * 2. This refillComposedArea method erase previous paint characters by textarea's background color. - * The refill area is only square that width and height defined by characters with input method. - * 3. CompositionTextPainter.draw method paints composed text. It was actually drawn by TextLayout. - * - * @param gfx set TextAreaPainter's Graphics object. - * @param fillBackGroundColor set textarea's background. - */ - public void draw(Graphics gfx, Color fillBackGroundColor) { - assert(composedTextLayout != null); - Point composedLoc = getCaretLocation(); - refillComposedArea(fillBackGroundColor, composedLoc.x, composedLoc.y); - composedTextLayout.draw((Graphics2D) gfx, composedLoc.x, composedLoc.y); - } - - /** - * Fill color to erase characters drawn by original TextAreaPainter. - * - * @param fillColor fill color to erase characters drawn by original TextAreaPainter method. - * @param x x-coordinate where to fill. - * @param y y-coordinate where to fill. - */ - private void refillComposedArea(Color fillColor, int x, int y) { - Graphics gfx = textArea.getPainter().getGraphics(); - gfx.setColor(fillColor); - FontMetrics fm = textArea.getPainter().getFontMetrics(); - int newY = y - (fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT); - int paintHeight = fm.getHeight(); - int paintWidth = (int) composedTextLayout.getBounds().getWidth(); - gfx.fillRect(x, newY, paintWidth, paintHeight); - } - - private Point getCaretLocation() { - Point loc = new Point(); - TextAreaPainter painter = textArea.getPainter(); - FontMetrics fm = painter.getFontMetrics(); - int offsetY = fm.getHeight() - CompositionTextManager.COMPOSING_UNDERBAR_HEIGHT; - int lineIndex = textArea.getCaretLine(); - loc.y = lineIndex * fm.getHeight() + offsetY; - int offsetX = composedBeginCaretPosition - textArea.getLineStartOffset(lineIndex); - loc.x = textArea.offsetToX(lineIndex, offsetX); - return loc; - } -} diff --git a/app/src/processing/app/syntax/im/InputMethodSupport.java b/app/src/processing/app/syntax/im/InputMethodSupport.java deleted file mode 100644 index 461be3d1677..00000000000 --- a/app/src/processing/app/syntax/im/InputMethodSupport.java +++ /dev/null @@ -1,120 +0,0 @@ -package processing.app.syntax.im; - -import java.awt.Rectangle; -import java.awt.event.InputMethodEvent; -import java.awt.event.InputMethodListener; -import java.awt.font.TextHitInfo; -import java.awt.im.InputMethodRequests; -import java.text.AttributedCharacterIterator; - -import processing.app.syntax.JEditTextArea; - -/** - * Support in-line Japanese input for PDE. (Maybe Chinese, Korean and more) - * This class is implemented by Java Input Method Framework and handles - * If you would like to know more about Java Input Method Framework, - * Please see http://java.sun.com/j2se/1.5.0/docs/guide/imf/ - * - * This class is implemented to fix Bug #854. - * http://dev.processing.org/bugs/show_bug.cgi?id=854 - * - * @author Takashi Maekawa (takachin@generative.info) - */ -public class InputMethodSupport implements InputMethodRequests, - InputMethodListener { - - private int committed_count = 0; - private CompositionTextManager textManager; - - public InputMethodSupport(JEditTextArea textArea) { - textManager = new CompositionTextManager(textArea); - textArea.enableInputMethods(true); - textArea.addInputMethodListener(this); - } - - public Rectangle getTextLocation(TextHitInfo offset) { - return textManager.getTextLocation(); - } - - public TextHitInfo getLocationOffset(int x, int y) { - return null; - } - - public int getInsertPositionOffset() { - return textManager.getInsertPositionOffset(); - } - - public AttributedCharacterIterator getCommittedText(int beginIndex, - int endIndex, AttributedCharacterIterator.Attribute[] attributes) { - return textManager.getCommittedText(beginIndex, endIndex); - } - - public int getCommittedTextLength() { - return committed_count; - } - - public AttributedCharacterIterator cancelLatestCommittedText( - AttributedCharacterIterator.Attribute[] attributes) { - return null; - } - - public AttributedCharacterIterator getSelectedText( - AttributedCharacterIterator.Attribute[] attributes) { - return null; - } - - /** - * Handles events from InputMethod. - * This method judges whether beginning of input or - * progress of input or end and call related method. - * - * @param event event from Input Method. - */ - public void inputMethodTextChanged(InputMethodEvent event) { - AttributedCharacterIterator text = event.getText(); - committed_count = event.getCommittedCharacterCount(); - if(isFullWidthSpaceInput(text)){ - textManager.insertFullWidthSpace(); - caretPositionChanged(event); - return; - } - if(isBeginInputProcess(text, textManager)){ - textManager.beginCompositionText(text, committed_count); - caretPositionChanged(event); - return; - } - if (isInputProcess(text)){ - textManager.processCompositionText(text, committed_count); - caretPositionChanged(event); - return; - } - textManager.endCompositionText(text, committed_count); - caretPositionChanged(event); - } - - private boolean isFullWidthSpaceInput(AttributedCharacterIterator text){ - if(text == null) - return false; - if(textManager.getIsInputProcess()) - return false; - return (String.valueOf(text.first()).equals("\u3000")); - } - - private boolean isBeginInputProcess(AttributedCharacterIterator text, CompositionTextManager textManager){ - if(text == null) - return false; - if(textManager.getIsInputProcess()) - return false; - return (isInputProcess(text)); - } - - private boolean isInputProcess(AttributedCharacterIterator text){ - if(text == null) - return false; - return (text.getEndIndex() - (text.getBeginIndex() + committed_count) > 0); - } - - public void caretPositionChanged(InputMethodEvent event) { - event.consume(); - } -} diff --git a/app/src/processing/app/syntax/readme.txt b/app/src/processing/app/syntax/readme.txt deleted file mode 100644 index 07a825cd7bb..00000000000 --- a/app/src/processing/app/syntax/readme.txt +++ /dev/null @@ -1,46 +0,0 @@ -OLDSYNTAX PACKAGE README - -I am placing the jEdit 2.2.1 syntax highlighting package in the public -domain. This means it can be integrated into commercial programs, etc. - -This package requires at least Java 1.1 and Swing 1.1. Syntax -highlighting for the following file types is supported: - -- C++, C -- CORBA IDL -- Eiffel -- HTML -- Java -- Java properties -- JavaScript -- MS-DOS INI -- MS-DOS batch files -- Makefile -- PHP -- Perl -- Python -- TeX -- Transact-SQL -- Unix patch/diff -- Unix shell script -- XML - -This package is undocumented; read the source (start by taking a look at -JEditTextArea.java) to find out how to use it; it's really simple. Feel -free to e-mail questions, queries, etc. to me, but keep in mind that -this code is very old and I no longer maintain it. So if you find a bug, -don't bother me about it; fix it yourself. - -* Copyright - -The jEdit 2.2.1 syntax highlighting package contains code that is -Copyright 1998-1999 Slava Pestov, Artur Biesiadowski, Clancy Malcolm, -Jonathan Revusky, Juha Lindfors and Mike Dillon. - -You may use and modify this package for any purpose. Redistribution is -permitted, in both source and binary form, provided that this notice -remains intact in all source distributions of this package. - --- Slava Pestov -25 September 2000 - diff --git a/app/src/processing/app/tools/DiscourseFormat.java b/app/src/processing/app/tools/DiscourseFormat.java index a4a381c5a22..75240cb6947 100644 --- a/app/src/processing/app/tools/DiscourseFormat.java +++ b/app/src/processing/app/tools/DiscourseFormat.java @@ -25,11 +25,14 @@ 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 @@ *

* 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 void show() { StringBuilder cf = new StringBuilder(html ? "

\n" : "[code]\n");
 
     int selStart = textarea.getSelectionStart();
-    int selStop = textarea.getSelectionStop();
-
-    int startLine = textarea.getSelectionStartLine();
-    int stopLine = textarea.getSelectionStopLine();
+    int selStop = textarea.getSelectionEnd();
+
+    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 void show() {
       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 @@ private void appendToHTML(char c, StringBuilder buffer) {
   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 void appendFormattedLine(StringBuilder cf, int line) {
       }
 
     } 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;
+      
+      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);
         }
-        offset += length;
-        tokens = tokens.next;
+        
+        tokenList = tokenList.getNextToken();
       }
+  
     }
   }
 }
diff --git a/app/test/processing/app/AutoformatSavesCaretPositionTest.java b/app/test/processing/app/AutoformatSavesCaretPositionTest.java
index 8ffa03190d1..01a60ecb6b4 100644
--- a/app/test/processing/app/AutoformatSavesCaretPositionTest.java
+++ b/app/test/processing/app/AutoformatSavesCaretPositionTest.java
@@ -2,7 +2,7 @@
 
 import org.fest.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
-import processing.app.helpers.JEditTextAreaFixture;
+import processing.app.helpers.RSyntaxTextAreaFixture;
 
 import static org.junit.Assert.assertEquals;
 
@@ -13,7 +13,7 @@ public void shouldSaveCaretPositionAfterAutoformat() {
     JMenuItemFixture menuToolsAutoFormat = window.menuItem("menuToolsAutoFormat");
     menuToolsAutoFormat.requireEnabled();
 
-    JEditTextAreaFixture editor = window.jEditTextArea("editor");
+    RSyntaxTextAreaFixture editor = window.RSyntaxTextArea("editor");
     editor.setText("void setup() {\n" +
             "              // put your setup code here, to run once:\n" +
             "\n" +
diff --git a/app/test/processing/app/AutoformatTest.java b/app/test/processing/app/AutoformatTest.java
index 51fdc35d6ba..35f71d5918c 100644
--- a/app/test/processing/app/AutoformatTest.java
+++ b/app/test/processing/app/AutoformatTest.java
@@ -2,7 +2,7 @@
 
 import org.fest.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
-import processing.app.helpers.JEditTextAreaFixture;
+import processing.app.helpers.RSyntaxTextAreaFixture;
 
 import static org.junit.Assert.assertEquals;
 
@@ -13,7 +13,7 @@ public void shouldProduceNicelyFormattedCode() throws Exception {
     JMenuItemFixture menuToolsAutoFormat = window.menuItem("menuToolsAutoFormat");
     menuToolsAutoFormat.requireEnabled();
 
-    JEditTextAreaFixture editor = window.jEditTextArea("editor");
+    RSyntaxTextAreaFixture editor = window.RSyntaxTextArea("editor");
     editor.setText("void setup() {\n" +
             "// put your setup code here, to run once:\n" +
             "int foo[] = { 1, 2, 3, 4, 5};\n" +
diff --git a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
index c760ef29be4..d9fa4ed2109 100644
--- a/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
+++ b/app/test/processing/app/HittingEscapeOnCloseConfirmationDialogTest.java
@@ -4,7 +4,7 @@
 import org.fest.swing.finder.WindowFinder;
 import org.fest.swing.fixture.DialogFixture;
 import org.junit.Test;
-import processing.app.helpers.JEditTextAreaFixture;
+import processing.app.helpers.RSyntaxTextAreaFixture;
 
 import javax.swing.*;
 import java.awt.event.KeyEvent;
@@ -15,7 +15,7 @@ public class HittingEscapeOnCloseConfirmationDialogTest extends AbstractGUITest
 
   @Test
   public void shouldJustCloseTheDialog() throws Exception {
-    JEditTextAreaFixture editor = window.jEditTextArea("editor");
+    RSyntaxTextAreaFixture editor = window.RSyntaxTextArea("editor");
     editor.setText("test");
 
     window.close();
diff --git a/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java b/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java
index 9ee7e0c5089..49d460cfb57 100644
--- a/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java
+++ b/app/test/processing/app/ReduceIndentWith1CharOnLastLineTest.java
@@ -5,7 +5,7 @@
 import org.fest.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
 
-import processing.app.helpers.JEditTextAreaFixture;
+import processing.app.helpers.RSyntaxTextAreaFixture;
 
 public class ReduceIndentWith1CharOnLastLineTest extends AbstractGUITest {
 
@@ -13,7 +13,7 @@ public class ReduceIndentWith1CharOnLastLineTest extends AbstractGUITest {
   public void shouldJustCloseTheDialog() throws Exception {
     JMenuItemFixture menuDecreaseIndent = window.menuItem("menuDecreaseIndent");
 
-    JEditTextAreaFixture editor = window.jEditTextArea("editor");
+    RSyntaxTextAreaFixture editor = window.RSyntaxTextArea("editor");
     editor.setText("void loop()\n{\n  Serial.begin(9600)\n}");
 
     editor.selectAll();
diff --git a/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java b/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java
index bd03f1108b0..21cce003bd0 100644
--- a/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java
+++ b/app/test/processing/app/ReplacingTextGeneratesTwoUndoActionsTest.java
@@ -2,7 +2,7 @@
 
 import org.fest.swing.fixture.JMenuItemFixture;
 import org.junit.Test;
-import processing.app.helpers.JEditTextAreaFixture;
+import processing.app.helpers.RSyntaxTextAreaFixture;
 
 import static org.junit.Assert.assertEquals;
 
@@ -15,19 +15,19 @@ public void shouldUndoAndRedo() throws Exception {
     JMenuItemFixture menuEditRedo = window.menuItem("menuEditRedo");
     menuEditRedo.requireDisabled();
 
-    JEditTextAreaFixture jEditTextArea = window.jEditTextArea("editor");
+    RSyntaxTextAreaFixture RSyntaxTextArea = window.RSyntaxTextArea("editor");
 
-    jEditTextArea.setText("fake text");
+    RSyntaxTextArea.setText("fake text");
 
     menuEditUndo.requireEnabled();
     menuEditUndo.click();
 
-    assertEquals("", jEditTextArea.getText());
+    assertEquals("", RSyntaxTextArea.getText());
 
     menuEditRedo.requireEnabled();
     menuEditRedo.click();
 
-    //assertEquals("fake text", jEditTextArea.getText());
+    //assertEquals("fake text", RSyntaxTextArea.getText());
 
     menuEditUndo.requireEnabled();
     menuEditUndo.click();
diff --git a/app/test/processing/app/helpers/ArduinoFrameFixture.java b/app/test/processing/app/helpers/ArduinoFrameFixture.java
index aec49a5a89d..54fc2f420db 100644
--- a/app/test/processing/app/helpers/ArduinoFrameFixture.java
+++ b/app/test/processing/app/helpers/ArduinoFrameFixture.java
@@ -1,10 +1,10 @@
 package processing.app.helpers;
 
+import java.awt.Frame;
+
 import org.fest.swing.core.Robot;
 import org.fest.swing.fixture.FrameFixture;
-import processing.app.syntax.JEditTextArea;
-
-import java.awt.*;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
 
 public class ArduinoFrameFixture extends FrameFixture {
 
@@ -24,7 +24,7 @@ public ArduinoFrameFixture(String name) {
     super(name);
   }
 
-  public JEditTextAreaFixture jEditTextArea(String name) {
-    return new JEditTextAreaFixture(robot, (JEditTextArea) this.robot.finder().find(new JEditTextAreaComponentMatcher(name)));
+  public RSyntaxTextAreaFixture RSyntaxTextArea(String name) {
+    return new RSyntaxTextAreaFixture(robot, (RSyntaxTextArea) this.robot.finder().find(new RSyntaxTextAreaComponentMatcher(name)));
   }
 }
diff --git a/app/test/processing/app/helpers/JEditTextAreaComponentMatcher.java b/app/test/processing/app/helpers/JEditTextAreaComponentMatcher.java
deleted file mode 100644
index f168b92b92f..00000000000
--- a/app/test/processing/app/helpers/JEditTextAreaComponentMatcher.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package processing.app.helpers;
-
-import org.fest.swing.core.ComponentMatcher;
-import processing.app.syntax.JEditTextArea;
-
-import java.awt.*;
-
-public class JEditTextAreaComponentMatcher implements ComponentMatcher {
-
-  private final String name;
-
-  public JEditTextAreaComponentMatcher(String name) {
-    this.name = name;
-  }
-
-  @Override
-  public boolean matches(Component component) {
-    return component instanceof JEditTextArea && name.equals(component.getName());
-  }
-}
diff --git a/app/test/processing/app/helpers/JEditTextAreaFixture.java b/app/test/processing/app/helpers/JEditTextAreaFixture.java
deleted file mode 100644
index 19560d88be9..00000000000
--- a/app/test/processing/app/helpers/JEditTextAreaFixture.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package processing.app.helpers;
-
-import org.fest.swing.core.Robot;
-import org.fest.swing.fixture.ComponentFixture;
-import processing.app.syntax.JEditTextArea;
-
-public class JEditTextAreaFixture extends ComponentFixture {
-
-  private final JEditTextAreaComponentDriver driver;
-
-  public JEditTextAreaFixture(Robot robot, Class type) {
-    super(robot, type);
-    this.driver = new JEditTextAreaComponentDriver(robot);
-  }
-
-  public JEditTextAreaFixture(Robot robot, String name, Class type) {
-    super(robot, name, type);
-    this.driver = new JEditTextAreaComponentDriver(robot);
-  }
-
-  public JEditTextAreaFixture(Robot robot, JEditTextArea target) {
-    super(robot, target);
-    this.driver = new JEditTextAreaComponentDriver(robot);
-  }
-
-  public JEditTextAreaFixture enterText(String text) {
-    driver.enterText((JEditTextArea) target, text);
-    return this;
-  }
-
-  public JEditTextAreaFixture setText(String text) {
-    driver.setText((JEditTextArea) target, text);
-    return this;
-  }
-
-  public String getText() {
-    return driver.getText((JEditTextArea) target);
-  }
-
-  public JEditTextAreaFixture selectAll() {
-    driver.selectAll((JEditTextArea) target);
-    return this;
-  }
-
-  public int getCaretPosition() {
-    return driver.getCaretPosition((JEditTextArea) target);
-  }
-
-  public void setCaretPosition(int caretPosition) {
-    driver.setCaretPosition((JEditTextArea) target, caretPosition);
-  }
-}
diff --git a/app/test/processing/app/helpers/JEditTextAreaComponentDriver.java b/app/test/processing/app/helpers/RSyntaxTextAreaComponentDriver.java
similarity index 53%
rename from app/test/processing/app/helpers/JEditTextAreaComponentDriver.java
rename to app/test/processing/app/helpers/RSyntaxTextAreaComponentDriver.java
index f292c399827..73d2694de8c 100644
--- a/app/test/processing/app/helpers/JEditTextAreaComponentDriver.java
+++ b/app/test/processing/app/helpers/RSyntaxTextAreaComponentDriver.java
@@ -4,24 +4,24 @@
 import org.fest.swing.driver.JComponentDriver;
 import org.fest.swing.edt.GuiActionRunner;
 import org.fest.swing.edt.GuiQuery;
-import processing.app.syntax.JEditTextArea;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
 
-public class JEditTextAreaComponentDriver extends JComponentDriver {
+public class RSyntaxTextAreaComponentDriver extends JComponentDriver {
 
-  public JEditTextAreaComponentDriver(Robot robot) {
+  public RSyntaxTextAreaComponentDriver(Robot robot) {
     super(robot);
   }
 
-  public void enterText(JEditTextArea target, String text) {
+  public void enterText(RSyntaxTextArea target, String text) {
     focusAndWaitForFocusGain(target);
     robot.enterText(text);
   }
 
-  public void setText(final JEditTextArea target, final String text) {
+  public void setText(final RSyntaxTextArea target, final String text) {
     focusAndWaitForFocusGain(target);
-    GuiActionRunner.execute(new GuiQuery() {
+    GuiActionRunner.execute(new GuiQuery() {
 
-      protected JEditTextArea executeInEDT() {
+      protected RSyntaxTextArea executeInEDT() {
         target.setText(text);
         return target;
       }
@@ -30,7 +30,7 @@ protected JEditTextArea executeInEDT() {
     robot.waitForIdle();
   }
 
-  public String getText(final JEditTextArea target) {
+  public String getText(final RSyntaxTextArea target) {
     focusAndWaitForFocusGain(target);
     return GuiActionRunner.execute(new GuiQuery() {
 
@@ -41,10 +41,10 @@ protected String executeInEDT() {
     });
   }
 
-  public JEditTextArea selectAll(final JEditTextArea target) {
-    return GuiActionRunner.execute(new GuiQuery() {
+  public RSyntaxTextArea selectAll(final RSyntaxTextArea target) {
+    return GuiActionRunner.execute(new GuiQuery() {
 
-      protected JEditTextArea executeInEDT() {
+      protected RSyntaxTextArea executeInEDT() {
         target.selectAll();
         return target;
       }
@@ -52,7 +52,7 @@ protected JEditTextArea executeInEDT() {
     });
   }
 
-  public Integer getCaretPosition(final JEditTextArea target) {
+  public Integer getCaretPosition(final RSyntaxTextArea target) {
     focusAndWaitForFocusGain(target);
     return GuiActionRunner.execute(new GuiQuery() {
 
@@ -63,11 +63,11 @@ protected Integer executeInEDT() {
     });
   }
 
-  public void setCaretPosition(final JEditTextArea target, final int caretPosition) {
+  public void setCaretPosition(final RSyntaxTextArea target, final int caretPosition) {
     focusAndWaitForFocusGain(target);
-    GuiActionRunner.execute(new GuiQuery() {
+    GuiActionRunner.execute(new GuiQuery() {
 
-      protected JEditTextArea executeInEDT() {
+      protected RSyntaxTextArea executeInEDT() {
         target.setCaretPosition(caretPosition);
         return target;
       }
diff --git a/app/test/processing/app/helpers/RSyntaxTextAreaComponentMatcher.java b/app/test/processing/app/helpers/RSyntaxTextAreaComponentMatcher.java
new file mode 100644
index 00000000000..27d85f7829b
--- /dev/null
+++ b/app/test/processing/app/helpers/RSyntaxTextAreaComponentMatcher.java
@@ -0,0 +1,20 @@
+package processing.app.helpers;
+
+import java.awt.Component;
+
+import org.fest.swing.core.ComponentMatcher;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+
+public class RSyntaxTextAreaComponentMatcher implements ComponentMatcher {
+
+  private final String name;
+
+  public RSyntaxTextAreaComponentMatcher(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public boolean matches(Component component) {
+    return component instanceof RSyntaxTextArea && name.equals(component.getName());
+  }
+}
diff --git a/app/test/processing/app/helpers/RSyntaxTextAreaFixture.java b/app/test/processing/app/helpers/RSyntaxTextAreaFixture.java
new file mode 100644
index 00000000000..4eb1ec11d07
--- /dev/null
+++ b/app/test/processing/app/helpers/RSyntaxTextAreaFixture.java
@@ -0,0 +1,52 @@
+package processing.app.helpers;
+
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.ComponentFixture;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+
+public class RSyntaxTextAreaFixture extends ComponentFixture {
+
+  private final RSyntaxTextAreaComponentDriver driver;
+
+  public RSyntaxTextAreaFixture(Robot robot, Class type) {
+    super(robot, type);
+    this.driver = new RSyntaxTextAreaComponentDriver(robot);
+  }
+
+  public RSyntaxTextAreaFixture(Robot robot, String name, Class type) {
+    super(robot, name, type);
+    this.driver = new RSyntaxTextAreaComponentDriver(robot);
+  }
+
+  public RSyntaxTextAreaFixture(Robot robot, RSyntaxTextArea target) {
+    super(robot, target);
+    this.driver = new RSyntaxTextAreaComponentDriver(robot);
+  }
+
+  public RSyntaxTextAreaFixture enterText(String text) {
+    driver.enterText((RSyntaxTextArea) target, text);
+    return this;
+  }
+
+  public RSyntaxTextAreaFixture setText(String text) {
+    driver.setText((RSyntaxTextArea) target, text);
+    return this;
+  }
+
+  public String getText() {
+    return driver.getText((RSyntaxTextArea) target);
+  }
+
+  public RSyntaxTextAreaFixture selectAll() {
+    driver.selectAll((RSyntaxTextArea) target);
+    return this;
+  }
+
+  public int getCaretPosition() {
+    return driver.getCaretPosition((RSyntaxTextArea) target);
+  }
+
+  public void setCaretPosition(int caretPosition) {
+    driver.setCaretPosition((RSyntaxTextArea) target, caretPosition);
+  }
+}
diff --git a/arduino-autocomplete/.classpath b/arduino-autocomplete/.classpath
new file mode 100755
index 00000000000..ed37cc30bfe
--- /dev/null
+++ b/arduino-autocomplete/.classpath
@@ -0,0 +1,10 @@
+
+
+	
+	
+	
+	
+	
+	
+	
+
diff --git a/arduino-autocomplete/.gitignore b/arduino-autocomplete/.gitignore
new file mode 100755
index 00000000000..583ba9546e9
--- /dev/null
+++ b/arduino-autocomplete/.gitignore
@@ -0,0 +1,2 @@
+/bin/
+arduino-autocomplete.jar
\ No newline at end of file
diff --git a/arduino-autocomplete/.project b/arduino-autocomplete/.project
new file mode 100755
index 00000000000..16edc16e828
--- /dev/null
+++ b/arduino-autocomplete/.project
@@ -0,0 +1,17 @@
+
+
+	arduino-autocomplete
+	
+	
+	
+	
+		
+			org.eclipse.jdt.core.javabuilder
+			
+			
+		
+	
+	
+		org.eclipse.jdt.core.javanature
+	
+
diff --git a/arduino-autocomplete/build.xml b/arduino-autocomplete/build.xml
new file mode 100755
index 00000000000..202058c634d
--- /dev/null
+++ b/arduino-autocomplete/build.xml
@@ -0,0 +1,66 @@
+
+
+
+  
+    
+      
+    
+    
+      
+      
+      
+    
+    
+    
+  
+
+  
+    
+    
+  
+
+  
+    
+      
+    
+    
+
+
+    
+
+    
+
+    
+    
+    
+
+    
+    
+
+    
+      
+        
+        
+      
+    
+  
+
+  
+    
+  
+
+
diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/ArduinoLibraryScanner.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/ArduinoLibraryScanner.java
new file mode 100755
index 00000000000..ea8a7a3a573
--- /dev/null
+++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/ArduinoLibraryScanner.java
@@ -0,0 +1,55 @@
+package cc.arduino.packages.autocomplete;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import processing.app.helpers.filefilters.OnlyFilesWithExtension;
+import processing.app.packages.Library;
+import br.com.criativasoft.cpluslibparser.LibraryScanner;
+import br.com.criativasoft.cpluslibparser.SourceParser;
+
+/**
+ * Scanner for Arduino libraries.
+ * This class is only a bridge between the libraries and the {@link SourceParser}, making the settings and adjustments
+ * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) 
+ * @date 20/11/2014
+ */
+public class ArduinoLibraryScanner extends LibraryScanner {
+  
+  private Library library;
+  
+  private static final List ignoredFiles = new ArrayList();
+  
+  static{
+    ignoredFiles.add("USBCore.h");
+    ignoredFiles.add("USBDesc.h");
+  }
+
+  public ArduinoLibraryScanner(Library library) {
+    this.library = library;
+  }
+
+  @Override
+  protected File[] getFilesToParse(File folder) {
+    
+    // check /src directory
+    File src = new File(folder, "src");
+    if(src.exists()){
+        folder =  src;
+    }
+    
+    return folder.listFiles(new OnlyFilesWithExtension(ignoredFiles, ".h"));
+  }
+
+  @Override
+  protected void configParser(SourceParser parser, File currentFile) {
+    parser.addMacro("UBRR0H", "1"); // Enable Serial !
+  }
+
+  @Override
+  protected String getLibraryName() {
+    return library.getName();
+  }
+
+}
diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionProviderWithContext.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionProviderWithContext.java
new file mode 100755
index 00000000000..2d209c847e3
--- /dev/null
+++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionProviderWithContext.java
@@ -0,0 +1,329 @@
+package cc.arduino.packages.autocomplete;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.swing.text.JTextComponent;
+
+import org.fife.ui.autocomplete.Completion;
+import org.fife.ui.autocomplete.DefaultCompletionProvider;
+import org.fife.ui.autocomplete.FunctionCompletion;
+import org.fife.ui.autocomplete.Util;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+
+import processing.app.helpers.Predicate;
+import br.com.criativasoft.cpluslibparser.LibraryIndex;
+import br.com.criativasoft.cpluslibparser.metadata.TAttribute;
+import br.com.criativasoft.cpluslibparser.metadata.TClass;
+import br.com.criativasoft.cpluslibparser.metadata.TClass.TClassType;
+import br.com.criativasoft.cpluslibparser.metadata.TElementLocation;
+import br.com.criativasoft.cpluslibparser.metadata.TFunction;
+import br.com.criativasoft.cpluslibparser.metadata.TLibrary;
+
+/**
+ * Autocomplete logic implementation for instance functions, static functions and local variables.
+ * It determines the options that will be presented in accordance with the current context.
+ * @author Ricardo JL Rufino (ricardo@criativasoft.com.br)
+ */
+public class CompletionProviderWithContext extends DefaultCompletionProvider{
+  
+  private final static Logger LOG = Logger.getLogger(CompletionProviderWithContext.class.getName()); 
+  
+  private static final String DOT = ".";
+  private static final String POINTER = "->";
+  private static final String STATIC = "::";
+  private TLibrary sketchLibrary; // classes, variables, functions of sketch
+  
+  private List sketchCompletions = new ArrayList(); // variables and functions of sketch
+  
+  public CompletionProviderWithContext() {
+    super();
+  }
+  
+  public void setSketchLibrary(TLibrary sketchLibrary) {
+    this.sketchLibrary = sketchLibrary;
+  }
+
+  @Override
+  public List getCompletionsImpl(JTextComponent comp) {
+    
+    RSyntaxTextArea textarea = (RSyntaxTextArea) comp;
+
+    String entredText = getAlreadyEnteredText(comp);
+
+    List contextCompletions = null;
+    
+    LOG.finest("entredText = "+ entredText);
+
+    // Functions/Attrs of Classes
+    if (entredText != null && entredText.length() > 0) {
+
+      String separator = null;
+
+      if (entredText.contains(DOT))    separator = DOT;
+      if (entredText.contains(POINTER))separator = POINTER;
+      if (entredText.contains(STATIC)) separator = STATIC;
+
+      if (separator != null) {
+        String var = entredText.substring(0, entredText.lastIndexOf(separator));
+        String startMethod = entredText.substring(entredText.lastIndexOf(separator) + separator.length());
+        contextCompletions = getCompletionsForClass(var, separator, startMethod, textarea);
+      }
+
+    }
+
+    // Only show functions/attrs of class.
+    if (contextCompletions != null) return contextCompletions;
+
+    List list = new LinkedList();
+
+    TFunction functionScope = getCurrentFunctionScope(textarea);
+
+    if (functionScope != null && sketchCompletions != null  && !sketchCompletions.isEmpty()) {
+
+      // Variables and functions
+      list.addAll(sketchCompletions);
+
+      // Local variables of functions.
+      list.addAll(getVariablesInCurrentScope(textarea));
+
+      // Need to order before searching
+      if(list.size() != sketchCompletions.size()){
+        Collections.sort(list, comparator);
+      }
+
+      List filterCompletions = filterCompletions(entredText, list);
+      
+      if (filterCompletions != null) list = filterCompletions;
+
+    }
+
+    List completions = super.getCompletionsImpl(comp);
+
+    // Outside of function (only show classes)
+    if (functionScope == null) {
+      for (Completion completion : completions) {
+        if (!(completion instanceof FunctionCompletion)) {
+          list.add(completion);
+        }
+      }
+    } else {
+      list.addAll(completions);
+    }
+
+    return list;
+    
+  }
+  
+  public List getVariablesInCurrentScope(RSyntaxTextArea textarea){
+    
+    int caretPos = textarea.getCaretPosition();
+    TFunction functionScope = getCurrentFunctionScope(textarea);
+    List completions = new ArrayList();
+    
+    if(functionScope != null){
+      
+      Set localVariables = functionScope.getLocalVariables();
+      
+      if(localVariables != null){
+        for (TAttribute var : localVariables) {
+          if(var.getLocation().getStartOffset() <= caretPos){ // try to filter variables not declared at cursor position
+                                                              // TODO: this may not work if many rows were changed after the last parser
+            completions.add(new TElementCompletion(this, var));
+          }
+        }
+      }    
+      
+    }
+    
+
+    return completions;
+    
+  }
+  
+  public List getCompletionsForClass(String context, String token, String startMethod, RSyntaxTextArea textarea){
+    
+    List linkedList = new LinkedList();
+    
+    boolean isInstance = (token == POINTER || Character.isLowerCase(context.charAt(0)));
+    boolean isStatic = (token == STATIC && Character.isUpperCase(context.charAt(0)));
+    
+    TClass tClass = null;
+    
+    if(!isStatic){
+      // Find the correct type based on global/local variables
+      Set variables = new HashSet();
+      variables.addAll(sketchLibrary.getGlobalVariables());
+      
+      TFunction functionScope = getCurrentFunctionScope(textarea);
+      
+      if(functionScope != null){
+        variables.addAll(functionScope.getLocalVariables());
+      }
+      
+      for (TAttribute attribute : variables) {
+        if(attribute.name().equals(context)){
+          tClass = LibraryIndex.getClass(attribute.getType()); 
+          isInstance = true;
+          break;
+        }
+      } 
+    }
+    
+    if(tClass == null){
+      tClass = LibraryIndex.getClass(context); // if isInstance, ignore case !!
+    }
+    
+    if(tClass != null){
+      
+      Collection functions = tClass.getFunctions(true);
+      Collection attributes = tClass.getAttributes();
+      
+      // if the user had entered part of the function/attr name
+      if(startMethod != null && startMethod.length() > 0){
+        int matchMode = (startMethod.length() > 2 ? TElementFilter.MATCH_CONTAINS : TElementFilter.MATCH_START);
+        functions = Predicate.filter(functions, new TFunctionFilter(startMethod, matchMode));
+        attributes = Predicate.filter(attributes, new TAttributeFilter(startMethod, matchMode));
+      }
+      
+      String startTemplate = context + token;
+      
+      for (TFunction tFunction : functions) {
+        
+        if(!tFunction.isPublic() || tFunction.isConstructor()) continue;
+        
+        boolean add = false;
+        
+        // Instance
+        if(isInstance && !tFunction.isStatic()){
+          add = true;
+        }
+        
+        // Static
+        if(!isInstance && tFunction.isStatic()){
+          add = true;
+        }   
+        
+        // Extern (show static and instance)
+        if(tClass.getType() == TClassType.EXTERN){
+          add = true;
+        }   
+        
+        if(add){
+          linkedList.add(new TFunctionCompletion(this, tFunction, startTemplate)); 
+        }
+
+      }
+      
+      for (TAttribute attr : attributes) {
+        
+        // Instance
+        if(isInstance && !attr.isStatic()){
+          linkedList.add(new TElementCompletion(this, attr , startTemplate));        
+        }
+        
+        // Static
+        if(!isInstance && attr.isStatic() || tClass.getType() == TClassType.EXTERN){
+          linkedList.add(new TElementCompletion(this, attr , startTemplate)); 
+        }       
+
+      }
+      
+    }
+    
+    return linkedList;
+  }
+  
+  public void setSketchCompletions(List sketchCompletions) {
+    this.sketchCompletions = sketchCompletions;
+    Collections.sort(sketchCompletions, comparator);
+  }
+  
+  public List getSketchCompletions() {
+    return sketchCompletions;
+  }
+  
+  
+  /**
+   * Find function at the caret position
+   * @param textarea
+   * @return
+   */
+  public TFunction getCurrentFunctionScope(RSyntaxTextArea textarea){
+    
+    int caretPos = textarea.getCaretPosition();
+    
+    Set globalFunctions = sketchLibrary.getGlobalFunctions();
+    
+    for (TFunction tFunction : globalFunctions) {
+      TElementLocation location = tFunction.getLocation();
+      if(location != null && location.containsOffset(caretPos)){
+        
+        LOG.finest("getCurrentFunctionScope = " + tFunction + ", loc: " + location.toString());
+        
+        return tFunction;
+      }
+    }
+
+    return null;
+  }
+  
+  /**
+   * Filter through the options that can be applied to the text that the user entered
+   * @param alreadyEnteredText
+   * @param completions - Must be ordered before searching
+   * @return
+   */
+  protected List filterCompletions(String alreadyEnteredText, List completions) {
+    
+    if(alreadyEnteredText == null || alreadyEnteredText.isEmpty()) return new ArrayList(completions);
+    List retVal = new ArrayList();
+    
+    if (alreadyEnteredText!=null) {
+
+      int index = Collections.binarySearch(completions, alreadyEnteredText, comparator);
+      if (index<0) { // No exact match
+        index = -index - 1;
+      }
+      else {
+        // If there are several overloads for the function being
+        // completed, Collections.binarySearch() will return the index
+        // of one of those overloads, but we must return all of them,
+        // so search backward until we find the first one.
+        int pos = index - 1;
+        while (pos>0 &&
+            comparator.compare(completions.get(pos), alreadyEnteredText)==0) {
+          retVal.add(completions.get(pos));
+          pos--;
+        }
+      }
+
+      while (index' == ch  || '-' == ch  || '<' == ch || '#' == ch || ':' == ch /**|| getParameterListStart() == ch */;
+  }
+
+}
diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionType.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionType.java
new file mode 100755
index 00000000000..23723cb9dd1
--- /dev/null
+++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionType.java
@@ -0,0 +1,15 @@
+package cc.arduino.packages.autocomplete;
+
+public enum CompletionType {
+  LIBRARY,
+  CLASS,
+  ENUM,
+  STRUCT,
+  LOCAL_VAR,
+  STATIC_VAR,
+  VARIABLE,
+  FUNCTION,
+  TEMPLATE,
+  ERROR
+
+}
diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionsRenderer.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionsRenderer.java
new file mode 100755
index 00000000000..e8178e0c3a5
--- /dev/null
+++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/CompletionsRenderer.java
@@ -0,0 +1,132 @@
+package cc.arduino.packages.autocomplete;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JList;
+
+import org.fife.ui.autocomplete.BasicCompletion;
+import org.fife.ui.autocomplete.FunctionCompletion;
+import org.fife.ui.autocomplete.ShorthandCompletion;
+import org.fife.ui.autocomplete.TemplateCompletion;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+
+import br.com.criativasoft.cpluslibparser.metadata.TElement;
+import br.com.criativasoft.cpluslibparser.metadata.TFunction;
+
+/**
+ * Class responsible for formatting the elements of the autocomplete list
+ * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) 
+ * @date 11/12/2014
+ */
+public class CompletionsRenderer extends DefaultListCellRenderer {
+  
+  private static final long serialVersionUID = 1L;
+
+  private static final Color HIGHLIGHT_COLOR = new Color(74, 144, 217);
+  
+  private static final String GRAY = "#A0A0A0";
+  private static final String LIGHT_BLUE = "#008080";
+  
+  /** Keeps the HTML descriptions from "wrapping" in the list, which cuts off words. */
+  private static final String PREFIX = "";
+  
+  private static Map iconsTypes = new HashMap();
+  
+  static{
+    iconsTypes.put(CompletionType.CLASS, getIcon("class.gif"));
+    iconsTypes.put(CompletionType.ENUM, getIcon("enum.gif"));
+    iconsTypes.put(CompletionType.VARIABLE, getIcon("variable.gif"));
+    iconsTypes.put(CompletionType.LOCAL_VAR, getIcon("variable_local.gif"));
+    iconsTypes.put(CompletionType.STATIC_VAR, getIcon("variable_static.gif"));
+    iconsTypes.put(CompletionType.FUNCTION, getIcon("function.gif"));
+    iconsTypes.put(CompletionType.ERROR, getIcon("error.gif"));
+    iconsTypes.put(CompletionType.TEMPLATE, getIcon("template_obj.gif"));
+  }
+  
+  
+  private static Icon getIcon(String image){
+     return new ImageIcon(CompletionsRenderer.class.getResource("icons/"+image));
+  }
+  
+  public static Icon getIcon(CompletionType tokenType){
+    return iconsTypes.get(tokenType);
+  }
+
+  public CompletionsRenderer() {
+    setOpaque(true);
+    // Font f = Theme.getDefaultFont();
+    Font f = RSyntaxTextArea.getDefaultFont();
+    setFont(f.deriveFont(f.getStyle() & ~Font.BOLD)); // remove bold.
+  }
+  
+  @Override
+  public Component getListCellRendererComponent(JList list, Object value,
+                                                int index, boolean isSelected,
+                                                boolean cellHasFocus) {
+    
+    String text = null;
+    CompletionType tokenType = null;
+    
+    if(value instanceof TElementCompletion){
+      
+      TElementCompletion completion = (TElementCompletion) value;
+      TElement element = completion.getElement();
+      tokenType = completion.getType();
+      text = element.getHtmlRepresentation();
+      
+    }else if(value instanceof TFunctionCompletion){
+      
+      TFunctionCompletion completion =  (TFunctionCompletion) value;
+      TFunction function = completion.getFunction();
+      text = function.getHtmlRepresentation();
+      tokenType =  CompletionType.FUNCTION;      
+      
+    }else if(value instanceof ShorthandCompletion){
+      text = ((ShorthandCompletion) value).getShortDescription();
+      tokenType = CompletionType.TEMPLATE;
+    }else if(value instanceof FunctionCompletion){
+      text = ((FunctionCompletion) value).getShortDescription();
+      tokenType = CompletionType.FUNCTION;
+    }else if(value instanceof BasicCompletion){
+      text = ((BasicCompletion) value).getInputText();
+    }else if(value instanceof TemplateCompletion){
+      TemplateCompletion template = (TemplateCompletion) value;
+      text = font(template.getInputText(), LIGHT_BLUE) + font(" - " + template.getDefinitionString(), GRAY) ;
+    }
+    
+    if(text == null){
+      text = value.toString();
+    }
+    
+    // Find Icon
+    if(tokenType != null){
+      setIcon(iconsTypes.get(tokenType));
+    }else{
+      setIcon(iconsTypes.get(CompletionType.TEMPLATE));
+    }
+    
+    if (isSelected) {
+      setText(text.replaceAll("\\<[^>]*>",""));
+      setBackground(HIGHLIGHT_COLOR);
+      setForeground(Color.white);
+    } else {
+      setText(PREFIX + text);
+      setBackground(Color.white);
+      setForeground(Color.black);
+    }
+    
+    return this;
+  }
+  
+  private String font(String text, String color){
+    return ""+text+"";
+  }
+
+}
diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/ParameterChoicesProvider.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/ParameterChoicesProvider.java
new file mode 100755
index 00000000000..ae799c12873
--- /dev/null
+++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/ParameterChoicesProvider.java
@@ -0,0 +1,132 @@
+package cc.arduino.packages.autocomplete;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.text.JTextComponent;
+
+import org.fife.ui.autocomplete.BasicCompletion;
+import org.fife.ui.autocomplete.Completion;
+import org.fife.ui.autocomplete.CompletionProvider;
+import org.fife.ui.autocomplete.CompletionProviderBase;
+import org.fife.ui.autocomplete.ParameterizedCompletion;
+import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter;
+
+import cc.arduino.packages.autocomplete.TFunctionCompletion.TFunctionParam;
+import br.com.criativasoft.cpluslibparser.metadata.TFunction;
+import br.com.criativasoft.cpluslibparser.metadata.TParam;
+
+/**
+ * This is used when a user code-completes a parameterized completion, such as a function or method. 
+ * For any parameter to the function/method, this object can return possible completions. + * @see {@link CompletionProviderBase#setParameterChoicesProvider(org.fife.ui.autocomplete.ParameterChoicesProvider)} + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 09/12/2014 + */ +public class ParameterChoicesProvider implements org.fife.ui.autocomplete.ParameterChoicesProvider { + + private SketchCompletionProvider provider; + + public ParameterChoicesProvider(SketchCompletionProvider provider) { + super(); + this.provider = provider; + } + + @Override + public List getParameterChoices(JTextComponent tc, ParameterizedCompletion pc, Parameter param) { + + if (param instanceof TFunctionParam) { + + TFunctionParam functionParam = (TFunctionParam) param; + TFunction function = functionParam.getFunction(); + TParam tParam = functionParam.getAttributeParam(); + + // FIXME: REMOVE AFTER IMPLEMENT PARSER FROM SOURCE + if (function.name().equals("begin") && tParam.name().equals("baud")) { + // @param baud (Values: 300, 600, 1200 ...) + tParam.setAllowedValues("300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200"); + } + + if (function.name().equals("pinMode") && tParam.name().equals("mode")) { + tParam.setAllowedValues("OUTPUT, INPUT, INPUT_PULLUP"); + } + + if (function.name().equals("digitalWrite") && tParam.name().equals("value")) { + tParam.setAllowedValues("HIGH, LOW"); + } + + // and: remove -------------- + + if (tParam.getAllowedValues() != null) { + return createFromAllowedValues(tParam); + } + + } + + if(pc instanceof TemplateChoicesCompletion){ + TemplateChoicesCompletion choicesCompletion = (TemplateChoicesCompletion) pc; + return choicesCompletion.getChoices(param); + } + + return null; + } + + private List createFromAllowedValues(TParam param) { + + List completions = new LinkedList(); + + String allowedValues = param.getAllowedValues(); + + String[] strings = allowedValues.split(","); + + for (String key : strings) { + completions.add(new ParamCompletion(provider, key.trim(), param.getType())); + } + + return completions; + } + + + // it is only necessary to properly sort integer values. + private static class ParamCompletion extends BasicCompletion { + + private Long value; + + public ParamCompletion(CompletionProvider provider, String replacementText, + String type) { + super(provider, replacementText); + + if (isNumber(type)) { + try { + value = Long.parseLong(replacementText); + } catch (Exception e) {} + } + + } + + private boolean isNumber(String type) { + if (type.contains("int") || type.contains("long")) { + return true; + } + + return false; + } + + public Long getValue() { + return value; + } + + @Override + public int compareTo(Completion c2) { + + if(value != null){ + return value.compareTo(((ParamCompletion)c2).getValue()); + }else{ + return super.compareTo(c2); + } + + } + + } + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/RealtimeCompletionsListener.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/RealtimeCompletionsListener.java new file mode 100755 index 00000000000..5a43555f940 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/RealtimeCompletionsListener.java @@ -0,0 +1,174 @@ +package cc.arduino.packages.autocomplete; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.Set; +import java.util.logging.Logger; + +import javax.swing.Timer; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.Segment; + +import processing.app.BaseSketch; +import processing.app.SketchCode; +import br.com.criativasoft.cpluslibparser.LibraryIndex; +import br.com.criativasoft.cpluslibparser.metadata.TElement; +import br.com.criativasoft.cpluslibparser.metadata.TError; +import br.com.criativasoft.cpluslibparser.metadata.TFunction; +import br.com.criativasoft.cpluslibparser.metadata.TLibrary; + +/** + * This is the class responsible for monitoring the events and changes in the code and decide when it should + * be done a parser to extract the metadata to make the autocomplete
+ * This listener is registered by: {@link SketchCompletionProvider#onSketchInserted(Sketch, SketchCode)} and {@link PdeTextArea}. + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 22/11/2014 + */ +public class RealtimeCompletionsListener implements KeyListener, DocumentListener { + + private final static Logger LOG = Logger.getLogger(RealtimeCompletionsListener.class.getName()); + + private BaseSketch sketch; + private Segment segment = new Segment(); + private Timer debounce; + private int startOffs; + private int endOffs; + private int startLine; + private int endLine; + + public RealtimeCompletionsListener(BaseSketch sketch) { + this.sketch = sketch; + debounce = new Timer(800, new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + analizeModified(); + } + }); + debounce.setRepeats(false); + + } + + @Override + public void keyTyped(KeyEvent e) { + + } + + @Override + public void keyPressed(KeyEvent e) { + + } + + @Override + public void keyReleased(KeyEvent e) { + if(e.getKeyChar() == ';' || e.getKeyChar() == '}'){ + fireParser(); + } + } + + + protected void fireParser(){ + SketchCode sketchCode = sketch.getCurrentCode(); + TLibrary library = sketch.getSketchMetadata(); + if(library != null){ + + long time = System.currentTimeMillis(); + if(library.getLastUpdate() > 0 && (time - library.getLastUpdate() < 2000 )){ + LOG.fine("Ignoring parser, a recent parser has been done !"); + return; + } + + library.clear(); + } + + try { + Document document = SketchCompletionProvider.getDocument(sketchCode); + String code = document.getText(0, document.getLength()); + LibraryIndex.scanSource(code, new SketchCodeScanner(sketch.getCurrentCode())); // fire #onLoadLibrary on finish + } catch (BadLocationException e) { + LOG.severe(e.getMessage()); + } + } + + @Override + public void insertUpdate(DocumentEvent e) { + startOffs = e.getOffset(); + endOffs = startOffs + e.getLength(); + Document doc = e.getDocument(); + Element root = doc.getDefaultRootElement(); + startLine = root.getElementIndex(startOffs); + endLine = root.getElementIndex(endOffs); + + debounce.restart(); // waits for the user stops typing + + try { + doc.getText(startOffs, e.getLength(), segment); + + if (startLine != endLine) { // Inserted text covering > 1 line... + // fireParser(); + // System.out.println("RealtimeCompletionsListener.insertUpdate: startLine != endLine"); + + } else if (segment.length() > 0&& segment.charAt(segment.length() - 1) == '}') { + // System.out.println("RealtimeCompletionsListener.insertUpdate: == '}'"); + } + } catch (BadLocationException e2) { + e2.printStackTrace(); + } + + } + + @Override + public void removeUpdate(DocumentEvent e) { + + debounce.restart(); // waits for the user stops typing + + } + + @Override + public void changedUpdate(DocumentEvent e) { + + } + + protected void analizeModified(){ + + + if(sketch != null && sketch.getSketchMetadata() != null){ + TLibrary metadata = sketch.getSketchMetadata(); + + // Check errors + if(TError.containsError(startOffs, metadata.getErrors())){ + LOG.fine("Offset contains erros, firing parser"); + fireParser(); + return; + } + + // Variables + if(TElement.contains(startOffs, metadata.getGlobalVariables())){ + LOG.fine("Changing a global variable, firing parser"); + fireParser(); + return; + } + + Set functions = metadata.getGlobalFunctions(); + + for (TFunction function : functions) { + if(!"setup".equals(function.name())){ + if(TElement.contains(startOffs, function.getLocalVariables())){ + LOG.fine("Changing a local variable, firing parser"); + fireParser(); + return; + } + } + } + + } + + } + + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/SketchCodeScanner.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/SketchCodeScanner.java new file mode 100755 index 00000000000..d17eb50fe92 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/SketchCodeScanner.java @@ -0,0 +1,54 @@ +package cc.arduino.packages.autocomplete; + +import java.io.File; + +import processing.app.BaseSketch; +import processing.app.SketchCode; +import processing.app.helpers.filefilters.OnlyFilesWithExtension; +import br.com.criativasoft.cpluslibparser.LibraryScanner; +import br.com.criativasoft.cpluslibparser.SourceParser; + +public class SketchCodeScanner extends LibraryScanner { + + private SketchCode code; + private BaseSketch sketch; + + public SketchCodeScanner(SketchCode code) { + this(code.getSketch()); + this.code = code; + } + + public SketchCodeScanner(BaseSketch sketch) { + super(); + this.sketch = sketch; + setSerialize(false); // not cache + setDeserialize(false); // not cache + } + + @Override + protected File[] getFilesToParse(File folder) { + + if(code != null){ // single file{ + + return new File[]{code.getFile()}; + + }else{ + + return folder.listFiles(new OnlyFilesWithExtension("ino", "pde", "c", "cpp", "h")); + + } + + } + + @Override + protected void configParser(SourceParser parser, File currentFile) { + parser.setParseInternalAttrs(true); + if(currentFile == null) parser.setDefaultFileName(code.getFileName()); + } + + @Override + protected String getLibraryName() { + return SketchCompletionProvider.SKETCH_LIB_PREFIX + sketch.getName(); + } + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/SketchCompletionProvider.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/SketchCompletionProvider.java new file mode 100755 index 00000000000..9c5abb2e08c --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/SketchCompletionProvider.java @@ -0,0 +1,496 @@ +package cc.arduino.packages.autocomplete; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.text.Document; + +import org.fife.ui.autocomplete.AutoCompletion; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.LanguageAwareCompletionProvider; +import org.fife.ui.autocomplete.TemplateCompletion; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; + +import processing.app.BaseNoGui; +import processing.app.BaseSketch; +import processing.app.SketchCode; +import processing.app.SketchDocumentProvider; +import processing.app.helpers.StringUtils; +import processing.app.packages.Library; +import processing.app.packages.LibraryList; +import processing.app.packages.LibraryListener; +import processing.app.packages.SketchListener; +import br.com.criativasoft.cpluslibparser.LibraryIndex; +import br.com.criativasoft.cpluslibparser.LibraryIndexListener; +import br.com.criativasoft.cpluslibparser.metadata.*; +import cc.arduino.packages.autocomplete.template.GenerateVarTemplate; +import cc.arduino.packages.autocomplete.template.IncludeTemplate; + +/** + * CompletionProvider for Arduino/CPP Language.
+ * This class is responsible for the settings required for the logic of autocomplete, + * external events such as adding libraries, creating new files, etc .. will be monitored.
+ * Filtering and decision will appear in the autocomplete is up to the: {@link CompletionProviderWithContext}.
+ * Another class that works together is the: {@link RealtimeCompletionsListener} , + * that monitor real-time changes to the text and notifies when it should be made a new parser. + *

+ * The analysis(parser) of the source code is made by the library cplus-libparser, Which was specially developed + * for the Arduino, more can be used in other projects. + * + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 22/11/2014 + */ +public class SketchCompletionProvider extends LanguageAwareCompletionProvider implements LibraryListener, LibraryIndexListener, SketchListener{ + + private final static Logger LOG = Logger.getLogger(RealtimeCompletionsListener.class.getName()); + + public static final String SKETCH_LIB_PREFIX = BaseSketch.SKETCH_LIB_PREFIX; + + /** + * Matches strings like {@code obj.myMethod(params)} + * {@code (?U)} lets {@code \\w} also match non-ASCII letters. + */ + public static final Pattern INSTANCE_CALL_REGEX = Pattern.compile("(?U)([.\\w]+)\\.([\\w]+)\\s*\\((.*)\\)"); + + public static final Pattern FUNCTION_CALL_REGEX = Pattern.compile("(?U)([.\\w]+)\\s*\\((.*)\\)"); + + private TLibrary sketchLibrary; // classes, variables, functions of sketch + private BaseSketch sketch; + private LibraryList importedLibraries; // same instance in Sketch + + private RealtimeCompletionsListener realtimeCompletionsListener; + + + private CompletionProviderWithContext provider = new CompletionProviderWithContext(); // Loaded static auto-completes. + + private AutoCompletion autoCompletion; + + public SketchCompletionProvider(BaseSketch sketch) { + super(); + this.sketch = sketch; + + provider.setParameterChoicesProvider(new ParameterChoicesProvider(this)); + setDefaultCompletionProvider(provider); + provider.setParameterizedCompletionParams('(', ", ", ')'); + + LibraryIndex.addListener(this); + sketch.addListener(this); + realtimeCompletionsListener = new RealtimeCompletionsListener(sketch); + + // Import existing... + + // Arduino Core + //======================= + File folder = BaseNoGui.getTargetPlatform().getFolder(); + File coreFolder = new File(folder,"cores"+File.separator+"arduino"); + if(coreFolder.exists()){ + Library coreLib; + try { + coreLib = Library.create(coreFolder); + LibraryIndex.scanFolder(coreFolder, new ArduinoLibraryScanner(coreLib)); // fire #onLoadLibrary on finish + } catch (IOException e) { + e.printStackTrace(); + } + } + + LibraryIndex.scanFolder(sketch.getFolder(), new SketchCodeScanner(sketch)); // fire #onLoadLibrary on finish + + + onSketchLoad(sketch); + for (SketchCode sketchCode : sketch.getCodes()) { + onSketchInserted(sketch, sketchCode); + } + + loadDefaultAutocompletes(); + } + + + /** + * Remove all added listeners + */ + public void uninstall() { + LibraryIndex.removeListener(this); + sketch.removeListener(this); + importedLibraries.addListener(this); + + for (SketchCode code : sketch.getCodes()) { + Document document = getDocument(code); + if(document != null) + document.removeDocumentListener(realtimeCompletionsListener); + } + } + + + // ========================================== + // Sketch Library (importedLibraries) Listener + // ========================================== + + @Override + public void onInsertLibrary(Library library) { + + if(sketch.isExternalMode()) return; + + LOG.fine("Arduino Lib: " + library); + LibraryIndex.scanFolder(library.getFolder(), new ArduinoLibraryScanner(library)); // scan lib and fire #onLoadLibrary + } + + @Override + public void onRemoveLibrary(Library library) { + + if(sketch.isExternalMode()) return; + + LOG.fine("Arduino Lib: " + library); + } + + @Override + public void onClearLibraryList() { + + if(sketch.isExternalMode()) return; + + } + + + // ========================================== + // SketchCode Listener + // ========================================== + + @Override + public void onSketchLoad(BaseSketch sketch) { + + if(sketch.isExternalMode()) return; + + LOG.fine(sketch.getName()); + + importedLibraries = sketch.getImportedLibraries(); + importedLibraries.addListener(this); + for (Library library : importedLibraries) { + onInsertLibrary(library); + } + } + + + @Override + public void onSketchInserted(BaseSketch sketch, SketchCode code) { + + if(sketch.isExternalMode()) return; + + LOG.fine(code.getFileName()); + + Object metadata = code.getMetadata(); + if(metadata instanceof SketchDocumentProvider){ + Document document = ((SketchDocumentProvider) metadata).getDocument(); + if(document != null) + document.addDocumentListener(realtimeCompletionsListener); + } + + } + + @Override + public void onSketchSaved(BaseSketch sketch, SketchCode code) { + + if(sketch.isExternalMode()) return; + + LOG.fine(code.getFileName()); + + if(sketchLibrary != null){ + sketchLibrary.clear(); // Clear to get the new variables and remove those that no longer exist + } + + LibraryIndex.scanFolder(sketch.getFolder(), new SketchCodeScanner(code)); // fire #onLoadLibrary on finish + } + + + // ========================================== + // LibraryIndex Listener + // ========================================== + + @Override + public void onLoadLibrary(TLibrary library) { + + if(sketch.isExternalMode()) return; + + if(library.name().equals(SKETCH_LIB_PREFIX+sketch.getName())){ // Sketch indexing finish + sketchLibrary = library; + sketchLibrary.setReloadable(true); + provider.setSketchLibrary(sketchLibrary); + sketch.setSketchMetadata(sketchLibrary); + } + + createCompletions(library); + } + + @Override + public void onUnloadLibrary(TLibrary library) { + LOG.fine(library.name()); + + } + + @Override + public void onReloadLibrary(TLibrary library) { + + Map libraries = LibraryIndex.getLibraries(); + + System.out.println("onReloadLibrary libraries = " + libraries.keySet()); + + if(sketch.isExternalMode()) return; + + createCompletions(library); + } + + protected void loadDefaultAutocompletes(){ + + List completions = new ArrayList(); + + completions.add(new IncludeTemplate(provider)); + completions.add(new TemplateCompletion(provider, "for", "interate over array", "for (int ${i} = 0; ${i} < ${array}.length; ${i}++) {\n\t${cursor}\n}")); + completions.add(new TemplateCompletion(provider, "while", "while block", "while (${condition}) {\n\t${cursor}\n}")); + completions.add(new TemplateCompletion(provider, "if", "if block", "if (${condition}) {\n\t${cursor}\n}")); + completions.add(new TemplateCompletion(provider, "elseif", "elseif block", "else if (${condition}) {\n\t${cursor}\n}")); + completions.add(new TemplateCompletion(provider, "else", "else block", "else{\n\t${cursor}\n}")); + + completions.add(new TemplateCompletion(provider, "println", "Serial.println()", "Serial.println(\"${cursor}\");")); + + //Add as ENUNM + // TODO: only show if in method params + String[] names = {"HIGH", "LOW", "OUTPUT", "INPUT", "INPUT_PULLUP", "CHANGE", "FALLING", "RISING", "RISING", "DEC", "HEX", "OCT", "BIN"}; + for (String name : names) { + TAttribute attribute = new TAttribute("int", name); + attribute.setEnum(true); + completions.add(new TElementCompletion(provider, attribute)); + } + + provider.addCompletions(completions); + } + + + /** + * Add Completions from parsed lib + */ + protected void createCompletions( TLibrary library ) { + + LOG.fine("build auto-complete for: " + library.name()); + + Set allMembers = library.getAllMembers(); + + // Sketch indexing finish + // sketchCompletions are dynamic. + if (sketchLibrary != null && library.name().equals(sketchLibrary.name())) { + + List sketchCompletions = new ArrayList(); + + for (TElement element : allMembers) { + + // Set sketck attrs as not static + if (element instanceof TAttribute) { + ((TAttribute) element).setStatic(false); + } + + // track positions of methods and variables even after document changes + TElementLocation location = element.getLocation(); + if (location != null) { + Document document = findSketchDocument(element); + // If if document already loaded + if (document != null) { + TDynamicLocation dynamicLocation = new TDynamicLocation(document, location); + element.setLocation(dynamicLocation); + } + } + + if (element instanceof TFunction) { + if (element.name().equals("setup") || element.name().equals("loop")) continue; + TFunctionCompletion completion = new TFunctionCompletion(provider, (TFunction) element); + completion.setRelevance(3); + sketchCompletions.add(completion); + } else { + sketchCompletions.add(new TElementCompletion(provider, element)); + } + } + + provider.setSketchCompletions(sketchCompletions); + + } else { + + List staticCompletions = new ArrayList(); + + Set classes = library.getClasses(); + for (TClass tClass : classes) { + // XXX autocomplete: PdeTokenMaker.addKeyword(tClass.name(), Token.DATA_TYPE); + } + + for (TElement element : allMembers) { + if (element instanceof TFunction) { // global func. + if (element.name().equals("setup") || element.name().equals("loop")) continue; + TFunctionCompletion completion = new TFunctionCompletion(provider, (TFunction) element); + staticCompletions.add(completion); + } else { + staticCompletions.add(new TElementCompletion(provider, element)); + } + } + + provider.addCompletions(staticCompletions); + + } + + } + + protected Document findSketchDocument(TElement element){ + + TElementLocation location = element.getLocation(); + + String name = location.getFileName(); + + SketchCode[] codes = sketch.getCodes(); + + if(codes.length == 1) return getDocument(codes[0]); + + for (SketchCode sketchCode : codes) { + if(sketchCode.getFileName().equals(name)){ + return getDocument(sketchCode); + } + } + + return null; + } + + static Document getDocument(SketchCode code){ + Object metadata = code.getMetadata(); + if(metadata instanceof SketchDocumentProvider){ + return ((SketchDocumentProvider) metadata).getDocument(); + } + return null; + } + + public BaseSketch getSketch() { + return sketch; + } + + public TFunction getCurrentFunctionScope(RSyntaxTextArea textarea){ + return provider.getCurrentFunctionScope(textarea); + } + + + public void generateNewVariableFor(String expression, int startOffSet) { + + Matcher instanceCall = INSTANCE_CALL_REGEX.matcher(expression); + + String returnType = null; + String function = null; + String varname = null; + + RSyntaxTextArea textArea = (RSyntaxTextArea) autoCompletion.getTextComponent(); + + // find retun type of function like (obj.myMethod(params)) + if (instanceCall.find()) { + String instance = instanceCall.group(1); + function = instanceCall.group(2); + + Set variables = new HashSet(); + + variables.addAll(sketchLibrary.getGlobalVariables()); + + TFunction functionScope = provider.getCurrentFunctionScope(textArea); + if(functionScope != null){ + variables.addAll(functionScope.getLocalVariables()); + } + + TClass varClass = null; + + for (TAttribute var : variables) { + + LOG.fine("generateVariableName , var : " + var); + + // find class of instance. + if(var.name().equals(instance)){ + LOG.fine("generateVariableName , found : " + var); + + String type = var.getType(); + varClass = LibraryIndex.getClass(type); + if(varClass != null) break; + } + + } + + // Find like: Serial / Ethernet. + if(varClass == null){ + varClass = LibraryIndex.getClass(instance); + } + + if(varClass != null){ + + LOG.fine("Class : " +varClass + ""); + TFunction tFunction = varClass.getFunction(function, true); + + LOG.fine("Function = " +tFunction + " (for:" + function + ")"); + + if(tFunction != null){ + returnType = tFunction.getReturnType(); + } + + } + + + }else{ + + Matcher functionCall = FUNCTION_CALL_REGEX.matcher(expression); + if (functionCall.find()) { + function = functionCall.group(1); + String param = functionCall.group(2); + + if(function.equals("digitalRead") || function.equals("analoadRead")){ + returnType = "int"; + varname = param +"Val"; + }else{ + Set functions = LibraryIndex.getGlobalFunctions(function); + + if(!functions.isEmpty()){ + returnType = functions.iterator().next().getReturnType(); + } + } + + } + + } + + + if(returnType != null){ + + if(varname == null){ + if(TElement.isPrimitive(returnType)){ + varname = function; + }else{ + String lastWord = StringUtils.findLastWord(returnType); + if(lastWord == null) lastWord = returnType; + varname = StringUtils.uncapitalize(lastWord); + } + } + + if(varname != null){ + + GenerateVarTemplate completion = new GenerateVarTemplate(provider, returnType, varname); + + textArea.setCaretPosition(startOffSet); + + AutoCompletion autoCompletion = getAutoCompletion(); + autoCompletion.startParameterizedCompletionAssistance(completion, false); + + } + } + + } + + + public void setAutoCompletion(AutoCompletion autoCompletion) { + this.autoCompletion = autoCompletion; + } + + public AutoCompletion getAutoCompletion() { + return autoCompletion; + } + + + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TAttributeFilter.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TAttributeFilter.java new file mode 100755 index 00000000000..8c55e1b9f17 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TAttributeFilter.java @@ -0,0 +1,15 @@ +package cc.arduino.packages.autocomplete; + +import br.com.criativasoft.cpluslibparser.metadata.TAttribute; + +/** + * Simple collections filter + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public class TAttributeFilter extends TElementFilter { + + public TAttributeFilter(String text, int mathType) { + super(text, mathType); + } + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TDynamicLocation.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TDynamicLocation.java new file mode 100755 index 00000000000..fd387a551e2 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TDynamicLocation.java @@ -0,0 +1,120 @@ +package cc.arduino.packages.autocomplete; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.Position; + +import br.com.criativasoft.cpluslibparser.metadata.TElement; +import br.com.criativasoft.cpluslibparser.metadata.TElementLocation; + +/** + * Element to track positions of methods and variables even after document changes.
+ * This feature is implemented by the swing api ({@link Position}) + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public class TDynamicLocation extends TElementLocation { + + private Document document; + private Position startOffs; + private Position endOffs; + + private int lastEndOffs = -1; + private int cachedEndLine; + private TElementLocation reference; + + public TDynamicLocation(Document document, TElement element) { + this(document, element.getLocation()); + element.setLocation(this); + } + + public TDynamicLocation(Document document, TElementLocation reference) { + super(); + this.document = document; + try { + this.startOffs = document.createPosition(reference.getStartOffset()); + this.endOffs = document.createPosition(reference.getEndOffset()); + this.reference = reference; + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + + + /** + * Returns whether the specified offset is "inside". This method + * returns true if the offset is greater than the element start + * offset, and no further than the last offset of the last element line. + * + * @param offs The offset to check. + * @return Whether the offset is "inside" the element. + * @see #containsLine(int) + */ + public boolean containsOffset(int offs) { + boolean contained = false; + if (offs>getStartOffset()) { + // Use Elements to avoid BadLocationExceptions + Element root = document.getDefaultRootElement(); + int line = root.getElementIndex(offs); + contained = line<=getEndLine(); + } + return contained; + } + + /** + * Returns the end line of this Element. + * + * The value returned by this method will automatically update as the + * text area's contents are modified, to track the ending line of the + * code block. + * + * @return The end line of this code block. + * @see #getEndOffset() + */ + public int getEndLine() { + int endOffs = getEndOffset(); + if (lastEndOffs==endOffs) { + return cachedEndLine; + } + lastEndOffs = endOffs; + Element root = document.getDefaultRootElement(); + return cachedEndLine = root.getElementIndex(endOffs); + } + + + /** + * Returns the starting offset of this Element. + * + * The value returned by this method will automatically update as the + * text area's contents are modified, to track the starting offset of the + * code block. + * + * @return The start offset of this fold. + * @see #getEndOffset() + */ + public int getStartOffset() { + return startOffs.getOffset(); + } + + /** + * Returns the end offset of this Element. + * + * The value returned by this method will automatically update as the + * text area's contents are modified, to track the ending offset of the + * code block. + * + * @return The end offset of this code block. + * @see #getEndLine() + */ + public int getEndOffset() { + return endOffs!=null ? endOffs.getOffset() : Integer.MAX_VALUE; + } + + + @Override + public String toString() { + return "["+getStartOffset()+", "+getEndOffset()+"], orig: " + reference.toString(); + } + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TElementCompletion.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TElementCompletion.java new file mode 100755 index 00000000000..7a53577ef10 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TElementCompletion.java @@ -0,0 +1,114 @@ +package cc.arduino.packages.autocomplete; + +import org.fife.ui.autocomplete.AbstractCompletion; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.CompletionProvider; +import org.fife.ui.rsyntaxtextarea.Token; + +import br.com.criativasoft.cpluslibparser.metadata.TAttribute; +import br.com.criativasoft.cpluslibparser.metadata.TClass; +import br.com.criativasoft.cpluslibparser.metadata.TElement; +import br.com.criativasoft.cpluslibparser.metadata.TFunction; +import br.com.criativasoft.cpluslibparser.metadata.TLibrary; + +public class TElementCompletion extends AbstractCompletion { + + private TElement element; + private String alreadyEntered; + + public TElementCompletion(CompletionProvider provider, TElement element) { + super(provider); + this.element = element; + } + + public TElementCompletion(CompletionProvider provider, TElement element, String alreadyEntered) { + super(provider); + this.element = element; + this.alreadyEntered = alreadyEntered; + } + + @Override + public String getReplacementText() { + + if(alreadyEntered == null || alreadyEntered.length() == 0) return element.name(); + + return alreadyEntered + element.name(); + } + + @Override + public String getSummary() { + return element.toString(); + } + + @Override + public String getInputText() { + return element.name(); + } + + public TElement getElement() { + return element; + } + + public String getShortDescription(){ + return element.name(); + } + + + public CompletionType getType(){ + + if(element instanceof TLibrary){ + return CompletionType.LIBRARY; + } + + if(element instanceof TClass){ + return CompletionType.CLASS; + } + + if(element instanceof TFunction){ + return CompletionType.FUNCTION; + } + + if(element instanceof TAttribute){ + TAttribute attribute = (TAttribute) element; + if(attribute.isLocal()) return CompletionType.LOCAL_VAR; + if(attribute.isEnum()) return CompletionType.ENUM; + if(attribute.isStatic()) return CompletionType.STATIC_VAR; + return CompletionType.VARIABLE; + } + + return CompletionType.VARIABLE; + + } + + + @Override + public int getRelevance() { + if(element instanceof TLibrary){ + return 1; + } + + if(element instanceof TClass){ + return 2; + } + + if(element instanceof TFunction){ + return 3; + } + + if(element instanceof TAttribute){ + TAttribute attribute = (TAttribute) element; + if(attribute.isLocal()) return 7; + if(attribute.isEnum()) return 5; + if(attribute.isStatic()) return 4; + return 6; + } + + return 0; + } + + @Override + public String toString() { + return element.name(); + } + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TElementFilter.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TElementFilter.java new file mode 100755 index 00000000000..19b56b4594d --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TElementFilter.java @@ -0,0 +1,46 @@ +package cc.arduino.packages.autocomplete; + +import processing.app.helpers.IPredicate; +import br.com.criativasoft.cpluslibparser.metadata.TElement; + +/** + * Simple collections filter + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public class TElementFilter implements IPredicate { + + public static final int MATCH_START = 1; + public static final int MATCH_CONTAINS = 2; + public static final int MATCH_END = 3; + + private String text; + + private int mathType = MATCH_START; + + + public TElementFilter(String text, int mathType) { + super(); + this.text = text; + this.mathType = mathType; + } + + + @Override + public boolean apply(TElement node) { + String name = node.name().toLowerCase(); + + if(mathType == MATCH_START){ + return name.startsWith(text); + } + if(mathType == MATCH_CONTAINS){ + return name.contains(text); + } + if(mathType == MATCH_END){ + return name.endsWith(text); + } + + return false; + } + + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TFunctionCompletion.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TFunctionCompletion.java new file mode 100755 index 00000000000..7c894a2ff06 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TFunctionCompletion.java @@ -0,0 +1,91 @@ +package cc.arduino.packages.autocomplete; + +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.fife.ui.autocomplete.CompletionProvider; +import org.fife.ui.autocomplete.FunctionCompletion; + +import br.com.criativasoft.cpluslibparser.metadata.TFunction; +import br.com.criativasoft.cpluslibparser.metadata.TParam; + +public class TFunctionCompletion extends FunctionCompletion { + + private TFunction function; + private String alreadyEntered; + + public TFunctionCompletion(CompletionProvider provider, TFunction function) { + this(provider, function, null); + } + + public TFunctionCompletion(CompletionProvider provider, TFunction function, String alreadyEntered) { + super(provider, function.name(), function.getReturnType()); + this.function = function; + this.alreadyEntered = alreadyEntered; + + Set params = function.getParams(); + List list = new LinkedList(); + + for (TParam param : params) { + list.add(new TFunctionParam(param, function)); + } + + setParams(list); + } + + public TFunction getFunction() { + return function; + } + + @Override + public String getShortDescription() { + return function.toDeclarationString(); + } + + @Override + public String getInputText() { + return function.name(); + } + + + @Override + public String getReplacementText() { + + if(alreadyEntered == null || alreadyEntered.length() == 0) return super.getReplacementText(); + + return alreadyEntered + super.getReplacementText(); + } + + @Override + public int getRelevance() { + return 3; + } + + @Override + public String toString() { + return function.name(); + } + + public static class TFunctionParam extends Parameter{ + + private TParam param; + private TFunction function; + + public TFunctionParam(TParam attribute, TFunction function) { + super(attribute.getType(), attribute.name()); + this.param = attribute; + this.function = function; + } + + public TFunction getFunction() { + return function; + } + + public TParam getAttributeParam() { + return param; + } + + } + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TFunctionFilter.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TFunctionFilter.java new file mode 100755 index 00000000000..73c67649fbf --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TFunctionFilter.java @@ -0,0 +1,15 @@ +package cc.arduino.packages.autocomplete; + +import br.com.criativasoft.cpluslibparser.metadata.TFunction; + +/** + * Simple collections filter + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public class TFunctionFilter extends TElementFilter{ + + public TFunctionFilter(String text, int mathType) { + super(text, mathType); + } + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TemplateChoicesCompletion.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TemplateChoicesCompletion.java new file mode 100755 index 00000000000..b3065bf0280 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/TemplateChoicesCompletion.java @@ -0,0 +1,36 @@ +package cc.arduino.packages.autocomplete; + +import java.util.List; + +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.CompletionProvider; +import org.fife.ui.autocomplete.ParameterizedCompletion; +import org.fife.ui.autocomplete.TemplateCompletion; + +/** + * Base class for templates that provide assistance to autocomplete parameters + * @see ParameterizedCompletion + * @see ParameterChoicesProvider + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 09/12/2014 + */ +public abstract class TemplateChoicesCompletion extends TemplateCompletion { + + public TemplateChoicesCompletion(CompletionProvider provider, + String inputText, String definitionString, + String template) { + super(provider, inputText, definitionString, template); + } + + public TemplateChoicesCompletion(CompletionProvider provider, + String inputText, String definitionString, + String template, String shortDescription, + String summary) { + super(provider, inputText, definitionString, template, shortDescription,summary); + } + + + public abstract List getChoices(Parameter param); + + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/class.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/class.gif new file mode 100755 index 00000000000..e4c2a836f83 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/class.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/enum.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/enum.gif new file mode 100755 index 00000000000..15535f52f52 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/enum.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/error.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/error.gif new file mode 100755 index 00000000000..0bc60689c6d Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/error.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/field_default_obj.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/field_default_obj.gif new file mode 100755 index 00000000000..6929d3d13f2 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/field_default_obj.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/field_public_obj.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/field_public_obj.gif new file mode 100755 index 00000000000..d4cb4254d92 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/field_public_obj.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/function.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/function.gif new file mode 100755 index 00000000000..7d24707ee82 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/function.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/function_static.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/function_static.gif new file mode 100755 index 00000000000..e02dc63568c Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/function_static.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/int_obj.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/int_obj.gif new file mode 100755 index 00000000000..2ebc46e1d3b Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/int_obj.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/jdoc_tag_obj.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/jdoc_tag_obj.gif new file mode 100755 index 00000000000..c43c5d51c51 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/jdoc_tag_obj.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/static_co.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/static_co.gif new file mode 100755 index 00000000000..6119fb446f4 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/static_co.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/template_obj.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/template_obj.gif new file mode 100755 index 00000000000..fdde5fbb95e Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/template_obj.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable.gif new file mode 100755 index 00000000000..f4a1ea15070 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable_local.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable_local.gif new file mode 100755 index 00000000000..8adce9541f1 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable_local.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable_static.gif b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable_static.gif new file mode 100755 index 00000000000..c63eb8506b0 Binary files /dev/null and b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/icons/variable_static.gif differ diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/template/GenerateVarTemplate.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/template/GenerateVarTemplate.java new file mode 100755 index 00000000000..88fcbae86cd --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/template/GenerateVarTemplate.java @@ -0,0 +1,32 @@ +package cc.arduino.packages.autocomplete.template; + +import org.fife.ui.autocomplete.CompletionProvider; +import org.fife.ui.autocomplete.TemplateCompletion; + +import cc.arduino.packages.autocomplete.SketchCompletionProvider; + +/** + * Used in {@link SketchCompletionProvider#generateNewVariableFor(String, int)} + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + * @date 11/12/2014 + */ +public class GenerateVarTemplate extends TemplateCompletion { + + private String type, name; + + public GenerateVarTemplate(CompletionProvider provider,String type, String name) { + super(provider, name, name, type + " ${" + name + "} = "); + this.type = type; + this.name = name; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + +} diff --git a/arduino-autocomplete/src/cc/arduino/packages/autocomplete/template/IncludeTemplate.java b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/template/IncludeTemplate.java new file mode 100755 index 00000000000..820cb913f50 --- /dev/null +++ b/arduino-autocomplete/src/cc/arduino/packages/autocomplete/template/IncludeTemplate.java @@ -0,0 +1,33 @@ +package cc.arduino.packages.autocomplete.template; + +import java.util.LinkedList; +import java.util.List; + +import org.fife.ui.autocomplete.BasicCompletion; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.CompletionProvider; + +import processing.app.BaseNoGui; +import processing.app.packages.Library; +import processing.app.packages.LibraryList; +import cc.arduino.packages.autocomplete.TemplateChoicesCompletion; + +public class IncludeTemplate extends TemplateChoicesCompletion { + + public IncludeTemplate(CompletionProvider provider) { + super(provider, "#include", "#include <>", "#include <${include}.h>"); + } + + @Override + public List getChoices(Parameter param) { + List completions = new LinkedList(); + + LibraryList libraries = BaseNoGui.getLibraries(); + for (Library library : libraries) { + completions.add(new BasicCompletion(getProvider(), library.getName())); + } + + return completions; + } + +} diff --git a/arduino-core/.classpath b/arduino-core/.classpath index bf61b582622..e9ac3dc66da 100644 --- a/arduino-core/.classpath +++ b/arduino-core/.classpath @@ -9,5 +9,6 @@ + diff --git a/arduino-core/build.xml b/arduino-core/build.xml index db709c3f876..465a79ebad7 100644 --- a/arduino-core/build.xml +++ b/arduino-core/build.xml @@ -5,6 +5,9 @@ + + + diff --git a/arduino-core/src/processing/app/BaseSketch.java b/arduino-core/src/processing/app/BaseSketch.java new file mode 100755 index 00000000000..224e90718c8 --- /dev/null +++ b/arduino-core/src/processing/app/BaseSketch.java @@ -0,0 +1,631 @@ +/* -*- 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 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 static processing.app.I18n._; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import processing.app.debug.RunnerException; +import processing.app.helpers.PreferencesMapException; +import processing.app.packages.Library; +import processing.app.packages.LibraryList; +import processing.app.packages.SketchListener; +import br.com.criativasoft.cpluslibparser.LibraryCache; +import br.com.criativasoft.cpluslibparser.metadata.TLibrary; + + +/** + * Stores information about files in the current sketch + */ +public abstract class BaseSketch { + + public static final String SKETCH_LIB_PREFIX = "private:sketch:"; // for autocomplete metadata + + /** true if any of the files have been modified. */ + protected boolean modified; + + protected int currentIndex; + + protected SketchData data; + + /** Class name for the PApplet, as determined by the preprocessor. */ + protected String appletClassName; + + private Set listeners = new LinkedHashSet(); + + + /** + * Build the list of files. + *

+ * Generally this is only done once, rather than + * each time a change is made, because otherwise it gets to be + * a nightmare to keep track of what files went where, because + * not all the data will be saved to disk. + *

+ * This also gets called when the main sketch file is renamed, + * because the sketch has to be reloaded from a different folder. + *

+ * Another exception is when an external editor is in use, + * in which case the load happens each time "run" is hit. + */ + protected abstract void load() throws IOException; + + /** + * Handler for the New Code menu option. + */ + public abstract void handleNewCode(); + + /** + * Handler for the Rename Code menu option. + */ + public abstract void handleRenameCode(); + + + /** + * This is called upon return from entering a new file name. + * (that is, from either newCode or renameCode after the prompt) + * This code is almost identical for both the newCode and renameCode + * cases, so they're kept merged except for right in the middle + * where they diverge. + */ + protected abstract void nameCode(String newName); + + + /** + * Remove a piece of code from the sketch and from the disk. + */ + public abstract void handleDeleteCode(); + + + /** + * Move to the previous tab. + */ + public void handlePrevCode() { + int prev = currentIndex - 1; + if (prev < 0) prev = data.getCodeCount()-1; + setCurrentCode(prev); + } + + + /** + * Move to the next tab. + */ + public void handleNextCode() { + setCurrentCode((currentIndex + 1) % data.getCodeCount()); + } + + + /** + * Sets the modified value for the code in the frontmost tab. + */ + public abstract void setModified(boolean state); + + + protected abstract void calcModified(); + + + public boolean isModified() { + return modified; + } + + + /** + * Save all code in the current sketch. + */ + public abstract boolean save() throws IOException; + + + protected boolean renameCodeToInoExtension(File pdeFile) { + for (SketchCode c : data.getCodes()) { + if (!c.getFile().equals(pdeFile)) + continue; + + String pdeName = pdeFile.getPath(); + pdeName = pdeName.substring(0, pdeName.length() - 4) + ".ino"; + return c.renameTo(new File(pdeName)); + } + return false; + } + + + /** + * Handles 'Save As' for a sketch. + *

+ * This basically just duplicates the current sketch folder to + * a new location, and then calls 'Save'. (needs to take the current + * state of the open files and save them to the new folder.. + * but not save over the old versions for the old sketch..) + *

+ * Also removes the previously-generated .class and .jar files, + * because they can cause trouble. + */ + protected abstract boolean saveAs() throws IOException; + + + /** + * Prompt the user for a new file to the sketch, then call the + * other addFile() function to actually add it. + */ + public abstract void handleAddFile(); + + + /** + * Add a file to the sketch. + *

+ * .pde or .java files will be added to the sketch folder.
+ * .jar, .class, .dll, .jnilib, and .so files will all + * be added to the "code" folder.
+ * All other files will be added to the "data" folder. + *

+ * If they don't exist already, the "code" or "data" folder + * will be created. + *

+ * @return true if successful. + */ + public abstract boolean addFile(File sourceFile); + + public void importLibrary(Library lib) throws IOException { + importLibrary(lib.getSrcFolder()); + data.addLibrary(lib); + } + + /** + * Add import statements to the current tab for all of packages inside + * the specified jar file. + */ + public abstract void importLibrary(File jarPath) throws IOException; + + + /** + * Change what file is currently being edited. Changes the current tab index. + *

    + *
  1. store the String for the text of the current file. + *
  2. retrieve the String for the text of the new file. + *
  3. change the text that's visible in the text area + *
+ */ + public abstract void setCurrentCode(int which); + + + /** + * Internal helper function to set the current tab based on a name. + * @param findName the file name (not pretty name) to be shown + */ + protected void setCurrentCode(String findName) { + for (SketchCode code : data.getCodes()) { + if (findName.equals(code.getFileName()) || + findName.equals(code.getPrettyName())) { + setCurrentCode(data.indexOfCode(code)); + return; + } + } + } + + + /** + * Preprocess, Compile, and Run the current code. + *

+ * There are three main parts to this process: + *

+   *   (0. if not java, then use another 'engine'.. i.e. python)
+   *
+   *    1. do the p5 language preprocessing
+   *       this creates a working .java file in a specific location
+   *       better yet, just takes a chunk of java code and returns a
+   *       new/better string editor can take care of saving this to a
+   *       file location
+   *
+   *    2. compile the code from that location
+   *       catching errors along the way
+   *       placing it in a ready classpath, or .. ?
+   *
+   *    3. run the code
+   *       needs to communicate location for window
+   *       and maybe setup presentation space as well
+   *       run externally if a code folder exists,
+   *       or if more than one file is in the project
+   *
+   *    X. afterwards, some of these steps need a cleanup function
+   * 
+ */ + //protected String compile() throws RunnerException { + + /** + * When running from the editor, take care of preparations before running + * the build. + */ + public abstract void prepare() throws IOException; + + + + /** + * Map an error from a set of processed .java files back to its location + * in the actual sketch. + * @param message The error message. + * @param filename The .java file where the exception was found. + * @param line Line number of the .java file for the exception (1-indexed) + * @return A RunnerException to be sent to the editor, or null if it wasn't + * possible to place the exception to the sketch code. + */ +// public RunnerException placeExceptionAlt(String message, +// String filename, int line) { +// String appletJavaFile = appletClassName + ".java"; +// SketchCode errorCode = null; +// if (filename.equals(appletJavaFile)) { +// for (SketchCode code : getCode()) { +// if (code.isExtension("ino")) { +// if (line >= code.getPreprocOffset()) { +// errorCode = code; +// } +// } +// } +// } else { +// for (SketchCode code : getCode()) { +// if (code.isExtension("java")) { +// if (filename.equals(code.getFileName())) { +// errorCode = code; +// } +// } +// } +// } +// int codeIndex = getCodeIndex(errorCode); +// +// if (codeIndex != -1) { +// //System.out.println("got line num " + lineNumber); +// // in case this was a tab that got embedded into the main .java +// line -= getCode(codeIndex).getPreprocOffset(); +// +// // lineNumber is 1-indexed, but editor wants zero-indexed +// line--; +// +// // getMessage() will be what's shown in the editor +// RunnerException exception = +// new RunnerException(message, codeIndex, line, -1); +// exception.hideStackTrace(); +// return exception; +// } +// return null; +// } + + + /** + * Run the build inside the temporary build folder. + * @return null if compilation failed, main class name if not + * @throws RunnerException + */ + public abstract String build(boolean verbose) throws RunnerException, PreferencesMapException; + + /** + * Preprocess and compile all the code for this sketch. + * + * In an advanced program, the returned class name could be different, + * which is why the className is set based on the return value. + * A compilation error will burp up a RunnerException. + * + * @return null if compilation failed, main class name if not + */ + public abstract String build(String buildPath, boolean verbose) throws RunnerException, PreferencesMapException; + + protected abstract boolean exportApplet(boolean usingProgrammer) throws Exception; + + + /** + * Handle export to applet. + */ + public abstract boolean exportApplet(String appletPath, boolean usingProgrammer) + throws Exception; + + + protected abstract boolean upload(String buildPath, String suggestedClassName, boolean usingProgrammer) throws Exception; + + + public boolean exportApplicationPrompt() throws IOException, RunnerException { + return false; + } + + + /** + * Export to application via GUI. + */ + protected boolean exportApplication() throws IOException, RunnerException { + return false; + } + + + /** + * Export to application without GUI. + */ + public boolean exportApplication(String destPath, + int exportPlatform) throws IOException, RunnerException { + return false; + } + + + /** + * Make sure the sketch hasn't been moved or deleted by some + * nefarious user. If they did, try to re-create it and save. + * Only checks to see if the main folder is still around, + * but not its contents. + */ + protected void ensureExistence() { + if (data.getFolder().exists()) return; + + BaseNoGui.showWarning(_("Sketch Disappeared"), + _("The sketch folder has disappeared.\n " + + "Will attempt to re-save in the same location,\n" + + "but anything besides the code will be lost."), null); + try { + data.getFolder().mkdirs(); + modified = true; + + for (SketchCode code : data.getCodes()) { + code.save(); // this will force a save + } + calcModified(); + + } catch (Exception e) { + BaseNoGui.showWarning(_("Could not re-save sketch"), + _("Could not properly re-save the sketch. " + + "You may be in trouble at this point,\n" + + "and it might be time to copy and paste " + + "your code to another text editor."), e); + } + } + + + /** + * Returns true if this is a read-only sketch. Used for the + * examples directory, or when sketches are loaded from read-only + * volumes or folders without appropriate permissions. + */ + public boolean isReadOnly() { + String apath = data.getFolder().getAbsolutePath(); + for (File folder : BaseNoGui.getLibrariesPath()) { + if (apath.startsWith(folder.getAbsolutePath())) + return true; + } + if (apath.startsWith(BaseNoGui.getExamplesPath()) || + apath.startsWith(BaseNoGui.getSketchbookLibrariesFolder().getAbsolutePath())) { + return true; + } + + // canWrite() doesn't work on directories + // } else if (!folder.canWrite()) { + + // check to see if each modified code file can be written to + for (SketchCode code : data.getCodes()) { + if (code.isModified() && code.fileReadOnly() && code.fileExists()) { + // System.err.println("found a read-only file " + code[i].file); + return true; + } + } + return false; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + // Breaking out extension types in order to clean up the code, and make it + // easier for other environments (like Arduino) to incorporate changes. + + /** + * True if the specified code has the default file extension. + */ + public boolean hasDefaultExtension(SketchCode code) { + return code.isExtension(getDefaultExtension()); + } + + + /** + * True if the specified extension is the default file extension. + */ + public boolean isDefaultExtension(String what) { + return what.equals(getDefaultExtension()); + } + + + /** + * Check this extension (no dots, please) against the list of valid + * extensions. + */ + public boolean validExtension(String what) { + return data.getExtensions().contains(what); + } + + + /** + * Returns the default extension for this editor setup. + */ + public String getDefaultExtension() { + return data.getDefaultExtension(); + } + + static private List hiddenExtensions = Arrays.asList("ino", "pde"); + + public List getHiddenExtensions() { + return hiddenExtensions; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + // Additional accessors added in 0136 because of package work. + // These will also be helpful for tool developers. + + + /** + * Returns the name of this sketch. (The pretty name of the main tab.) + */ + public String getName() { + return data.getName(); + } + + + /** + * Returns path to the main .pde file for this sketch. + */ + public String getMainFilePath() { + return data.getMainFilePath(); + } + + + /** + * Returns the sketch folder. + */ + public File getFolder() { + return data.getFolder(); + } + + + /** + * Create the data folder if it does not exist already. As a convenience, + * it also returns the data folder, since it's likely about to be used. + */ + public File prepareDataFolder() { + if (!data.getDataFolder().exists()) { + data.getDataFolder().mkdirs(); + } + return data.getDataFolder(); + } + + + /** + * Create the code folder if it does not exist already. As a convenience, + * it also returns the code folder, since it's likely about to be used. + */ + public File prepareCodeFolder() { + if (!data.getCodeFolder().exists()) { + data.getCodeFolder().mkdirs(); + } + return data.getCodeFolder(); + } + + + public SketchCode[] getCodes() { + return data.getCodes(); + } + + + public int getCodeCount() { + return data.getCodeCount(); + } + + + public SketchCode getCode(int index) { + return data.getCode(index); + } + + + public int getCodeIndex(SketchCode who) { + return data.indexOfCode(who); + } + + + public abstract SketchCode getCurrentCode(); + + + public abstract void setUntitled(boolean u); + + + public abstract boolean isUntitled(); + + public abstract boolean isExternalMode(); + + public String getAppletClassName2() { + return appletClassName; + } + + + // ................................................................. + + + public boolean addListener(SketchListener listener){ + return listeners.add(listener); + } + + public boolean removeListener(SketchListener listener){ + return listeners.add(listener); + } + + public void notifyListeners(SketchListener.Event event, BaseSketch sketch, SketchCode code){ + + for (SketchListener listener : listeners) { + + if(event == SketchListener.Event.LOAD){ + listener.onSketchLoad(sketch); + } + if(event == SketchListener.Event.INSERTED){ + listener.onSketchInserted(sketch, code); + } + if(event == SketchListener.Event.SAVED){ + listener.onSketchSaved(sketch, code); + } + + } + + } + + /** + * Convert to sanitized name and alert the user + * if changes were made. + */ + static public String checkName(String origName) { + String newName = BaseNoGui.sanitizeName(origName); + + if (!newName.equals(origName)) { + String msg = + _("The sketch name had to be modified. Sketch names can only consist\n" + + "of ASCII characters and numbers (but cannot start with a number).\n" + + "They should also be less than 64 characters long."); + System.out.println(msg); + } + return newName; + } + + public SketchData getData() { + return data; + } + + public void setSketchMetadata( TLibrary sketchMetadata ) { + data.setSketchMetadata(sketchMetadata); + } + + public TLibrary getSketchMetadata() { + return data.getSketchMetadata(); + } + + public LibraryCache getLibraryCacheContext() { + return data.getLibraryCacheContext(); + } + + public LibraryList getImportedLibraries() { + return data.getImportedLibraries(); + } + +} diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index a8f2c16f1cd..022ed487bba 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -49,12 +49,16 @@ public class SketchCode { private int preprocOffset; private Object metadata; + + private BaseSketch sketch; - public SketchCode(File file) { + public SketchCode(BaseSketch sketch, File file) { + this.sketch = sketch; init(file, null); } - public SketchCode(File file, Object metadata) { + public SketchCode(BaseSketch sketch, File file, Object metadata) { + this.sketch = sketch; init(file, metadata); } @@ -164,6 +168,7 @@ public int getLineCount() { public void setModified(boolean modified) { this.modified = modified; + sketch.setModified(modified); } @@ -243,4 +248,9 @@ public Object getMetadata() { public void setMetadata(Object metadata) { this.metadata = metadata; } + + public BaseSketch getSketch() { + return sketch; + } + } diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index 36f5a8fceab..93402d841d9 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -4,11 +4,13 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; + +import processing.app.packages.Library; +import processing.app.packages.LibraryList; +import processing.app.preproc.PdePreprocessor; +import br.com.criativasoft.cpluslibparser.LibraryCache; +import br.com.criativasoft.cpluslibparser.metadata.TLibrary; public class SketchData { @@ -29,8 +31,22 @@ public class SketchData { * extension) */ private String name; + + private BaseSketch sketch; private List codes = new ArrayList(); + + /** + * List of library folders. + */ + private LibraryList importedLibraries = new LibraryList(); + + /** Sketch source metadata(classes, variables, functions), this is set in {@link SketchCompletionProvider#onLoadLibrary(TLibrary)} */ + private TLibrary sketchMetadata; + + /** Sketch and 'Libraries currently used' Metadata Cache - This is used by autocomplete */ + private LibraryCache libraryCacheContext; + private static final Comparator CODE_DOCS_COMPARATOR = new Comparator() { @Override @@ -38,7 +54,12 @@ public int compare(SketchCode x, SketchCode y) { return x.getFileName().compareTo(y.getFileName()); } }; - + + SketchData(BaseSketch sketch , File file) { + this(file); + this.sketch = sketch; + } + SketchData(File file) { primaryFile = file; @@ -47,11 +68,16 @@ public int compare(SketchCode x, SketchCode y) { String mainFilename = primaryFile.getName(); int suffixLength = getDefaultExtension().length() + 1; name = mainFilename.substring(0, mainFilename.length() - suffixLength); + + libraryCacheContext = new LibraryCache(); + libraryCacheContext.setName(name); folder = new File(file.getParent()); //System.out.println("sketch dir is " + folder); } + + static public File checkSketchFile(File file) { // check to make sure that this .pde file is // in a folder of the same name @@ -123,7 +149,7 @@ protected void load() throws IOException { // Don't allow people to use files with invalid names, since on load, // it would be otherwise possible to sneak in nasty filenames. [0116] if (BaseNoGui.isSanitaryName(base)) { - addCode(new SketchCode(new File(folder, filename))); + addCode(new SketchCode(sketch, new File(folder, filename))); } else { System.err.println(I18n.format("File name {0} is invalid: ignored", filename)); } @@ -143,6 +169,19 @@ protected void load() throws IOException { break; } } + + // Find used libraries + for (SketchCode code : getCodes()) { + List includes = PdePreprocessor.findIncludes(code.getProgram()); + if(includes != null){ + for (String include : includes) { + Library lib = BaseNoGui.importToLibraryTable.get(include); + if (lib != null && !importedLibraries.contains(lib)) { + importedLibraries.add(lib); + } + } + } + } // sort the entries at the top sortCode(); @@ -263,4 +302,26 @@ public File getDataFolder() { public File getCodeFolder() { return codeFolder; } + + public void setSketchMetadata(TLibrary sketchMetadata) { + this.sketchMetadata = sketchMetadata; + } + + public TLibrary getSketchMetadata() { + return sketchMetadata; + } + + public LibraryCache getLibraryCacheContext() { + return libraryCacheContext; + } + + public LibraryList getImportedLibraries() { + return importedLibraries; + } + + public void addLibrary(Library lib) { + if(lib != null){ + importedLibraries.add(lib); + } + } } diff --git a/arduino-core/src/processing/app/SketchDocumentProvider.java b/arduino-core/src/processing/app/SketchDocumentProvider.java new file mode 100755 index 00000000000..b5eecf4a22a --- /dev/null +++ b/arduino-core/src/processing/app/SketchDocumentProvider.java @@ -0,0 +1,9 @@ +package processing.app; + +import javax.swing.text.Document; + +public interface SketchDocumentProvider { + + Document getDocument(); + +} diff --git a/arduino-core/src/processing/app/debug/Compiler.java b/arduino-core/src/processing/app/debug/Compiler.java index 652d7c77fcf..b7e8bc09e25 100644 --- a/arduino-core/src/processing/app/debug/Compiler.java +++ b/arduino-core/src/processing/app/debug/Compiler.java @@ -43,7 +43,6 @@ import cc.arduino.packages.BoardPort; import cc.arduino.packages.Uploader; import cc.arduino.packages.UploaderFactory; - import processing.app.BaseNoGui; import processing.app.I18n; import processing.app.PreferencesData; @@ -711,8 +710,13 @@ public void message(String s) { // look for error line, which contains file name, line number, // and at least the first line of the error message - String errorFormat = "([\\w\\d_]+.\\w+):(\\d+):\\s*error:\\s*(.*)\\s*"; - String[] pieces = PApplet.match(s, errorFormat); + String[] pieces = null; + + if(OSUtils.isLinux()){ + pieces = PApplet.match(s, "([\\w\\d_]+.\\w+):(\\d+):\\d+:\\s*error:\\s*(.*)\\s*"); + }else{ + pieces = PApplet.match(s, "([\\w\\d_]+.\\w+):(\\d+):\\s*error:\\s*(.*)\\s*"); + } // if (pieces != null && exception == null) { // exception = sketch.placeException(pieces[3], pieces[1], PApplet.parseInt(pieces[2]) - 1); diff --git a/arduino-core/src/processing/app/helpers/IPredicate.java b/arduino-core/src/processing/app/helpers/IPredicate.java new file mode 100755 index 00000000000..3af53471d6e --- /dev/null +++ b/arduino-core/src/processing/app/helpers/IPredicate.java @@ -0,0 +1,7 @@ +package processing.app.helpers; + +/** + * Interface to perform the filtering elements + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public interface IPredicate { boolean apply(T element); } \ No newline at end of file diff --git a/arduino-core/src/processing/app/helpers/Predicate.java b/arduino-core/src/processing/app/helpers/Predicate.java new file mode 100755 index 00000000000..b12c58d9ad8 --- /dev/null +++ b/arduino-core/src/processing/app/helpers/Predicate.java @@ -0,0 +1,47 @@ +package processing.app.helpers; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Utility to filter elements based on predicates + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public class Predicate { + + public static Collection filter(Collection list, IPredicate predicate) { + Collection result = new ArrayList(); + return filter(list, predicate, result); + } + + public static Collection filter(Collection list, IPredicate predicate, Collection target) { + for (T element : list) { + if (predicate.apply(element)) { + target.add(element); + } + } + return target; +} + + public static T select(Collection target, IPredicate predicate) { + T result = null; + for (T element : target) { + if (!predicate.apply(element)) + continue; + result = element; + break; + } + return result; + } + + public static T select(Collection target, IPredicate predicate, T defaultValue) { + T result = defaultValue; + for (T element : target) { + if (!predicate.apply(element)) + continue; + result = element; + break; + } + return result; + } +} diff --git a/arduino-core/src/processing/app/helpers/StringUtils.java b/arduino-core/src/processing/app/helpers/StringUtils.java index ea623d90111..2db9c75976f 100644 --- a/arduino-core/src/processing/app/helpers/StringUtils.java +++ b/arduino-core/src/processing/app/helpers/StringUtils.java @@ -26,4 +26,30 @@ public static boolean wildcardMatch(String input, String pattern) { String regex = pattern.replace("?", ".?").replace("*", ".*?"); return input.matches(regex); } + + public static String findLastWord(String input){ + + int pos = -1; + for (int i = 0; i < input.length(); i++) { + if(Character.isUpperCase(input.charAt(i))){ + pos = i; + } + } + + if(pos > -1){ + return input.substring(pos); + }else{ + return null; + } + + } + + /** + * Cake the first character of a String lower case + */ + public static String uncapitalize(String input){ + String firstLetter = input.substring(0,1).toLowerCase(); + String restLetters = input.substring(1); + return firstLetter + restLetters; + } } diff --git a/arduino-core/src/processing/app/helpers/filefilters/OnlyFilesWithExtension.java b/arduino-core/src/processing/app/helpers/filefilters/OnlyFilesWithExtension.java index d5759994209..34dfaae5d4e 100644 --- a/arduino-core/src/processing/app/helpers/filefilters/OnlyFilesWithExtension.java +++ b/arduino-core/src/processing/app/helpers/filefilters/OnlyFilesWithExtension.java @@ -23,16 +23,33 @@ import java.io.File; import java.io.FilenameFilter; +import java.util.Collection; public class OnlyFilesWithExtension implements FilenameFilter { String extensions[]; + Collection ignoredFiles; public OnlyFilesWithExtension(String... ext) { extensions = ext; } + + + public OnlyFilesWithExtension(Collection ignoredFiles, String... ext) { + super(); + this.extensions = ext; + this.ignoredFiles = ignoredFiles; + } + public boolean accept(File dir, String name) { + + if(ignoredFiles != null){ + if(ignoredFiles.contains(name)){ + return false; + } + } + for (String ext : extensions) if (name.endsWith(ext)) return true; diff --git a/arduino-core/src/processing/app/packages/LibraryList.java b/arduino-core/src/processing/app/packages/LibraryList.java index 343ff4bde6a..5fbcc7ea795 100644 --- a/arduino-core/src/processing/app/packages/LibraryList.java +++ b/arduino-core/src/processing/app/packages/LibraryList.java @@ -4,11 +4,15 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; import processing.app.helpers.FileUtils; @SuppressWarnings("serial") public class LibraryList extends ArrayList { + + private Set listeners = new LinkedHashSet(); public LibraryList(LibraryList libs) { super(libs); @@ -67,4 +71,41 @@ public LibraryList filterLibrariesInSubfolder(File subFolder) { res.add(lib); return res; } + + @Override + public boolean add(Library l) { + + if(l == null || l.getName() == null || l.getName().length() == 0) return false; + + boolean add = super.add(l); + if(add){ // notify.. + if(listeners != null) for (LibraryListener listener : listeners) { listener.onInsertLibrary(l);} + } + + return add; + } + + @Override + public boolean remove(Object l) { + boolean remove = super.remove(l); + if(remove){ // notify.. + if(listeners != null) for (LibraryListener listener : listeners) { listener.onRemoveLibrary((Library)l);} + } + return remove; + } + + @Override + public void clear() { + if(listeners != null) for (LibraryListener listener : listeners) { listener.onClearLibraryList();} + super.clear(); + } + + public boolean addListener(LibraryListener e) { + return listeners.add(e); + } + + public boolean removeListener(LibraryListener e) { + return listeners.remove(e); + } + } diff --git a/arduino-core/src/processing/app/packages/LibraryListener.java b/arduino-core/src/processing/app/packages/LibraryListener.java new file mode 100755 index 00000000000..c176176ce52 --- /dev/null +++ b/arduino-core/src/processing/app/packages/LibraryListener.java @@ -0,0 +1,15 @@ +package processing.app.packages; + +/** + * Interface to monitor sketck library changes + * @author Ricardo JL Rufino (ricardo@criativasoft.com.br) + */ +public interface LibraryListener { + + void onInsertLibrary(Library library); + + void onRemoveLibrary(Library library); + + void onClearLibraryList(); + +} diff --git a/arduino-core/src/processing/app/packages/SketchListener.java b/arduino-core/src/processing/app/packages/SketchListener.java new file mode 100755 index 00000000000..eb5a650ecff --- /dev/null +++ b/arduino-core/src/processing/app/packages/SketchListener.java @@ -0,0 +1,18 @@ +package processing.app.packages; + +import processing.app.BaseSketch; +import processing.app.SketchCode; + +public interface SketchListener { + + public enum Event{ + LOAD, INSERTED, SAVED + } + + void onSketchLoad(BaseSketch sketch); + + void onSketchInserted(BaseSketch sketch, SketchCode code); + + void onSketchSaved(BaseSketch sketch, SketchCode code); + +} diff --git a/arduino-core/src/processing/app/preproc/PdePreprocessor.java b/arduino-core/src/processing/app/preproc/PdePreprocessor.java index 576f7468b9d..dc30922a92e 100644 --- a/arduino-core/src/processing/app/preproc/PdePreprocessor.java +++ b/arduino-core/src/processing/app/preproc/PdePreprocessor.java @@ -42,6 +42,9 @@ Processing version Copyright (c) 2004-05 Ben Fry and Casey Reas * Class that orchestrates preprocessing p5 syntax into straight Java. */ public class PdePreprocessor { + + private static final String IMPORT_REGEX = "^\\s*#include\\s*[<\"](\\S+)[\">]"; + // stores number of built user-defined function prototypes public int prototypeCount = 0; @@ -94,10 +97,9 @@ public int writePrefix(String program) } //String importRegexp = "(?:^|\\s|;)(import\\s+)(\\S+)(\\s*;)"; - String importRegexp = "^\\s*#include\\s*[<\"](\\S+)[\">]"; programImports = new ArrayList(); - String[][] pieces = PApplet.matchAll(program, importRegexp); + String[][] pieces = PApplet.matchAll(program, IMPORT_REGEX); if (pieces != null) for (int i = 0; i < pieces.length; i++) @@ -121,6 +123,19 @@ public int writePrefix(String program) return headerCount + prototypeCount; } + public static List findIncludes(String code){ + + String[][] pieces = PApplet.matchAll(code, IMPORT_REGEX); + + ArrayList programImports = new ArrayList(); + + if (pieces != null) + for (int i = 0; i < pieces.length; i++) + programImports.add(pieces[i][1]); // the package name + + return programImports; + } + static String substituteUnicode(String program) { // check for non-ascii chars (these will be/must be in unicode format) diff --git a/build/build.xml b/build/build.xml index eb8b7d02a59..af5cc3375ff 100644 --- a/build/build.xml +++ b/build/build.xml @@ -40,6 +40,7 @@ + @@ -52,6 +53,10 @@ + + + + @@ -85,11 +90,13 @@ + + @@ -471,7 +478,10 @@ + + + diff --git a/build/linux/dist/arduino b/build/linux/dist/arduino index 63811c20e80..420b1529590 100755 --- a/build/linux/dist/arduino +++ b/build/linux/dist/arduino @@ -20,5 +20,5 @@ export LD_LIBRARY_PATH export PATH="${APPDIR}/java/bin:${PATH}" -java -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel processing.app.Base --curdir $CURDIR "$@" +java -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel -splash:${APPDIR}/lib/about.jpg ArduinoIDE --curdir $CURDIR "$@" diff --git a/build/linux/dist/arduino.desktop b/build/linux/dist/arduino.desktop index dac3ebd9d68..8b271e49393 100644 --- a/build/linux/dist/arduino.desktop +++ b/build/linux/dist/arduino.desktop @@ -3,8 +3,8 @@ Type=Application Name=Arduino IDE GenericName=Integrated Development Environment Comment=An IDE for Arduino-compatible electronics prototyping platforms -Exec=arduino -Icon=arduino +Exec=FULL_PATH/arduino +Icon=FULL_PATH/lib/arduino.png Terminal=false Categories=Development;IDE;Electronics; MimeType=text/x-arduino diff --git a/build/linux/dist/install.sh b/build/linux/dist/install.sh new file mode 100644 index 00000000000..d34c8aac85c --- /dev/null +++ b/build/linux/dist/install.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +CURDIR=`pwd` +APPDIR="$(dirname -- "$(readlink -f -- "${0}")" )" + +cd "$APPDIR" + +# Set arduino.desktop absolute path workaround +mv arduino.desktop arduino.desktop-bak +sed -e "s,FULL_PATH,$PWD,g" arduino.desktop-bak > arduino.desktop +rm arduino.desktop-bak + +cp arduino.desktop ~/.local/share/applications/arduino.desktop +cp arduino.desktop ~/Desktop/arduino.desktop + +echo "Instaled Arduino IDE icons on menu and desktop !" + diff --git a/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino b/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino index 95c2b6eb0a8..c8d23d53fd0 100644 --- a/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino +++ b/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino @@ -1,3 +1,4 @@ + void setup() { // put your setup code here, to run once: diff --git a/build/shared/lib/arduino.png b/build/shared/lib/arduino.png new file mode 100644 index 00000000000..28fa03ab7f7 Binary files /dev/null and b/build/shared/lib/arduino.png differ diff --git a/build/shared/lib/theme/syntax/dark.xml b/build/shared/lib/theme/syntax/dark.xml new file mode 100644 index 00000000000..9b51fbc000c --- /dev/null +++ b/build/shared/lib/theme/syntax/dark.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +