From af430b8d0d0df50eb5de3a5357e55c4ebe6155bf Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 3 Dec 2015 18:08:12 +0100 Subject: [PATCH 01/64] Let EditorToolbar use the global KeyboardFocusManager For some toolbar buttons, when it is clicked while shift is pressed, its function changes. When handling the click event, this information is directly taken from KeyEvent.isShiftDown(). However, to also show the proper tooltip *before* clicking, EditorToolbar listened to key events on the main text area, to know when shift is (not) pressed. This approach means that pressing shift while the text area is not focused will not change the tooltip, and creates some unwanted coupling between the toolbar and the text area. This commit changes this approach to instead use the global KeyboardFocusManager. Any key presses pass through there before being dispatched to the currently focused component, so this makes sure that any shift presses are caught, as well as making EditorToolbar a bit more self-contained. --- app/src/processing/app/Editor.java | 3 --- app/src/processing/app/EditorToolbar.java | 26 ++++++----------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 2f7b5bfdf79..870ea6a8e8a 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -337,9 +337,6 @@ public void windowDeactivated(WindowEvent e) { // listener = new EditorListener(this, textarea); pane.add(box); - // get shift down/up events so we can show the alt version of toolbar buttons - textarea.addKeyListener(toolbar); - pane.setTransferHandler(new FileDropHandler()); // System.out.println("t1"); diff --git a/app/src/processing/app/EditorToolbar.java b/app/src/processing/app/EditorToolbar.java index e433d372c49..d47411af297 100644 --- a/app/src/processing/app/EditorToolbar.java +++ b/app/src/processing/app/EditorToolbar.java @@ -27,7 +27,6 @@ import javax.swing.event.MouseInputListener; import java.awt.*; import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import static processing.app.I18n.tr; @@ -36,7 +35,7 @@ /** * run/stop/etc buttons for the ide */ -public class EditorToolbar extends JComponent implements MouseInputListener, KeyListener { +public class EditorToolbar extends JComponent implements MouseInputListener, KeyEventDispatcher { /** * Rollover titles for each button. @@ -136,6 +135,7 @@ public EditorToolbar(Editor editor, JMenu menu) { addMouseListener(this); addMouseMotionListener(this); + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); } private void loadButtons() { @@ -440,24 +440,12 @@ public Dimension getMaximumSize() { return new Dimension(3000, BUTTON_HEIGHT); } - - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_SHIFT) { - shiftPressed = true; - repaint(); - } - } - - - public void keyReleased(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_SHIFT) { - shiftPressed = false; + public boolean dispatchKeyEvent(final KeyEvent e) { + if (shiftPressed != e.isShiftDown()) { + shiftPressed = !shiftPressed; repaint(); } + // Return false to continue processing this keyEvent + return false; } - - - public void keyTyped(KeyEvent e) { - } - } From c5e44c0af290378a4dd2596250b89a871ac156be Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 3 Dec 2015 23:36:55 +0100 Subject: [PATCH 02/64] Use a separate RSyntaxTextArea for each editor tab RSyntaxTextArea appears to support using a single instance and replacing the underlying text and document when switching between tabs, but in practice this support is not complete and even though the RSyntaxTextArea developers did some work to improve the situation, they recommend to just use a seperate instance for each tab. This commit implements exactly that. A new class EditorTab is introduce to wrap the RSyntaxTextArea and containing scroll pane, and to encapsulate the code related to handling the text area itself. Doing so removes some quirks and prepares for some later additions. In particular, error highlights are now no longer shared between all tabs, which was previously the case. This commit mostly moves code from Editor into EditorTab, and updates the callers to use getCurrentTab() and call methods on the result instead of calling them on Editor. Some code is added to take care of creating multiple EditorTab objects and switching between them. Some small changes have been made to make the flow of opening files work, though these are mostly a bit hacky. While moving code, changes to the rest of the code were kept minimal, retaining existing interfaces as much as possible. This sometimes result in less than ideal code, which should be cleaned up in subsequent commits. The SketchCodeDocument class has been pretty much emptied out, since it was mostly used to store things for tabs in the background, which are now just stored in each RSyntaxTextArea separately. The last remaining bits of this class can probably be moved or implemented differently later, so it can be removed. The entire flow of working with sketches and files needs to be cleaned up next, so no thorough attempt at testing this commit was done. It is likely that there are plenty of corner cases and race conditions, which will be fixed once the reset of the code is cleaned up. Fixes #3441 --- .../cc/arduino/packages/formatter/AStyle.java | 6 +- app/src/cc/arduino/view/GoToLineNumber.java | 2 +- .../arduino/view/findreplace/FindReplace.java | 24 +- app/src/processing/app/Base.java | 6 +- app/src/processing/app/Editor.java | 563 +++--------------- app/src/processing/app/EditorTab.java | 479 +++++++++++++++ app/src/processing/app/EditorToolbar.java | 2 +- app/src/processing/app/Sketch.java | 60 +- .../processing/app/SketchCodeDocument.java | 57 +- .../processing/app/syntax/SketchTextArea.java | 25 +- .../processing/app/tools/DiscourseFormat.java | 5 +- app/src/processing/app/tools/FixEncoding.java | 4 +- ...lockCommentGeneratesOneUndoActionTest.java | 2 +- 13 files changed, 634 insertions(+), 601 deletions(-) create mode 100644 app/src/processing/app/EditorTab.java diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index 7f7c244d6f6..fc74ea06a1b 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -78,7 +78,7 @@ public void init(Editor editor) { @Override public void run() { - String originalText = editor.getText(); + String originalText = editor.getCurrentTab().getText(); String formattedText = aStyleInterface.AStyleMain(originalText, formatterConfiguration); if (formattedText.equals(originalText)) { @@ -86,14 +86,14 @@ public void run() { return; } - SketchTextArea textArea = editor.getTextArea(); + SketchTextArea textArea = editor.getCurrentTab().getTextArea(); int line = getLineOfOffset(textArea); int lineOffset = getLineOffset(textArea, line); textArea.getUndoManager().beginInternalAtomicEdit(); editor.removeAllLineHighlights(); - editor.setText(formattedText); + editor.getCurrentTab().setText(formattedText); editor.getSketch().setModified(true); textArea.getUndoManager().endInternalAtomicEdit(); diff --git a/app/src/cc/arduino/view/GoToLineNumber.java b/app/src/cc/arduino/view/GoToLineNumber.java index 3a3bc6fcaa9..475b0bbe502 100644 --- a/app/src/cc/arduino/view/GoToLineNumber.java +++ b/app/src/cc/arduino/view/GoToLineNumber.java @@ -127,7 +127,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { private void okActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okActionPerformed try { int line = Integer.parseInt(lineNumber.getText()); - editor.goToLine(line); + editor.getCurrentTab().goToLine(line); cancelActionPerformed(evt); } catch (Exception e) { // ignore diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java index cffb6099c71..112726d1628 100644 --- a/app/src/cc/arduino/view/findreplace/FindReplace.java +++ b/app/src/cc/arduino/view/findreplace/FindReplace.java @@ -292,7 +292,7 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or return false; } - String text = editor.getText(); + String text = editor.getCurrentTab().getText(); if (ignoreCaseBox.isSelected()) { search = search.toLowerCase(); @@ -302,7 +302,7 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or int nextIndex; if (!backwards) { // int selectionStart = editor.textarea.getSelectionStart(); - int selectionEnd = editor.getSelectionStop(); + int selectionEnd = editor.getCurrentTab().getSelectionStop(); nextIndex = text.indexOf(search, selectionEnd); if (wrap && nextIndex == -1) { @@ -311,7 +311,7 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or } } else { // int selectionStart = editor.textarea.getSelectionStart(); - int selectionStart = editor.getSelectionStart() - 1; + int selectionStart = editor.getCurrentTab().getSelectionStart() - 1; if (selectionStart >= 0) { nextIndex = text.lastIndexOf(search, selectionStart); @@ -346,12 +346,12 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or if (backwards) { sketch.handlePrevCode(); this.setVisible(true); - int l = editor.getText().length() - 1; - editor.setSelection(l, l); + int l = editor.getCurrentTab().getText().length() - 1; + editor.getCurrentTab().setSelection(l, l); } else { sketch.handleNextCode(); this.setVisible(true); - editor.setSelection(0, 0); + editor.getCurrentTab().setSelection(0, 0); } return find(wrap, backwards, true, originTab); @@ -365,7 +365,7 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or } if (nextIndex != -1) { - editor.setSelection(nextIndex, nextIndex + search.length()); + editor.getCurrentTab().setSelection(nextIndex, nextIndex + search.length()); return true; } @@ -381,17 +381,17 @@ private void replace() { return; } - int newpos = editor.getSelectionStart() - findField.getText().length(); + int newpos = editor.getCurrentTab().getSelectionStart() - findField.getText().length(); if (newpos < 0) { newpos = 0; } - editor.setSelection(newpos, newpos); + editor.getCurrentTab().setSelection(newpos, newpos); boolean foundAtLeastOne = false; if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; - editor.setSelectedText(replaceField.getText()); + editor.getCurrentTab().setSelectedText(replaceField.getText()); editor.getSketch().setModified(true); // TODO is this necessary? } @@ -423,13 +423,13 @@ private void replaceAll() { editor.getSketch().setCurrentCode(0); // select the first tab } - editor.setSelection(0, 0); // move to the beginning + editor.getCurrentTab().setSelection(0, 0); // move to the beginning boolean foundAtLeastOne = false; while (true) { if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; - editor.setSelectedText(replaceField.getText()); + editor.getCurrentTab().setSelectedText(replaceField.getText()); editor.getSketch().setModified(true); // TODO is this necessary? } else { break; diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index f7af1aa3359..2f9730656c9 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -610,10 +610,10 @@ protected void handleActivated(Editor whichEditor) { activeEditor.rebuildRecentSketchesMenu(); if (PreferencesData.getBoolean("editor.external")) { try { - int previousCaretPosition = activeEditor.getTextArea().getCaretPosition(); + int previousCaretPosition = activeEditor.getCurrentTab().getTextArea().getCaretPosition(); activeEditor.getSketch().load(true); - if (previousCaretPosition < activeEditor.getText().length()) { - activeEditor.getTextArea().setCaretPosition(previousCaretPosition); + if (previousCaretPosition < activeEditor.getCurrentTab().getText().length()) { + activeEditor.getCurrentTab().getTextArea().setCaretPosition(previousCaretPosition); } } catch (IOException e) { // noop diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 870ea6a8e8a..d7667636ff7 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -31,30 +31,19 @@ import cc.arduino.view.findreplace.FindReplace; import com.jcraft.jsch.JSchException; import jssc.SerialPortException; -import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit; -import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; -import org.fife.ui.rtextarea.Gutter; -import org.fife.ui.rtextarea.RTextScrollPane; import processing.app.debug.RunnerException; import processing.app.forms.PasswordAuthorizationDialog; import processing.app.helpers.Keys; import processing.app.helpers.OSUtils; import processing.app.helpers.PreferencesMapException; import processing.app.legacy.PApplet; -import processing.app.syntax.ArduinoTokenMakerFactory; import processing.app.syntax.PdeKeywords; -import processing.app.syntax.SketchTextArea; -import processing.app.syntax.SketchTextAreaEditorKit; -import processing.app.tools.DiscourseFormat; import processing.app.tools.MenuScroller; import processing.app.tools.Tool; import javax.swing.*; -import javax.swing.border.MatteBorder; import javax.swing.event.*; import javax.swing.text.BadLocationException; -import javax.swing.text.PlainDocument; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; @@ -88,9 +77,12 @@ public class Editor extends JFrame implements RunnerListener { public static final int MAX_TIME_AWAITING_FOR_RESUMING_SERIAL_MONITOR = 10000; - private final Platform platform; + final Platform platform; private JMenu recentSketchesMenu; private JMenu programmersMenu; + private final Box upper; + private ArrayList tabs = new ArrayList<>(); + private int currentTabIndex = -1; private static class ShouldSaveIfModified implements Predicate { @@ -165,12 +157,12 @@ public boolean test(Sketch sketch) { // currently opened program Sketch sketch; - private EditorLineStatus lineStatus; + EditorLineStatus lineStatus; //JEditorPane editorPane; - private SketchTextArea textarea; - private RTextScrollPane scrollPane; + /** Contains all EditorTabs, of which only one will be visible */ + private JPanel codePanel; //Runner runtime; @@ -258,7 +250,7 @@ public void windowDeactivated(WindowEvent e) { contentPain.add(pane, BorderLayout.CENTER); Box box = Box.createVerticalBox(); - Box upper = Box.createVerticalBox(); + upper = Box.createVerticalBox(); if (toolbarMenu == null) { toolbarMenu = new JMenu(); @@ -270,9 +262,6 @@ public void windowDeactivated(WindowEvent e) { header = new EditorHeader(this); upper.add(header); - textarea = createTextArea(); - textarea.setName("editor"); - // assemble console panel, consisting of status area and the console itself JPanel consolePanel = new JPanel(); consolePanel.setLayout(new BorderLayout()); @@ -289,19 +278,9 @@ public void windowDeactivated(WindowEvent e) { lineStatus = new EditorLineStatus(); consolePanel.add(lineStatus, BorderLayout.SOUTH); - // RTextScrollPane - scrollPane = new RTextScrollPane(textarea, true); - scrollPane.setBorder(new MatteBorder(0, 6, 0, 0, Theme.getColor("editor.bgcolor"))); - scrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); - scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers")); - scrollPane.setIconRowHeaderEnabled(false); - - Gutter gutter = scrollPane.getGutter(); - gutter.setBookmarkingEnabled(false); - //gutter.setBookmarkIcon(CompletionsRenderer.getIcon(CompletionType.TEMPLATE)); - gutter.setIconRowHeaderInheritsGutterBackground(true); + codePanel = new JPanel(new BorderLayout()); + upper.add(codePanel); - upper.add(scrollPane); splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, upper, consolePanel); splitPane.setOneTouchExpandable(true); @@ -490,45 +469,11 @@ protected int[] getPlacement() { * with things in the Preferences window. */ public void applyPreferences() { - - // apply the setting for 'use external editor' boolean external = PreferencesData.getBoolean("editor.external"); - - textarea.setEditable(!external); saveMenuItem.setEnabled(!external); saveAsMenuItem.setEnabled(!external); - - textarea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding")); - scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding")); - scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers")); - - if (external) { - // disable line highlight and turn off the caret when disabling - textarea.setBackground(Theme.getColor("editor.external.bgcolor")); - textarea.setHighlightCurrentLine(false); - textarea.setEditable(false); - - } else { - textarea.setBackground(Theme.getColor("editor.bgcolor")); - textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight")); - textarea.setEditable(true); - } - - // apply changes to the font size for the editor - //TextAreaPainter painter = textarea.getPainter(); - textarea.setFont(PreferencesData.getFont("editor.font")); - //Font font = painter.getFont(); - //textarea.getPainter().setFont(new Font("Courier", Font.PLAIN, 36)); - - // in case tab expansion stuff has changed - // listener.applyPreferences(); - - // in case moved to a new location - // For 0125, changing to async version (to be implemented later) - //sketchbook.rebuildMenus(); - // For 0126, moved into Base, which will notify all editors. - //base.rebuildMenusAsync(); - + for (EditorTab tab: tabs) + tab.applyPreferences(); } @@ -1025,57 +970,14 @@ private String findClassInZipFile(String base, File file) { } } return null; - } - - - private SketchTextArea createTextArea() throws IOException { - final SketchTextArea textArea = new SketchTextArea(base.getPdeKeywords()); - textArea.setFocusTraversalKeysEnabled(false); - textArea.requestFocusInWindow(); - textArea.setMarkOccurrences(PreferencesData.getBoolean("editor.advanced")); - textArea.setMarginLineEnabled(false); - textArea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding")); - textArea.setAntiAliasingEnabled(PreferencesData.getBoolean("editor.antialias")); - textArea.setTabsEmulated(PreferencesData.getBoolean("editor.tabs.expand")); - textArea.setTabSize(PreferencesData.getInteger("editor.tabs.size")); - textArea.addHyperlinkListener(new HyperlinkListener() { - @Override - public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) { - try { - platform.openURL(sketch.getFolder(), hyperlinkEvent.getURL().toExternalForm()); - } catch (Exception e) { - Base.showWarning(e.getMessage(), e.getMessage(), e); - } - } - }); - textArea.addCaretListener(new CaretListener() { - - @Override - public void caretUpdate(CaretEvent e) { - int lineStart = textArea.getDocument().getDefaultRootElement().getElementIndex(e.getMark()); - int lineEnd = textArea.getDocument().getDefaultRootElement().getElementIndex(e.getDot()); - - lineStatus.set(lineStart, lineEnd); - } - - }); - - ToolTipManager.sharedInstance().registerComponent(textArea); - - configurePopupMenu(textArea); - return textArea; - } + } public void updateKeywords(PdeKeywords keywords) { - // update GUI for "Find In Reference" - textarea.setKeywords(keywords); - // update document for syntax highlighting - RSyntaxDocument document = (RSyntaxDocument) textarea.getDocument(); - document.setTokenMakerFactory(new ArduinoTokenMakerFactory(keywords)); - document.setSyntaxStyle(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS); + for (EditorTab tab : tabs) + tab.updateKeywords(keywords); } - private JMenuItem createToolMenuItem(String className) { + JMenuItem createToolMenuItem(String className) { try { Class toolClass = Class.forName(className); final Tool tool = (Tool) toolClass.newInstance(); @@ -1407,7 +1309,7 @@ private JMenu buildEditMenu() { JMenuItem cutItem = newJMenuItem(tr("Cut"), 'X'); cutItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleCut(); + getCurrentTab().handleCut(); } }); menu.add(cutItem); @@ -1415,7 +1317,7 @@ public void actionPerformed(ActionEvent e) { JMenuItem copyItem = newJMenuItem(tr("Copy"), 'C'); copyItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - textarea.copy(); + getCurrentTab().getTextArea().copy(); } }); menu.add(copyItem); @@ -1423,11 +1325,7 @@ public void actionPerformed(ActionEvent e) { JMenuItem copyForumItem = newJMenuItemShift(tr("Copy for Forum"), 'C'); copyForumItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { -// SwingUtilities.invokeLater(new Runnable() { -// public void run() { - new DiscourseFormat(Editor.this, false).show(); -// } -// }); + getCurrentTab().handleHTMLCopy(); } }); menu.add(copyForumItem); @@ -1435,11 +1333,7 @@ public void actionPerformed(ActionEvent e) { JMenuItem copyHTMLItem = newJMenuItemAlt(tr("Copy as HTML"), 'C'); copyHTMLItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { -// SwingUtilities.invokeLater(new Runnable() { -// public void run() { - new DiscourseFormat(Editor.this, true).show(); -// } -// }); + getCurrentTab().handleDiscourseCopy(); } }); menu.add(copyHTMLItem); @@ -1447,7 +1341,7 @@ public void actionPerformed(ActionEvent e) { JMenuItem pasteItem = newJMenuItem(tr("Paste"), 'V'); pasteItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - textarea.paste(); + getCurrentTab().handlePaste(); sketch.setModified(true); } }); @@ -1456,7 +1350,7 @@ public void actionPerformed(ActionEvent e) { JMenuItem selectAllItem = newJMenuItem(tr("Select All"), 'A'); selectAllItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - textarea.selectAll(); + getCurrentTab().handleSelectAll(); } }); menu.add(selectAllItem); @@ -1474,7 +1368,7 @@ public void actionPerformed(ActionEvent e) { JMenuItem commentItem = newJMenuItem(tr("Comment/Uncomment"), '/'); commentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); + getCurrentTab().handleCommentUncomment(); } }); menu.add(commentItem); @@ -1483,7 +1377,7 @@ public void actionPerformed(ActionEvent e) { increaseIndentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); increaseIndentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); + getCurrentTab().handleIndentOutdent(true); } }); menu.add(increaseIndentItem); @@ -1493,7 +1387,7 @@ public void actionPerformed(ActionEvent e) { decreseIndentItem.setName("menuDecreaseIndent"); decreseIndentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleIndentOutdent(false); + getCurrentTab().handleIndentOutdent(false); } }); menu.add(decreseIndentItem); @@ -1507,7 +1401,7 @@ public void actionPerformed(ActionEvent e) { find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE); } if (!OSUtils.isMacOS()) { - find.setFindText(getSelectedText()); + find.setFindText(getCurrentTab().getSelectedText()); } find.setLocationRelativeTo(Editor.this); find.setVisible(true); @@ -1542,7 +1436,7 @@ public void actionPerformed(ActionEvent e) { if (find == null) { find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE); } - find.setFindText(getSelectedText()); + find.setFindText(getCurrentTab().getSelectedText()); } }); menu.add(useSelectionForFindItem); @@ -1597,7 +1491,7 @@ public UndoAction() { public void actionPerformed(ActionEvent e) { try { - textarea.undoLastAction(); + getCurrentTab().handleRedo(); sketch.setModified(true); } catch (CannotUndoException ex) { //System.out.println("Unable to undo: " + ex); @@ -1606,8 +1500,7 @@ public void actionPerformed(ActionEvent e) { } protected void updateUndoState() { - - UndoManager undo = textarea.getUndoManager(); + UndoManager undo = getCurrentTab().getUndoManager(); if (undo.canUndo()) { this.setEnabled(true); @@ -1632,7 +1525,7 @@ public RedoAction() { public void actionPerformed(ActionEvent e) { try { - textarea.redoLastAction(); + getCurrentTab().handleRedo(); sketch.setModified(true); } catch (CannotRedoException ex) { //System.out.println("Unable to redo: " + ex); @@ -1641,8 +1534,8 @@ public void actionPerformed(ActionEvent e) { } protected void updateRedoState() { - UndoManager undo = textarea.getUndoManager(); - + UndoManager undo = getCurrentTab().getUndoManager(); + if (undo.canRedo()) { redoItem.setEnabled(true); redoItem.setText(undo.getRedoPresentationName()); @@ -1687,240 +1580,82 @@ public Sketch getSketch() { return sketch; } - /** - * Get the TextArea object for use (not recommended). This should only - * be used in obscure cases that really need to hack the internals of the - * JEditTextArea. Most tools should only interface via the get/set functions - * found in this class. This will maintain compatibility with future releases, - * which will not use TextArea. + * Gets the currently displaying tab. */ - public SketchTextArea getTextArea() { - return textarea; + public EditorTab getCurrentTab() { + return tabs.get(currentTabIndex); } - - - /** - * Get the contents of the current buffer. Used by the Sketch class. - */ - public String getText() { - return textarea.getText(); - } - - - /** - * Replace the entire contents of the front-most tab. - */ - public void setText(String what) { - textarea.setText(what); - } - - - + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** - * Called to update the text but not switch to a different set of code - * (which would affect the undo manager). + * Change the currently displayed tab. + * Note that the GUI might not update immediately, since this needs + * to run in the Event dispatch thread. + * @param index The index of the tab to select */ -// public void setText2(String what, int start, int stop) { -// beginCompoundEdit(); -// textarea.setText(what); -// endCompoundEdit(); -// -// // make sure that a tool isn't asking for a bad location -// start = Math.max(0, Math.min(start, textarea.getDocumentLength())); -// stop = Math.max(0, Math.min(start, textarea.getDocumentLength())); -// textarea.select(start, stop); -// -// textarea.requestFocus(); // get the caret blinking -// } - - - public String getSelectedText() { - return textarea.getSelectedText(); - } - - - public void setSelectedText(String what) { - textarea.replaceSelection(what); - } - - public void setSelection(int start, int stop) { - textarea.select(start, stop); - } - + public void selectTab(final int index) { + currentTabIndex = index; + undoAction.updateUndoState(); + redoAction.updateRedoState(); + updateTitle(); - /** - * Get the beginning point of the current selection. - */ - public int getSelectionStart() { - return textarea.getSelectionStart(); + // This must be run in the GUI thread + SwingUtilities.invokeLater(() -> { + codePanel.removeAll(); + codePanel.add(tabs.get(index), BorderLayout.CENTER); + tabs.get(index).requestFocus(); // get the caret blinking + // For some reason, these are needed. Revalidate says it should be + // automatically called when components are added or removed, but without + // it, the component switched to is not displayed. repaint() is needed to + // clear the entire text area of any previous text. + codePanel.revalidate(); + codePanel.repaint(); + }); } - - /** - * Get the end point of the current selection. - */ - public int getSelectionStop() { - return textarea.getSelectionEnd(); + public EditorTab findTab(final SketchCode doc) { + return tabs.get(findTabIndex(doc)); } - - /** - * Get text for a specified line. - */ - private String getLineText(int line) { - try { - return textarea.getText(textarea.getLineStartOffset(line), textarea.getLineEndOffset(line)); - } catch (BadLocationException e) { - return ""; + public int findTabIndex(final SketchCode doc) { + for (int i = 0; i < tabs.size(); ++i) { + if (tabs.get(i).getSketchCode() == doc) + return i; } + return -1; } - - public int getScrollPosition() { - return scrollPane.getVerticalScrollBar().getValue(); - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - /** - * Switch between tabs, this swaps out the Document object - * that's currently being manipulated. - */ - protected void setCode(final SketchCodeDocument codeDoc) { - RSyntaxDocument document = (RSyntaxDocument) codeDoc.getDocument(); - - if (document == null) { // this document not yet inited - document = new RSyntaxDocument(new ArduinoTokenMakerFactory(base.getPdeKeywords()), RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS); - document.putProperty(PlainDocument.tabSizeAttribute, PreferencesData.getInteger("editor.tabs.size")); - - // insert the program text into the document object + public void sketchLoaded(Sketch sketch) { + tabs.clear(); + currentTabIndex = -1; + tabs.ensureCapacity(sketch.getCodeCount()); + for (SketchCode code : sketch.getCodes()) { try { - document.insertString(0, codeDoc.getCode().getProgram(), null); - } catch (BadLocationException bl) { - bl.printStackTrace(); + addTab(code); + } catch(IOException e) { + // TODO: Improve / move error handling + System.err.println(e); } - // set up this guy's own undo manager -// code.undo = new UndoManager(); - - codeDoc.setDocument(document); } - - if(codeDoc.getUndo() == null){ - codeDoc.setUndo(new LastUndoableEditAwareUndoManager(textarea, this)); - document.addUndoableEditListener(codeDoc.getUndo()); - } - - // Update the document object that's in use - textarea.switchDocument(document, codeDoc.getUndo()); - - // HACK multiple tabs: for update Listeners of Gutter, forcin call: Gutter.setTextArea(RTextArea) - // BUG: https://github.com/bobbylight/RSyntaxTextArea/issues/84 - scrollPane.setViewportView(textarea); - - textarea.select(codeDoc.getSelectionStart(), codeDoc.getSelectionStop()); - textarea.requestFocus(); // get the caret blinking - - 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(); - } - }); - - updateTitle(); } - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - /** - * Implements Edit → Cut. + * Switch between tabs, this swaps out the Document object + * that's currently being manipulated. */ - private void handleCut() { - textarea.cut(); + protected void setCode(final SketchCodeDocument codeDoc) { + selectTab(findTabIndex(codeDoc.getCode())); } - - private void handleDiscourseCopy() { - new DiscourseFormat(Editor.this, false).show(); + protected void addTab(SketchCode code) throws IOException { + EditorTab tab = new EditorTab(this, code); + tabs.add(tab); } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - private void handleHTMLCopy() { - new DiscourseFormat(Editor.this, true).show(); - } - - - void handleCommentUncomment() { - - Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaToggleCommentAction); - action.actionPerformed(null); - - } - - - private void handleIndentOutdent(boolean indent) { - if (indent) { - Action action = textarea.getActionMap().get(SketchTextAreaEditorKit.rtaIncreaseIndentAction); - action.actionPerformed(null); - } else { - Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaDecreaseIndentAction); - action.actionPerformed(null); - } - } - - private String getCurrentKeyword() { - String text = ""; - if (textarea.getSelectedText() != null) - text = textarea.getSelectedText().trim(); - - try { - int current = textarea.getCaretPosition(); - int startOffset = 0; - int endIndex = current; - String tmp = textarea.getDocument().getText(current, 1); - // TODO probably a regexp that matches Arduino lang special chars - // already exists. - String regexp = "[\\s\\n();\\\\.!='\\[\\]{}]"; - - while (!tmp.matches(regexp)) { - endIndex++; - tmp = textarea.getDocument().getText(endIndex, 1); - } - // For some reason document index start at 2. - // if( current - start < 2 ) return; - - tmp = ""; - while (!tmp.matches(regexp)) { - startOffset++; - if (current - startOffset < 0) { - tmp = textarea.getDocument().getText(0, 1); - break; - } else - tmp = textarea.getDocument().getText(current - startOffset, 1); - } - startOffset--; - - int length = endIndex - current + startOffset; - text = textarea.getDocument().getText(current - startOffset, length); - - } catch (BadLocationException bl) { - bl.printStackTrace(); - } - return text; - } - - private void handleFindReference(ActionEvent e) { - String text = getCurrentKeyword(); + void handleFindReference(ActionEvent e) { + String text = getCurrentTab().getCurrentKeyword(); String referenceFile = base.getPdeKeywords().getReference(text); if (referenceFile == null) { @@ -2009,12 +1744,13 @@ public void run() { } public void removeAllLineHighlights() { - textarea.removeAllLineHighlights(); + for (EditorTab tab : tabs) + tab.getTextArea().removeAllLineHighlights(); } public void addLineHighlight(int line) throws BadLocationException { - textarea.addLineHighlight(line, new Color(1, 0, 0, 0.2f)); - textarea.setCaretPosition(textarea.getLineStartOffset(line)); + getCurrentTab().getTextArea().addLineHighlight(line, new Color(1, 0, 0, 0.2f)); + getCurrentTab().getTextArea().setCaretPosition(getCurrentTab().getTextArea().getLineStartOffset(line)); } private class DefaultStopHandler implements Runnable { @@ -2139,11 +1875,10 @@ protected void handleOpenUnchecked(File file, int codeIndex, untitled = false; sketch.setCurrentCode(codeIndex); - textarea.select(selStart, selStop); - scrollPane.getVerticalScrollBar().setValue(scrollPos); + getCurrentTab().setSelection(selStart, selStop); + getCurrentTab().setScrollPosition(scrollPos); } - /** * Second stage of open, occurs after having checked to see if the * modifications (if any) to the previous sketch need to be saved. @@ -2214,6 +1949,7 @@ protected boolean handleOpenInternal(File sketchFile) { Base.showWarning(tr("Error"), tr("Could not create the sketch."), e); return false; } + header.rebuild(); updateTitle(); // Disable untitled setting from previous document, if any @@ -2778,9 +2514,9 @@ private void handlePrint() { PrinterJob printerJob = PrinterJob.getPrinterJob(); if (pageFormat != null) { //System.out.println("setting page format " + pageFormat); - printerJob.setPrintable(textarea, pageFormat); + printerJob.setPrintable(getCurrentTab().getTextArea(), pageFormat); } else { - printerJob.setPrintable(textarea); + printerJob.setPrintable(getCurrentTab().getTextArea()); } // set the name of the job to the code name printerJob.setJobName(sketch.getCurrentCode().getPrettyName()); @@ -2833,17 +2569,17 @@ 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()) { + if (line >= getCurrentTab().getTextArea().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 (getLineText(line).length() == 0) { + line = getCurrentTab().getTextArea().getLineCount() - 1; + if (getCurrentTab().getLineText(line).length() == 0) { // The last line may be zero length, meaning nothing to select. // If so, back up one more line. line--; } } - if (line < 0 || line >= textarea.getLineCount()) { + if (line < 0 || line >= getCurrentTab().getTextArea().getLineCount()) { System.err.println(I18n.format(tr("Bad error line: {0}"), line)); } else { try { @@ -2888,7 +2624,6 @@ private void statusEmpty() { statusNotice(EMPTY); } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected void onBoardOrPortChange() { @@ -2902,110 +2637,4 @@ protected void onBoardOrPortChange() { } - private void configurePopupMenu(final SketchTextArea textarea){ - - JPopupMenu menu = textarea.getPopupMenu(); - - menu.addSeparator(); - - JMenuItem item = createToolMenuItem("cc.arduino.packages.formatter.AStyle"); - if (item == null) { - throw new NullPointerException("Tool cc.arduino.packages.formatter.AStyle unavailable"); - } - item.setName("menuToolsAutoFormat"); - - menu.add(item); - - item = newJMenuItem(tr("Comment/Uncomment"), '/'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); - } - }); - menu.add(item); - - item = newJMenuItem(tr("Increase Indent"), ']'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); - } - }); - menu.add(item); - - item = newJMenuItem(tr("Decrease Indent"), '['); - item.setName("menuDecreaseIndent"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(false); - } - }); - menu.add(item); - - item = new JMenuItem(tr("Copy for Forum")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleDiscourseCopy(); - } - }); - menu.add(item); - - item = new JMenuItem(tr("Copy as HTML")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleHTMLCopy(); - } - }); - menu.add(item); - - final JMenuItem referenceItem = new JMenuItem(tr("Find in Reference")); - referenceItem.addActionListener(this::handleFindReference); - menu.add(referenceItem); - - final JMenuItem openURLItem = new JMenuItem(tr("Open URL")); - openURLItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - Base.openURL(e.getActionCommand()); - } - }); - menu.add(openURLItem); - - menu.addPopupMenuListener(new PopupMenuListener() { - - @Override - public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - String referenceFile = base.getPdeKeywords().getReference(getCurrentKeyword()); - referenceItem.setEnabled(referenceFile != null); - - int offset = textarea.getCaretPosition(); - org.fife.ui.rsyntaxtextarea.Token token = RSyntaxUtilities.getTokenAtOffset(textarea, offset); - if (token != null && token.isHyperlink()) { - openURLItem.setEnabled(true); - openURLItem.setActionCommand(token.getLexeme()); - } else { - openURLItem.setEnabled(false); - } - } - - @Override - public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { - } - - @Override - public void popupMenuCanceled(PopupMenuEvent e) { - } - }); - - } - - public void goToLine(int line) { - if (line <= 0) { - return; - } - try { - textarea.setCaretPosition(textarea.getLineStartOffset(line - 1)); - } catch (BadLocationException e) { - //ignore - } - } - } diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java new file mode 100644 index 00000000000..8cd3a929c79 --- /dev/null +++ b/app/src/processing/app/EditorTab.java @@ -0,0 +1,479 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Arduino project - http://www.arduino.cc + + Copyright (c) 2015 Matthijs Kooijman + Copyright (c) 2004-09 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 +*/ + +package processing.app; + +import static processing.app.I18n.tr; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.ToolTipManager; +import javax.swing.border.MatteBorder; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; +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.rtextarea.Gutter; +import org.fife.ui.rtextarea.RTextScrollPane; + +import processing.app.syntax.ArduinoTokenMakerFactory; +import processing.app.syntax.PdeKeywords; +import processing.app.syntax.SketchTextArea; +import processing.app.syntax.SketchTextAreaEditorKit; +import processing.app.tools.DiscourseFormat; + +/** + * Single tab, editing a single file, in the main window. + */ +public class EditorTab extends JPanel { + protected Editor editor; + protected SketchTextArea textarea; + protected RTextScrollPane scrollPane; + protected SketchCode code; + + public EditorTab(Editor editor, SketchCode code) throws IOException { + super(new BorderLayout()); + this.editor = editor; + this.code = code; + this.textarea = createTextArea(); + this.scrollPane = createScrollPane(this.textarea); + applyPreferences(); + add(this.scrollPane, BorderLayout.CENTER); + + UndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor); + ((RSyntaxDocument)textarea.getDocument()).addUndoableEditListener(undo); + } + + private RSyntaxDocument createDocument() { + RSyntaxDocument document = new RSyntaxDocument(new ArduinoTokenMakerFactory(editor.base.getPdeKeywords()), RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS); + document.putProperty(PlainDocument.tabSizeAttribute, PreferencesData.getInteger("editor.tabs.size")); + + // insert the program text into the document object + try { + document.insertString(0, code.getProgram(), null); + } catch (BadLocationException bl) { + bl.printStackTrace(); + } + ((SketchCodeDocument) code.getMetadata()).setDocument(document); + + return document; + } + + private RTextScrollPane createScrollPane(SketchTextArea textArea) throws IOException { + RTextScrollPane scrollPane = new RTextScrollPane(textArea, true); + scrollPane.setBorder(new MatteBorder(0, 6, 0, 0, Theme.getColor("editor.bgcolor"))); + scrollPane.setViewportBorder(BorderFactory.createEmptyBorder()); + scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers")); + scrollPane.setIconRowHeaderEnabled(false); + + Gutter gutter = scrollPane.getGutter(); + gutter.setBookmarkingEnabled(false); + //gutter.setBookmarkIcon(CompletionsRenderer.getIcon(CompletionType.TEMPLATE)); + gutter.setIconRowHeaderInheritsGutterBackground(true); + + return scrollPane; + } + + private SketchTextArea createTextArea() throws IOException { + RSyntaxDocument document = createDocument(); + final SketchTextArea textArea = new SketchTextArea(document, editor.base.getPdeKeywords()); + textArea.setName("editor"); + textArea.setFocusTraversalKeysEnabled(false); + //textArea.requestFocusInWindow(); + textArea.setMarkOccurrences(PreferencesData.getBoolean("editor.advanced")); + textArea.setMarginLineEnabled(false); + textArea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding")); + textArea.setAntiAliasingEnabled(PreferencesData.getBoolean("editor.antialias")); + textArea.setTabsEmulated(PreferencesData.getBoolean("editor.tabs.expand")); + textArea.setTabSize(PreferencesData.getInteger("editor.tabs.size")); + textArea.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) { + try { + editor.platform.openURL(editor.getSketch().getFolder(), hyperlinkEvent.getURL().toExternalForm()); + } catch (Exception e) { + Base.showWarning(e.getMessage(), e.getMessage(), e); + } + } + }); + textArea.addCaretListener(new CaretListener() { + @Override + public void caretUpdate(CaretEvent e) { + int lineStart = textArea.getDocument().getDefaultRootElement().getElementIndex(e.getMark()); + int lineEnd = textArea.getDocument().getDefaultRootElement().getElementIndex(e.getDot()); + + editor.lineStatus.set(lineStart, lineEnd); + } + + }); + + ToolTipManager.sharedInstance().registerComponent(textArea); + + configurePopupMenu(textArea); + return textArea; + } + + private void configurePopupMenu(final SketchTextArea textarea){ + + JPopupMenu menu = textarea.getPopupMenu(); + + menu.addSeparator(); + + JMenuItem item = editor.createToolMenuItem("cc.arduino.packages.formatter.AStyle"); + if (item == null) { + throw new NullPointerException("Tool cc.arduino.packages.formatter.AStyle unavailable"); + } + item.setName("menuToolsAutoFormat"); + + menu.add(item); + + item = new JMenuItem(tr("Comment/Uncomment"), '/'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleCommentUncomment(); + } + }); + menu.add(item); + + item = new JMenuItem(tr("Increase Indent"), ']'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleIndentOutdent(true); + } + }); + menu.add(item); + + item = new JMenuItem(tr("Decrease Indent"), '['); + item.setName("menuDecreaseIndent"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleIndentOutdent(false); + } + }); + menu.add(item); + + item = new JMenuItem(tr("Copy for Forum")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleDiscourseCopy(); + } + }); + menu.add(item); + + item = new JMenuItem(tr("Copy as HTML")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleHTMLCopy(); + } + }); + menu.add(item); + + final JMenuItem referenceItem = new JMenuItem(tr("Find in Reference")); + referenceItem.addActionListener(editor::handleFindReference); + menu.add(referenceItem); + + final JMenuItem openURLItem = new JMenuItem(tr("Open URL")); + openURLItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Base.openURL(e.getActionCommand()); + } + }); + menu.add(openURLItem); + + menu.addPopupMenuListener(new PopupMenuListener() { + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + String referenceFile = editor.base.getPdeKeywords().getReference(getCurrentKeyword()); + referenceItem.setEnabled(referenceFile != null); + + int offset = textarea.getCaretPosition(); + org.fife.ui.rsyntaxtextarea.Token token = RSyntaxUtilities.getTokenAtOffset(textarea, offset); + if (token != null && token.isHyperlink()) { + openURLItem.setEnabled(true); + openURLItem.setActionCommand(token.getLexeme()); + } else { + openURLItem.setEnabled(false); + } + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + } + }); + + } + + public void applyPreferences() { + textarea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding")); + scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding")); + scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers")); + + // apply the setting for 'use external editor' + if (PreferencesData.getBoolean("editor.external")) { + // disable line highlight and turn off the caret when disabling + textarea.setBackground(Theme.getColor("editor.external.bgcolor")); + textarea.setHighlightCurrentLine(false); + textarea.setEditable(false); + + } else { + textarea.setBackground(Theme.getColor("editor.bgcolor")); + textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight")); + textarea.setEditable(true); + } + + // apply changes to the font size for the editor + textarea.setFont(PreferencesData.getFont("editor.font")); + } + + public void updateKeywords(PdeKeywords keywords) { + // update GUI for "Find In Reference" + textarea.setKeywords(keywords); + // update document for syntax highlighting + RSyntaxDocument document = (RSyntaxDocument) textarea.getDocument(); + document.setTokenMakerFactory(new ArduinoTokenMakerFactory(keywords)); + document.setSyntaxStyle(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS); + } + + /** + * Get the TextArea object for use (not recommended). This should only + * be used in obscure cases that really need to hack the internals of the + * JEditTextArea. Most tools should only interface via the get/set functions + * found in this class. This will maintain compatibility with future releases, + * which will not use TextArea. + */ + public SketchTextArea getTextArea() { + return textarea; + } + + /** + * Get the sketch this tab is editing a file from. + */ + public Sketch getSketch() { + return editor.getSketch(); + } + + /** + * Get the SketchCodeDocument that is being edited in this tab. + */ + public SketchCode getSketchCode() { + return this.code; + } + + /** + * Get the contents of the text area. + */ + public String getText() { + return textarea.getText(); + } + + /** + * Replace the entire contents of this tab. + */ + public void setText(String what) { + textarea.setText(what); + } + + public String getSelectedText() { + return textarea.getSelectedText(); + } + + + public void setSelectedText(String what) { + textarea.replaceSelection(what); + } + + public void setSelection(int start, int stop) { + textarea.select(start, stop); + } + + public int getScrollPosition() { + return scrollPane.getVerticalScrollBar().getValue(); + } + + public void setScrollPosition(int pos) { + scrollPane.getVerticalScrollBar().setValue(pos); + } + + /** + * Get the beginning point of the current selection. + */ + public int getSelectionStart() { + return textarea.getSelectionStart(); + } + + /** + * Get the end point of the current selection. + */ + public int getSelectionStop() { + return textarea.getSelectionEnd(); + } + + /** + * Get text for a specified line. + */ + public String getLineText(int line) { + try { + return textarea.getText(textarea.getLineStartOffset(line), textarea.getLineEndOffset(line)); + } catch (BadLocationException e) { + return ""; + } + } + + /** + * Jump to the given line + * @param line The line number to jump to, 1-based. + */ + public void goToLine(int line) { + if (line <= 0) { + return; + } + try { + textarea.setCaretPosition(textarea.getLineStartOffset(line - 1)); + } catch (BadLocationException e) { + //ignore + } + } + + void handleCut() { + textarea.cut(); + } + + void handleCopy() { + textarea.copy(); + } + + void handlePaste() { + textarea.paste(); + } + + void handleSelectAll() { + textarea.selectAll(); + } + + void handleCommentUncomment() { + Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaToggleCommentAction); + action.actionPerformed(null); + + } + + void handleDiscourseCopy() { + new DiscourseFormat(editor, this, false).show(); + } + + + void handleHTMLCopy() { + new DiscourseFormat(editor, this, true).show(); + } + + void handleIndentOutdent(boolean indent) { + if (indent) { + Action action = textarea.getActionMap().get(SketchTextAreaEditorKit.rtaIncreaseIndentAction); + action.actionPerformed(null); + } else { + Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaDecreaseIndentAction); + action.actionPerformed(null); + } + } + + void handleUndo() { + textarea.undoLastAction(); + } + + void handleRedo() { + textarea.redoLastAction(); + } + + public UndoManager getUndoManager() { + return textarea.getUndoManager(); + } + + public String getCurrentKeyword() { + String text = ""; + if (textarea.getSelectedText() != null) + text = textarea.getSelectedText().trim(); + + try { + int current = textarea.getCaretPosition(); + int startOffset = 0; + int endIndex = current; + String tmp = textarea.getDocument().getText(current, 1); + // TODO probably a regexp that matches Arduino lang special chars + // already exists. + String regexp = "[\\s\\n();\\\\.!='\\[\\]{}]"; + + while (!tmp.matches(regexp)) { + endIndex++; + tmp = textarea.getDocument().getText(endIndex, 1); + } + // For some reason document index start at 2. + // if( current - start < 2 ) return; + + tmp = ""; + while (!tmp.matches(regexp)) { + startOffset++; + if (current - startOffset < 0) { + tmp = textarea.getDocument().getText(0, 1); + break; + } else + tmp = textarea.getDocument().getText(current - startOffset, 1); + } + startOffset--; + + int length = endIndex - current + startOffset; + text = textarea.getDocument().getText(current - startOffset, length); + + } catch (BadLocationException bl) { + bl.printStackTrace(); + } + return text; + } + + @Override + public void requestFocus() { + /** If focus is requested, focus the textarea instead. */ + textarea.requestFocus(); + } + +} \ No newline at end of file diff --git a/app/src/processing/app/EditorToolbar.java b/app/src/processing/app/EditorToolbar.java index d47411af297..7313baa44a8 100644 --- a/app/src/processing/app/EditorToolbar.java +++ b/app/src/processing/app/EditorToolbar.java @@ -445,7 +445,7 @@ public boolean dispatchKeyEvent(final KeyEvent e) { shiftPressed = !shiftPressed; repaint(); } - // Return false to continue processing this keyEvent + // Return false to continue processing this keyEvent return false; } } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 444888c907d..3bb56ac7996 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -96,6 +96,7 @@ private void load() throws IOException { } protected void load(boolean forceUpdate) throws IOException { + current = null; data.load(); for (SketchCode code : data.getCodes()) { @@ -105,6 +106,7 @@ protected void load(boolean forceUpdate) throws IOException { // set the main file to be the current tab if (editor != null) { + editor.sketchLoaded(this); setCurrentCode(currentIndex, forceUpdate); } } @@ -318,7 +320,7 @@ protected void nameCode(String newName) { // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); try { // save this new SketchCode current.getCode().save(); @@ -363,9 +365,9 @@ protected void nameCode(String newName) { // (unfortunately this will kill positions for carets etc) editor.handleOpenUnchecked(newMainFile, currentIndex, - editor.getSelectionStart(), - editor.getSelectionStop(), - editor.getScrollPosition()); + editor.getCurrentTab().getSelectionStart(), + editor.getCurrentTab().getSelectionStop(), + editor.getCurrentTab().getScrollPosition()); // get the changes into the sketchbook menu // (re-enabled in 0115 to fix bug #332) @@ -399,7 +401,17 @@ protected void nameCode(String newName) { return; } ensureExistence(); - data.addCode((new SketchCodeDocument(this, newFile)).getCode()); + SketchCode code = (new SketchCodeDocument(this, newFile)).getCode(); + try { + editor.addTab(code); + } catch (IOException e) { + Base.showWarning(tr("Error"), + I18n.format( + "Failed to open tab for new file" + ), e); + return; + } + data.addCode(code); } // sort the entries @@ -543,7 +555,7 @@ public boolean save() throws IOException { // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); } // don't do anything if not actually modified @@ -699,7 +711,7 @@ protected boolean saveAs() throws IOException { // grab the contents of the current tab before saving // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); } // save the other tabs to their new location @@ -735,9 +747,9 @@ protected boolean saveAs() throws IOException { editor.handleOpenUnchecked(newFile, currentIndex, - editor.getSelectionStart(), - editor.getSelectionStop(), - editor.getScrollPosition()); + editor.getCurrentTab().getSelectionStart(), + editor.getCurrentTab().getSelectionStop(), + editor.getCurrentTab().getScrollPosition()); // Name changed, rebuild the sketch menus //editor.sketchbook.rebuildMenusAsync(); @@ -951,9 +963,9 @@ private void importLibrary(File jarPath) throws IOException { buffer.append(">\n"); } buffer.append('\n'); - buffer.append(editor.getText()); - editor.setText(buffer.toString()); - editor.setSelection(0, 0); // scroll to start + buffer.append(editor.getCurrentTab().getText()); + editor.getCurrentTab().setText(buffer.toString()); + editor.getCurrentTab().setSelection(0, 0); // scroll to start setModified(true); } @@ -977,26 +989,12 @@ private void setCurrentCode(int which, boolean forceUpdate) { } // get the text currently being edited - if (current != null) { - current.getCode().setProgram(editor.getText()); - current.setSelectionStart(editor.getSelectionStart()); - current.setSelectionStop(editor.getSelectionStop()); - current.setScrollPosition(editor.getScrollPosition()); - } + if (current != null) + current.getCode().setProgram(editor.getCurrentTab().getText()); current = (SketchCodeDocument) data.getCode(which).getMetadata(); currentIndex = which; - - if (SwingUtilities.isEventDispatchThread()) { - editor.setCode(current); - } else { - try { - SwingUtilities.invokeAndWait(() -> editor.setCode(current)); - } catch (Exception e) { - e.printStackTrace(); - } - } - + editor.setCode(current); editor.header.rebuild(); } @@ -1052,7 +1050,7 @@ public void prepare() throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); // TODO record history here //current.history.record(program, SketchHistory.RUN); diff --git a/app/src/processing/app/SketchCodeDocument.java b/app/src/processing/app/SketchCodeDocument.java index 681f0af9151..66f399e358f 100644 --- a/app/src/processing/app/SketchCodeDocument.java +++ b/app/src/processing/app/SketchCodeDocument.java @@ -5,23 +5,11 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.Document; -import javax.swing.undo.UndoManager; -public class SketchCodeDocument implements DocumentListener{ +public class SketchCodeDocument implements DocumentListener { private SketchCode code; private Sketch sketch; - private Document document; - - // Undo Manager for this tab, each tab keeps track of their own Editor.undo - // will be set to this object when this code is the tab that's currently the - // front. - private UndoManager undo; - - // saved positions from last time this tab was used - private int selectionStart; - private int selectionStop; - private int scrollPosition; public SketchCodeDocument(Sketch sketch, SketchCode code) { this.code = code; @@ -30,40 +18,7 @@ public SketchCodeDocument(Sketch sketch, SketchCode code) { } public SketchCodeDocument(Sketch sketch, File file) { - this.code = new SketchCode(file, this); - this.sketch = sketch; - } - - public UndoManager getUndo() { - return undo; - } - - public void setUndo(UndoManager undo) { - this.undo = undo; - } - - public int getSelectionStart() { - return selectionStart; - } - - public void setSelectionStart(int selectionStart) { - this.selectionStart = selectionStart; - } - - public int getSelectionStop() { - return selectionStop; - } - - public void setSelectionStop(int selectionStop) { - this.selectionStop = selectionStop; - } - - public int getScrollPosition() { - return scrollPosition; - } - - public void setScrollPosition(int scrollPosition) { - this.scrollPosition = scrollPosition; + this(sketch, new SketchCode(file)); } public SketchCode getCode() { @@ -74,12 +29,7 @@ public void setCode(SketchCode code) { this.code = code; } - public Document getDocument() { - return document; - } - public void setDocument(Document document) { - this.document = document; document.addDocumentListener(this); } @@ -96,8 +46,7 @@ public void removeUpdate(DocumentEvent e) { @Override public void changedUpdate(DocumentEvent e) { - // Callback for when styles in the current document change. - // This method is never called. + // ignore } } diff --git a/app/src/processing/app/syntax/SketchTextArea.java b/app/src/processing/app/syntax/SketchTextArea.java index ac50a2dc2a6..bc71817fbaf 100644 --- a/app/src/processing/app/syntax/SketchTextArea.java +++ b/app/src/processing/app/syntax/SketchTextArea.java @@ -35,7 +35,6 @@ import org.fife.ui.rsyntaxtextarea.Token; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextAreaUI; -import org.fife.ui.rtextarea.RUndoManager; import processing.app.Base; import processing.app.BaseNoGui; import processing.app.PreferencesData; @@ -44,9 +43,7 @@ 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 java.awt.*; import java.awt.event.MouseEvent; import java.io.File; @@ -69,7 +66,8 @@ public class SketchTextArea extends RSyntaxTextArea { private PdeKeywords pdeKeywords; - public SketchTextArea(PdeKeywords pdeKeywords) throws IOException { + public SketchTextArea(RSyntaxDocument document, PdeKeywords pdeKeywords) throws IOException { + super(document); this.pdeKeywords = pdeKeywords; installFeatures(); } @@ -148,25 +146,6 @@ public boolean isSelectionActive() { return this.getSelectedText() != null; } - 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 RTAMouseListener createMouseListener() { return new SketchTextAreaMouseListener(this); diff --git a/app/src/processing/app/tools/DiscourseFormat.java b/app/src/processing/app/tools/DiscourseFormat.java index c631df8bd2e..c79f7d11077 100644 --- a/app/src/processing/app/tools/DiscourseFormat.java +++ b/app/src/processing/app/tools/DiscourseFormat.java @@ -25,6 +25,7 @@ import org.fife.ui.rsyntaxtextarea.Token; import processing.app.Editor; +import processing.app.EditorTab; import processing.app.syntax.SketchTextArea; import javax.swing.text.BadLocationException; @@ -65,9 +66,9 @@ public class DiscourseFormat { * from the actual Processing Tab ready to send to the processing discourse * web (copy & paste) */ - public DiscourseFormat(Editor editor, boolean html) { + public DiscourseFormat(Editor editor, EditorTab tab, boolean html) { this.editor = editor; - this.textarea = editor.getTextArea(); + this.textarea = tab.getTextArea(); this.html = html; } diff --git a/app/src/processing/app/tools/FixEncoding.java b/app/src/processing/app/tools/FixEncoding.java index d76d9b1cb2a..89c2957be1d 100644 --- a/app/src/processing/app/tools/FixEncoding.java +++ b/app/src/processing/app/tools/FixEncoding.java @@ -69,10 +69,8 @@ public void run() { SketchCode code = sketch.getCode(i); code.setProgram(loadWithLocalEncoding(code.getFile())); code.setModified(true); // yes, because we want them to save this + editor.findTab(code).setText(code.getProgram()); } - // Update the currently visible program with its code - editor.setText(sketch.getCurrentCode().getProgram()); - } catch (IOException e) { String msg = tr("An error occurred while trying to fix the file encoding.\nDo not attempt to save this sketch as it may overwrite\nthe old version. Use Open to re-open the sketch and try again.\n") + diff --git a/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java b/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java index 411cb5de6ea..1a213eb1e57 100644 --- a/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java +++ b/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java @@ -55,7 +55,7 @@ public void shouldUndoAndRedo() throws Exception { GuiActionRunner.execute(new GuiQuery() { protected Frame executeInEDT() { - window.getEditor().handleCommentUncomment(); + window.getEditor().getCurrentTab().handleCommentUncomment(); return window.getEditor(); } From 448b8d5694ba6d22e5c28c922515b1f0569d4749 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 7 Dec 2015 12:59:11 +0100 Subject: [PATCH 03/64] Remove SketchCode::getLineCount() It was not used anymore, and removing it makes subsequent refactoring easier. --- arduino-core/src/processing/app/SketchCode.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index 3e185867641..d55220060c6 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -169,11 +169,6 @@ public void setProgram(String replacement) { } - public int getLineCount() { - return BaseNoGui.countLines(program); - } - - public void setModified(boolean modified) { this.modified = modified; } From b1bb853a094b477f4903911fd90bcc322379e1b3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 7 Dec 2015 14:48:14 +0100 Subject: [PATCH 04/64] Let EditorTab listen for changes to the text area Previously, EditorTab set the Document on the SketchCodeDocument, and the latter would listen for changes, only forwarding the modified status to SketchCode. This commit cuts out a step and lets EditorTab call SketchCode::setModified directly. Additionally, the DocumentTextChangedListener helper class is added, which wraps a simple (lambda) function to be called whenever anything about the document text is modified. This hides the verbosity of having to handle both insertion and deletion, and instead suffices with just having a single lambda function instead. --- app/src/processing/app/EditorTab.java | 5 ++- .../processing/app/SketchCodeDocument.java | 21 +--------- .../helpers/DocumentTextChangeListener.java | 39 +++++++++++++++++++ 3 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 app/src/processing/app/helpers/DocumentTextChangeListener.java diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 8cd3a929c79..625f19ad4b4 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -53,6 +53,7 @@ import org.fife.ui.rtextarea.Gutter; import org.fife.ui.rtextarea.RTextScrollPane; +import processing.app.helpers.DocumentTextChangeListener; import processing.app.syntax.ArduinoTokenMakerFactory; import processing.app.syntax.PdeKeywords; import processing.app.syntax.SketchTextArea; @@ -91,8 +92,8 @@ private RSyntaxDocument createDocument() { } catch (BadLocationException bl) { bl.printStackTrace(); } - ((SketchCodeDocument) code.getMetadata()).setDocument(document); - + document.addDocumentListener(new DocumentTextChangeListener( + () -> code.setModified(true))); return document; } diff --git a/app/src/processing/app/SketchCodeDocument.java b/app/src/processing/app/SketchCodeDocument.java index 66f399e358f..0f178570a17 100644 --- a/app/src/processing/app/SketchCodeDocument.java +++ b/app/src/processing/app/SketchCodeDocument.java @@ -6,7 +6,7 @@ import javax.swing.event.DocumentListener; import javax.swing.text.Document; -public class SketchCodeDocument implements DocumentListener { +public class SketchCodeDocument { private SketchCode code; private Sketch sketch; @@ -29,24 +29,5 @@ public void setCode(SketchCode code) { this.code = code; } - public void setDocument(Document document) { - document.addDocumentListener(this); - } - - @Override - public void insertUpdate(DocumentEvent e) { - if(!code.isModified()) sketch.setModified(true); - } - - - @Override - public void removeUpdate(DocumentEvent e) { - if(!code.isModified()) sketch.setModified(true); - } - - @Override - public void changedUpdate(DocumentEvent e) { - // ignore - } } diff --git a/app/src/processing/app/helpers/DocumentTextChangeListener.java b/app/src/processing/app/helpers/DocumentTextChangeListener.java new file mode 100644 index 00000000000..290275e3434 --- /dev/null +++ b/app/src/processing/app/helpers/DocumentTextChangeListener.java @@ -0,0 +1,39 @@ +package processing.app.helpers; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * Helper class that create a document listener that calls the given + * TextChangeListener on any change to the document text (but not changes to + * document attributes). + * + * The TextChangeListener to be passed is intended to be a lambda function, for + * easy definition of a callback. + */ +public class DocumentTextChangeListener implements DocumentListener { + public interface TextChangeListener { + public void textChanged(); + } + + private TextChangeListener onChange; + + public DocumentTextChangeListener(TextChangeListener onChange) { + this.onChange = onChange; + } + + @Override + public void changedUpdate(DocumentEvent arg0) { + /* Attributes changed, do nothing */ + } + + @Override + public void insertUpdate(DocumentEvent arg0) { + onChange.textChanged(); + } + + @Override + public void removeUpdate(DocumentEvent arg0) { + onChange.textChanged(); + } +} From 97bed6d1760a0ce8812cd913550703700eabd4ff Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 10:46:12 +0100 Subject: [PATCH 05/64] Do not store file contents in SketchCode Now that each file in the sketch has its own text area in the GUI, it is no longer needed to store the (possibly modified) contents of each file inside SketchCode. Keeping the contents in the text area is sufficient. Doing so allows removing the code that dealt with copying contents from the text area into the SketchCode instance at the right time, which was fragile and messy. However, when compiling a sketch, the current (modified) file contents still should be used. To allow this, the TextStorage interface is introduced. This is a simple interface implemented by EditorTab, that allows the SketchCode class to query the GUI for the current contents. By using an interface, there is no direct dependency on the GUI code. If no TextStorage instance is attached to a SketchCode, it will just assume that the contents are always unmodified and the contents from the file will be used during compilation. When not using the GUI (e.g. just compiling something from the commandline), there is no need to load the file contents from disk at all, the filenames just have to be passed to arduino-builder and the compiler. So, the SketchCode constructor no longer calls its `load()` function, leaving this to the GUI code to call when appropriate. This also modifies the `SketchCode.load()` function to return the loaded text, instead of storing it internally. To still support adding new files to a sketch (whose file does not exist on disk yet), the EditorTab constructor now allows an initial contents to be passed in, to be used instead of loading from disk. Only the empty string is passed for new files now, but this could also be used for the bare minimum contents of a new sketch later (which is now down by creating a .ino file in a temporary directory). Another side effect of this change is that all changes to the contents now happen through the text area, which keeps track of modifications already. This allows removing all manual calls to `Sketch.setModified()` (even more, the entire function is removed, making `Sketch.isModified()` always check the modification status of the contained files). --- .../cc/arduino/packages/formatter/AStyle.java | 1 - .../arduino/view/findreplace/FindReplace.java | 2 - app/src/processing/app/Editor.java | 19 +++-- app/src/processing/app/EditorTab.java | 69 ++++++++++++--- app/src/processing/app/Sketch.java | 70 +++------------ app/src/processing/app/tools/FixEncoding.java | 4 +- .../src/processing/app/SketchCode.java | 85 ++++++++++++------- 7 files changed, 135 insertions(+), 115 deletions(-) diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index fc74ea06a1b..5550223f7c8 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -94,7 +94,6 @@ public void run() { textArea.getUndoManager().beginInternalAtomicEdit(); editor.removeAllLineHighlights(); editor.getCurrentTab().setText(formattedText); - editor.getSketch().setModified(true); textArea.getUndoManager().endInternalAtomicEdit(); if (line != -1 && lineOffset != -1) { diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java index 112726d1628..81891ab1b7c 100644 --- a/app/src/cc/arduino/view/findreplace/FindReplace.java +++ b/app/src/cc/arduino/view/findreplace/FindReplace.java @@ -392,7 +392,6 @@ private void replace() { if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; editor.getCurrentTab().setSelectedText(replaceField.getText()); - editor.getSketch().setModified(true); // TODO is this necessary? } if (!foundAtLeastOne) { @@ -430,7 +429,6 @@ private void replaceAll() { if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; editor.getCurrentTab().setSelectedText(replaceField.getText()); - editor.getSketch().setModified(true); // TODO is this necessary? } else { break; } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index d7667636ff7..9f073bd5635 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1342,7 +1342,6 @@ public void actionPerformed(ActionEvent e) { pasteItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handlePaste(); - sketch.setModified(true); } }); menu.add(pasteItem); @@ -1492,7 +1491,6 @@ public UndoAction() { public void actionPerformed(ActionEvent e) { try { getCurrentTab().handleRedo(); - sketch.setModified(true); } catch (CannotUndoException ex) { //System.out.println("Unable to undo: " + ex); //ex.printStackTrace(); @@ -1526,7 +1524,6 @@ public RedoAction() { public void actionPerformed(ActionEvent e) { try { getCurrentTab().handleRedo(); - sketch.setModified(true); } catch (CannotRedoException ex) { //System.out.println("Unable to redo: " + ex); //ex.printStackTrace(); @@ -1631,7 +1628,7 @@ public void sketchLoaded(Sketch sketch) { tabs.ensureCapacity(sketch.getCodeCount()); for (SketchCode code : sketch.getCodes()) { try { - addTab(code); + addTab(code, null); } catch(IOException e) { // TODO: Improve / move error handling System.err.println(e); @@ -1647,8 +1644,18 @@ protected void setCode(final SketchCodeDocument codeDoc) { selectTab(findTabIndex(codeDoc.getCode())); } - protected void addTab(SketchCode code) throws IOException { - EditorTab tab = new EditorTab(this, code); + /** + * Add a new tab. + * + * @param code + * The file to show in the tab. + * @param contents + * The contents to show in the tab, or null to load the + * contents from the given file. + * @throws IOException + */ + protected void addTab(SketchCode code, String contents) throws IOException { + EditorTab tab = new EditorTab(this, code, contents); tabs.add(tab); } diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 625f19ad4b4..7252d9d9735 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -63,37 +63,63 @@ /** * Single tab, editing a single file, in the main window. */ -public class EditorTab extends JPanel { +public class EditorTab extends JPanel implements SketchCode.TextStorage { protected Editor editor; protected SketchTextArea textarea; protected RTextScrollPane scrollPane; protected SketchCode code; + protected boolean modified; - public EditorTab(Editor editor, SketchCode code) throws IOException { + /** + * Create a new EditorTab + * + * @param editor + * The Editor this tab runs in + * @param code + * The file to display in this tab + * @param contents + * Initial contents to display in this tab. Can be used when + * editing a file that doesn't exist yet. If null is passed, + * code.load() is called and displayed instead. + * @throws IOException + */ + public EditorTab(Editor editor, SketchCode code, String contents) + throws IOException { super(new BorderLayout()); + + // Load initial contents contents from file if nothing was specified. + if (contents == null) { + contents = code.load(); + modified = false; + } else { + modified = true; + } + this.editor = editor; this.code = code; - this.textarea = createTextArea(); + RSyntaxDocument document = createDocument(contents); + this.textarea = createTextArea(document); this.scrollPane = createScrollPane(this.textarea); applyPreferences(); add(this.scrollPane, BorderLayout.CENTER); - + UndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor); ((RSyntaxDocument)textarea.getDocument()).addUndoableEditListener(undo); + code.setStorage(this); } - private RSyntaxDocument createDocument() { + private RSyntaxDocument createDocument(String contents) { RSyntaxDocument document = new RSyntaxDocument(new ArduinoTokenMakerFactory(editor.base.getPdeKeywords()), RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS); document.putProperty(PlainDocument.tabSizeAttribute, PreferencesData.getInteger("editor.tabs.size")); - + // insert the program text into the document object try { - document.insertString(0, code.getProgram(), null); + document.insertString(0, contents, null); } catch (BadLocationException bl) { bl.printStackTrace(); } document.addDocumentListener(new DocumentTextChangeListener( - () -> code.setModified(true))); + () -> setModified(true))); return document; } @@ -112,8 +138,8 @@ private RTextScrollPane createScrollPane(SketchTextArea textArea) throws IOExcep return scrollPane; } - private SketchTextArea createTextArea() throws IOException { - RSyntaxDocument document = createDocument(); + private SketchTextArea createTextArea(RSyntaxDocument document) + throws IOException { final SketchTextArea textArea = new SketchTextArea(document, editor.base.getPdeKeywords()); textArea.setName("editor"); textArea.setFocusTraversalKeysEnabled(false); @@ -316,6 +342,29 @@ public void setText(String what) { textarea.setText(what); } + /** + * Is the text modified since the last save / load? + */ + public boolean isModified() { + return modified; + } + + /** + * Clear modified status. Should only be called by SketchCode through + * the TextStorage interface. + */ + public void clearModified() { + setModified(false); + } + + private void setModified(boolean value) { + if (value != modified) { + modified = value; + // TODO: Improve decoupling + editor.getSketch().calcModified(); + } + } + public String getSelectedText() { return textarea.getSelectedText(); } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 3bb56ac7996..fa28ba794e4 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -58,9 +58,6 @@ public class Sketch { private final Editor editor; - /** true if any of the files have been modified. */ - private boolean modified; - private SketchCodeDocument current; private int currentIndex; @@ -320,7 +317,6 @@ protected void nameCode(String newName) { // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getCurrentTab().getText()); try { // save this new SketchCode current.getCode().save(); @@ -403,7 +399,7 @@ protected void nameCode(String newName) { ensureExistence(); SketchCode code = (new SketchCodeDocument(this, newFile)).getCode(); try { - editor.addTab(code); + editor.addTab(code, ""); } catch (IOException e) { Base.showWarning(tr("Error"), I18n.format( @@ -510,31 +506,16 @@ public void handleNextCode() { setCurrentCode((currentIndex + 1) % data.getCodeCount()); } - /** - * Sets the modified value for the code in the frontmost tab. + * Called whenever the modification status of one of the tabs changes. TODO: + * Move this code into Editor and improve decoupling from EditorTab */ - public void setModified(boolean state) { - //System.out.println("setting modified to " + state); - //new Exception().printStackTrace(); - current.getCode().setModified(state); - calcModified(); - } - - - private void calcModified() { - modified = false; - for (SketchCode code : data.getCodes()) { - if (code.isModified()) { - modified = true; - break; - } - } + public void calcModified() { editor.header.repaint(); if (OSUtils.isMacOS()) { // http://developer.apple.com/qa/qa2001/qa1146.html - Object modifiedParam = modified ? Boolean.TRUE : Boolean.FALSE; + Object modifiedParam = isModified() ? Boolean.TRUE : Boolean.FALSE; editor.getRootPane().putClientProperty("windowModified", modifiedParam); editor.getRootPane().putClientProperty("Window.documentModified", modifiedParam); } @@ -542,7 +523,11 @@ private void calcModified() { public boolean isModified() { - return modified; + for (SketchCode code : data.getCodes()) { + if (code.isModified()) + return true; + } + return false; } @@ -553,14 +538,6 @@ public boolean save() throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); - // first get the contents of the editor text area - if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getCurrentTab().getText()); - } - - // don't do anything if not actually modified - //if (!modified) return false; - if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) { Base.showMessage(tr("Sketch is read-only"), tr("Some files are marked \"read-only\", so you'll\n" + @@ -606,7 +583,6 @@ public boolean save() throws IOException { } data.save(); - calcModified(); return true; } @@ -708,12 +684,6 @@ protected boolean saveAs() throws IOException { // now make a fresh copy of the folder newFolder.mkdirs(); - // grab the contents of the current tab before saving - // first get the contents of the editor text area - if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getCurrentTab().getText()); - } - // save the other tabs to their new location for (SketchCode code : data.getCodes()) { if (data.indexOfCode(code) == 0) continue; @@ -913,18 +883,6 @@ public boolean addFile(File sourceFile) { data.sortCode(); } setCurrentCode(filename); - editor.header.repaint(); - if (editor.untitled) { // TODO probably not necessary? problematic? - // Mark the new code as modified so that the sketch is saved - current.getCode().setModified(true); - } - - } else { - if (editor.untitled) { // TODO probably not necessary? problematic? - // If a file has been added, mark the main code as modified so - // that the sketch is properly saved. - data.getCode(0).setModified(true); - } } return true; } @@ -966,7 +924,6 @@ private void importLibrary(File jarPath) throws IOException { buffer.append(editor.getCurrentTab().getText()); editor.getCurrentTab().setText(buffer.toString()); editor.getCurrentTab().setSelection(0, 0); // scroll to start - setModified(true); } @@ -988,10 +945,6 @@ private void setCurrentCode(int which, boolean forceUpdate) { return; } - // get the text currently being edited - if (current != null) - current.getCode().setProgram(editor.getCurrentTab().getText()); - current = (SketchCodeDocument) data.getCode(which).getMetadata(); currentIndex = which; editor.setCode(current); @@ -1050,8 +1003,6 @@ public void prepare() throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); - current.getCode().setProgram(editor.getCurrentTab().getText()); - // TODO record history here //current.history.record(program, SketchHistory.RUN); @@ -1215,7 +1166,6 @@ private void ensureExistence() { "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 diff --git a/app/src/processing/app/tools/FixEncoding.java b/app/src/processing/app/tools/FixEncoding.java index 89c2957be1d..3ee5d055406 100644 --- a/app/src/processing/app/tools/FixEncoding.java +++ b/app/src/processing/app/tools/FixEncoding.java @@ -67,9 +67,7 @@ public void run() { try { for (int i = 0; i < sketch.getCodeCount(); i++) { SketchCode code = sketch.getCode(i); - code.setProgram(loadWithLocalEncoding(code.getFile())); - code.setModified(true); // yes, because we want them to save this - editor.findTab(code).setText(code.getProgram()); + editor.findTab(code).setText(loadWithLocalEncoding(code.getFile())); } } catch (IOException e) { String msg = diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index d55220060c6..26a22bc27fc 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -46,14 +46,33 @@ public class SketchCode { */ private File file; + private Object metadata; + /** - * Text of the program text for this tab + * Interface for an in-memory storage of text file contents. This is + * intended to allow a GUI to keep modified text in memory, and allow + * SketchCode to check for changes when needed. */ - private String program; + public static interface TextStorage { + /** Get the current text */ + public String getText(); - private boolean modified; + /** + * Is the text modified externally, after the last call to + * clearModified() or setText()? + */ + public boolean isModified(); - private Object metadata; + /** Clear the isModified() result value */ + public void clearModified(); + }; + + /** + * A storage for this file's text. This can be set by a GUI, so we can + * have access to any modified version of the file. This can be null, + * in which case the file is never modified, and saving is a no-op. + */ + private TextStorage storage; public SketchCode(File file) { init(file, null); @@ -66,13 +85,14 @@ public SketchCode(File file, Object metadata) { private void init(File file, Object metadata) { this.file = file; this.metadata = metadata; + } - try { - load(); - } catch (IOException e) { - System.err.println( - I18n.format(tr("Error while loading code {0}"), file.getName())); - } + /** + * Set an in-memory storage for this file's text, that will be queried + * on compile, save, and whenever the text is needed. + */ + public void setStorage(TextStorage text) { + this.storage = text; } @@ -160,36 +180,33 @@ public boolean isExtension(List extensions) { public String getProgram() { - return program; - } + if (storage != null) + return storage.getText(); - - public void setProgram(String replacement) { - program = replacement; - } - - - public void setModified(boolean modified) { - this.modified = modified; + return null; } public boolean isModified() { - return modified; + if (storage != null) + return storage.isModified(); + return false; } /** - * Load this piece of code from a file. + * Load this piece of code from a file and return the contents. This + * completely ignores any changes in the linked storage, if any, and + * just directly reads the file. */ - private void load() throws IOException { - program = BaseNoGui.loadFile(file); + public String load() throws IOException { + String text = BaseNoGui.loadFile(file); - if (program == null) { + if (text == null) { throw new IOException(); } - if (program.indexOf('\uFFFD') != -1) { + if (text.indexOf('\uFFFD') != -1) { System.err.println( I18n.format( tr("\"{0}\" contains unrecognized characters. " + @@ -202,8 +219,7 @@ private void load() throws IOException { ); System.err.println(); } - - setModified(false); + return text; } @@ -212,11 +228,11 @@ private void load() throws IOException { * flag is set or not. */ public void save() throws IOException { - // TODO re-enable history - //history.record(s, SketchHistory.SAVE); + if (storage == null) + return; /* Nothing to do */ - BaseNoGui.saveFile(program, file); - setModified(false); + BaseNoGui.saveFile(storage.getText(), file); + storage.clearModified(); } @@ -224,7 +240,10 @@ public void save() throws IOException { * Save this file to another location, used by Sketch.saveAs() */ public void saveAs(File newFile) throws IOException { - BaseNoGui.saveFile(program, newFile); + if (storage == null) + return; /* Nothing to do */ + + BaseNoGui.saveFile(storage.getText(), newFile); } From d8e449a194a88e0694723d7df1e0c5206c66c55f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 12:12:00 +0100 Subject: [PATCH 06/64] Add getTabs() and getCurrentTabIndex() to Editor and use them Previously, some of the GUI code would use Editor.getSketch() to get the current sketch, and Sketch.getCurrentCode() to find out the currently selected tab. Since this code is really concerned with the currently open tab in the GUI, it makes more sense to query the Editor tabs list directly. This removes all references the current sketch code, as tracked by Sketch, external to Sketch itself. This prepares for removing the current tab tracking from Sketch later. --- .../arduino/view/findreplace/FindReplace.java | 9 ++++---- app/src/processing/app/Editor.java | 23 ++++++++++++++++--- app/src/processing/app/EditorHeader.java | 17 +++++++++----- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java index 81891ab1b7c..b585fe0ddfa 100644 --- a/app/src/cc/arduino/view/findreplace/FindReplace.java +++ b/app/src/cc/arduino/view/findreplace/FindReplace.java @@ -327,10 +327,10 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or if (nextIndex == -1) { // Nothing found on this tab: Search other tabs if required if (searchTabs) { - // editor. + int numTabs = editor.getTabs().size(); Sketch sketch = editor.getSketch(); - if (sketch.getCodeCount() > 1) { - int realCurrentTab = sketch.getCodeIndex(sketch.getCurrentCode()); + if (numTabs > 1) { + int realCurrentTab = editor.getCurrentTabIndex(); if (originTab != realCurrentTab) { if (originTab < 0) { @@ -338,7 +338,8 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or } if (!wrap) { - if ((!backwards && realCurrentTab + 1 >= sketch.getCodeCount()) || (backwards && realCurrentTab - 1 < 0)) { + if ((!backwards && realCurrentTab + 1 >= numTabs) + || (backwards && realCurrentTab - 1 < 0)) { return false; // Can't continue without wrap } } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 9f073bd5635..b174f4fb8cb 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1583,6 +1583,21 @@ public Sketch getSketch() { public EditorTab getCurrentTab() { return tabs.get(currentTabIndex); } + + /** + * Gets the index of the currently displaying tab. + */ + public int getCurrentTabIndex() { + return currentTabIndex; + } + + /** + * Returns an (unmodifiable) list of currently opened tabs. + */ + public List getTabs() { + return Collections.unmodifiableList(tabs); + } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Change the currently displayed tab. @@ -1970,10 +1985,12 @@ private void updateTitle() { if (sketch == null) { return; } - if (sketch.getName().equals(sketch.getCurrentCode().getPrettyName())) { + SketchCode current = getCurrentTab().getSketchCode(); + if (sketch.getName().equals(current.getPrettyName())) { setTitle(I18n.format(tr("{0} | Arduino {1}"), sketch.getName(), BaseNoGui.VERSION_NAME_LONG)); } else { - setTitle(I18n.format(tr("{0} - {1} | Arduino {2}"), sketch.getName(), sketch.getCurrentCode().getFileName(), BaseNoGui.VERSION_NAME_LONG)); + setTitle(I18n.format(tr("{0} - {1} | Arduino {2}"), sketch.getName(), + current.getFileName(), BaseNoGui.VERSION_NAME_LONG)); } } @@ -2526,7 +2543,7 @@ private void handlePrint() { printerJob.setPrintable(getCurrentTab().getTextArea()); } // set the name of the job to the code name - printerJob.setJobName(sketch.getCurrentCode().getPrettyName()); + printerJob.setJobName(getCurrentTab().getSketchCode().getPrettyName()); if (printerJob.printDialog()) { try { diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index 84dc49df4c1..48c800dc2a5 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -32,7 +32,7 @@ import java.awt.*; import java.awt.event.*; import java.io.IOException; - +import java.util.List; import javax.swing.*; @@ -229,15 +229,18 @@ public void paintComponent(Graphics screen) { g.setColor(backgroundColor); g.fillRect(0, 0, imageW, imageH); - int codeCount = sketch.getCodeCount(); + List tabs = editor.getTabs(); + + int codeCount = tabs.size(); if ((tabLeft == null) || (tabLeft.length < codeCount)) { tabLeft = new int[codeCount]; tabRight = new int[codeCount]; } int x = 6; // offset from left edge of the component - for (int i = 0; i < sketch.getCodeCount(); i++) { - SketchCode code = sketch.getCode(i); + int i = 0; + for (EditorTab tab : tabs) { + SketchCode code = tab.getSketchCode(); String codeName = code.isExtension(sketch.getHiddenExtensions()) ? code.getPrettyName() : code.getFileName(); @@ -252,7 +255,7 @@ public void paintComponent(Graphics screen) { int pieceCount = 2 + (textWidth / PIECE_WIDTH); int pieceWidth = pieceCount * PIECE_WIDTH; - int state = (code == sketch.getCurrentCode()) ? SELECTED : UNSELECTED; + int state = (i == editor.getCurrentTabIndex()) ? SELECTED : UNSELECTED; g.drawImage(pieces[state][LEFT], x, 0, null); x += PIECE_WIDTH; @@ -272,6 +275,7 @@ public void paintComponent(Graphics screen) { g.drawImage(pieces[state][RIGHT], x, 0, null); x += PIECE_WIDTH - 1; // overlap by 1 pixel + i++; } menuLeft = sizeW - (16 + pieces[0][MENU].getWidth(this)); @@ -318,7 +322,8 @@ public void rebuildMenu() { if (sketch != null) { menu.addSeparator(); int i = 0; - for (SketchCode code : sketch.getCodes()) { + for (EditorTab tab : editor.getTabs()) { + SketchCode code = tab.getSketchCode(); final int index = i++; item = new JMenuItem(code.isExtension(sketch.getDefaultExtension()) ? code.getPrettyName() : code.getFileName()); From 3c48410cbdb104444955750ff38dbc364bf2932e Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 12:54:17 +0100 Subject: [PATCH 07/64] Remove current and currentIndex variables from Sketch Instead of letting Sketch (also) keep track of the currently selected tab, this moves the responsibility to Editor instead. When Sketch need to know the current tab and file, it now asks Editor. Switching between tabs is still handled through Sketch methods, but that will be cleaned up later. --- app/src/processing/app/Sketch.java | 68 +++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index fa28ba794e4..cf4f16b44f8 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -57,10 +57,6 @@ */ public class Sketch { private final Editor editor; - - private SketchCodeDocument current; - private int currentIndex; - private final SketchData data; /** @@ -93,7 +89,6 @@ private void load() throws IOException { } protected void load(boolean forceUpdate) throws IOException { - current = null; data.load(); for (SketchCode code : data.getCodes()) { @@ -103,8 +98,11 @@ protected void load(boolean forceUpdate) throws IOException { // set the main file to be the current tab if (editor != null) { + int current = editor.getCurrentTabIndex(); + if (current < 0) + current = 0; editor.sketchLoaded(this); - setCurrentCode(currentIndex, forceUpdate); + setCurrentCode(current, forceUpdate); } } @@ -138,6 +136,9 @@ public void handleNewCode() { * Handler for the Rename Code menu option. */ public void handleRenameCode() { + SketchCode current = editor.getCurrentTab().getSketchCode(); + int currentIndex = editor.getCurrentTabIndex(); + editor.status.clearState(); // make sure the user didn't hide the sketch folder ensureExistence(); @@ -164,8 +165,8 @@ public void handleRenameCode() { renamingCode = true; String prompt = (currentIndex == 0) ? "New name for sketch:" : "New name for file:"; - String oldName = (current.getCode().isExtension("ino")) ? - current.getCode().getPrettyName() : current.getCode().getFileName(); + String oldName = (current.isExtension("ino")) ? current.getPrettyName() + : current.getFileName(); editor.status.edit(prompt, oldName); } @@ -178,6 +179,9 @@ public void handleRenameCode() { * where they diverge. */ protected void nameCode(String newName) { + SketchCode current = editor.getCurrentTab().getSketchCode(); + int currentIndex = editor.getCurrentTabIndex(); + // make sure the user didn't hide the sketch folder ensureExistence(); @@ -192,7 +196,8 @@ protected void nameCode(String newName) { // (osx is case insensitive but preserving, windows insensitive, // *nix is sensitive and preserving.. argh) if (renamingCode) { - if (newName.equalsIgnoreCase(current.getCode().getFileName()) && OSUtils.isWindows()) { + if (newName.equalsIgnoreCase(current.getFileName()) + && OSUtils.isWindows()) { // exit quietly for the 'rename' case. // if it's a 'new' then an error will occur down below return; @@ -221,7 +226,7 @@ protected void nameCode(String newName) { // Don't let the user create the main tab as a .java file instead of .pde if (!isDefaultExtension(newExtension)) { if (renamingCode) { // If creating a new tab, don't show this error - if (current.getCode() == data.getCode(0)) { // If this is the main tab, disallow + if (current == data.getCode(0)) { // If this is the main tab, disallow Base.showWarning(tr("Problem with rename"), tr("The main file can't use an extension.\n" + "(It may be time for your to graduate to a\n" + @@ -316,21 +321,21 @@ protected void nameCode(String newName) { // however this *will* first save the sketch, then rename // first get the contents of the editor text area - if (current.getCode().isModified()) { + if (current.isModified()) { try { // save this new SketchCode - current.getCode().save(); + current.save(); } catch (Exception e) { Base.showWarning(tr("Error"), tr("Could not rename the sketch. (0)"), e); return; } } - if (!current.getCode().renameTo(newFile)) { + if (!current.renameTo(newFile)) { Base.showWarning(tr("Error"), I18n.format( tr("Could not rename \"{0}\" to \"{1}\""), - current.getCode().getFileName(), + current.getFileName(), newFile.getName() ), null); return; @@ -370,11 +375,11 @@ protected void nameCode(String newName) { editor.base.rebuildSketchbookMenus(); } else { // else if something besides code[0] - if (!current.getCode().renameTo(newFile)) { + if (!current.renameTo(newFile)) { Base.showWarning(tr("Error"), I18n.format( tr("Could not rename \"{0}\" to \"{1}\""), - current.getCode().getFileName(), + current.getFileName(), newFile.getName() ), null); return; @@ -425,6 +430,8 @@ protected void nameCode(String newName) { * Remove a piece of code from the sketch and from the disk. */ public void handleDeleteCode() throws IOException { + SketchCode current = editor.getCurrentTab().getSketchCode(); + int currentIndex = editor.getCurrentTabIndex(); editor.status.clearState(); // make sure the user didn't hide the sketch folder ensureExistence(); @@ -443,7 +450,8 @@ public void handleDeleteCode() throws IOException { Object[] options = { tr("OK"), tr("Cancel") }; String prompt = (currentIndex == 0) ? tr("Are you sure you want to delete this sketch?") : - I18n.format(tr("Are you sure you want to delete \"{0}\"?"), current.getCode().getFileNameWithExtensionIfNotIno()); + I18n.format(tr("Are you sure you want to delete \"{0}\"?"), + current.getFileNameWithExtensionIfNotIno()); int result = JOptionPane.showOptionDialog(editor, prompt, tr("Delete"), @@ -470,14 +478,14 @@ public void handleDeleteCode() throws IOException { } else { // delete the file - if (!current.getCode().deleteFile(BaseNoGui.getBuildFolder(data).toPath())) { + if (!current.deleteFile(BaseNoGui.getBuildFolder(data).toPath())) { Base.showMessage(tr("Couldn't do it"), - I18n.format(tr("Could not delete \"{0}\"."), current.getCode().getFileName())); + I18n.format(tr("Could not delete \"{0}\"."), current.getFileName())); return; } // remove code from the list - data.removeCode(current.getCode()); + data.removeCode(current); // just set current tab to the main tab setCurrentCode(0); @@ -493,7 +501,7 @@ public void handleDeleteCode() throws IOException { * Move to the previous tab. */ public void handlePrevCode() { - int prev = currentIndex - 1; + int prev = editor.getCurrentTabIndex() - 1; if (prev < 0) prev = data.getCodeCount()-1; setCurrentCode(prev); } @@ -503,7 +511,7 @@ public void handlePrevCode() { * Move to the next tab. */ public void handleNextCode() { - setCurrentCode((currentIndex + 1) % data.getCodeCount()); + setCurrentCode((editor.getCurrentTabIndex() + 1) % data.getCodeCount()); } /** @@ -716,7 +724,7 @@ protected boolean saveAs() throws IOException { data.getCode(0).saveAs(newFile); editor.handleOpenUnchecked(newFile, - currentIndex, + editor.getCurrentTabIndex(), editor.getCurrentTab().getSelectionStart(), editor.getCurrentTab().getSelectionStop(), editor.getCurrentTab().getScrollPosition()); @@ -908,7 +916,7 @@ private void importLibrary(File jarPath) throws IOException { // import statements into the main sketch file (code[0]) // if the current code is a .java file, insert into current //if (current.flavor == PDE) { - if (hasDefaultExtension(current.getCode())) { + if (hasDefaultExtension(editor.getCurrentTab().getSketchCode())) { setCurrentCode(0); } // could also scan the text in the file to see if each import @@ -940,14 +948,11 @@ public void setCurrentCode(int which) { } private void setCurrentCode(int which, boolean forceUpdate) { - // if current is null, then this is the first setCurrent(0) - if (!forceUpdate && (currentIndex == which) && (current != null)) { + if (!forceUpdate && (editor.getCurrentTabIndex() == which)) { return; } - current = (SketchCodeDocument) data.getCode(which).getMetadata(); - currentIndex = which; - editor.setCode(current); + editor.setCode((SketchCodeDocument)editor.getTabs().get(which).getSketchCode().getMetadata()); editor.header.rebuild(); } @@ -1329,11 +1334,6 @@ public int getCodeIndex(SketchCode who) { } - public SketchCode getCurrentCode() { - return current.getCode(); - } - - private void setUntitled(boolean u) { editor.untitled = u; } From e5f05a447524adfa01d0a74dff37c5283cbe2c4c Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 15:14:27 +0100 Subject: [PATCH 08/64] Remove tab switching logic from Sketch This lets all code directly call `Editor.selectTab()`, or the newly introduced `Editor.selectNextTab()` or `Editor.selectPrevTab()`. This also adds a new `Editor.findTabIndex(String)` to look up a tab based on the filename (what `Sketch.setCurrentCode(String)` used to do). At some point, this method might need to be removed, but for now it allows other code to keep working with minimal changes. --- .../arduino/view/findreplace/FindReplace.java | 8 +-- app/src/processing/app/Editor.java | 37 ++++++++--- app/src/processing/app/EditorHeader.java | 16 ++--- app/src/processing/app/Sketch.java | 66 ++----------------- 4 files changed, 43 insertions(+), 84 deletions(-) diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java index b585fe0ddfa..1b40e1b12a3 100644 --- a/app/src/cc/arduino/view/findreplace/FindReplace.java +++ b/app/src/cc/arduino/view/findreplace/FindReplace.java @@ -31,7 +31,6 @@ import processing.app.Base; import processing.app.Editor; -import processing.app.Sketch; import processing.app.helpers.OSUtils; import java.awt.*; @@ -328,7 +327,6 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or // Nothing found on this tab: Search other tabs if required if (searchTabs) { int numTabs = editor.getTabs().size(); - Sketch sketch = editor.getSketch(); if (numTabs > 1) { int realCurrentTab = editor.getCurrentTabIndex(); @@ -345,12 +343,12 @@ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int or } if (backwards) { - sketch.handlePrevCode(); + editor.selectNextTab(); this.setVisible(true); int l = editor.getCurrentTab().getText().length() - 1; editor.getCurrentTab().setSelection(l, l); } else { - sketch.handleNextCode(); + editor.selectPrevTab(); this.setVisible(true); editor.getCurrentTab().setSelection(0, 0); } @@ -420,7 +418,7 @@ private void replaceAll() { } if (searchAllFilesBox.isSelected()) { - editor.getSketch().setCurrentCode(0); // select the first tab + editor.selectTab(0); // select the first tab } editor.getCurrentTab().setSelection(0, 0); // move to the beginning diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index b174f4fb8cb..d6425978c50 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1610,6 +1610,7 @@ public void selectTab(final int index) { undoAction.updateUndoState(); redoAction.updateRedoState(); updateTitle(); + header.rebuild(); // This must be run in the GUI thread SwingUtilities.invokeLater(() -> { @@ -1625,6 +1626,14 @@ public void selectTab(final int index) { }); } + public void selectNextTab() { + selectTab((currentTabIndex + 1) % tabs.size()); + } + + public void selectPrevTab() { + selectTab((currentTabIndex - 1 + tabs.size()) % tabs.size()); + } + public EditorTab findTab(final SketchCode doc) { return tabs.get(findTabIndex(doc)); } @@ -1637,6 +1646,22 @@ public int findTabIndex(final SketchCode doc) { return -1; } + /** + * Finds the tab that shows the given file (as returned by + * SketchCode.getFileName() or SketchCode.getPrettyName()). + */ + public int findTabIndex(final String name) { + int i = 0; + for (EditorTab tab : tabs) { + SketchCode code = tab.getSketchCode(); + if (name.equals(code.getFileName()) || + name.equals(code.getPrettyName())) { + return i; + } + } + return -1; + } + public void sketchLoaded(Sketch sketch) { tabs.clear(); currentTabIndex = -1; @@ -1651,14 +1676,6 @@ public void sketchLoaded(Sketch sketch) { } } - /** - * Switch between tabs, this swaps out the Document object - * that's currently being manipulated. - */ - protected void setCode(final SketchCodeDocument codeDoc) { - selectTab(findTabIndex(codeDoc.getCode())); - } - /** * Add a new tab. * @@ -1896,7 +1913,7 @@ protected void handleOpenUnchecked(File file, int codeIndex, // untitled document, then editor.untitled will be set by Base. untitled = false; - sketch.setCurrentCode(codeIndex); + selectTab(codeIndex); getCurrentTab().setSelection(selStart, selStop); getCurrentTab().setScrollPosition(scrollPos); } @@ -2588,7 +2605,7 @@ public void statusError(Exception e) { if (e instanceof RunnerException) { RunnerException re = (RunnerException) e; if (re.hasCodeIndex()) { - sketch.setCurrentCode(re.getCodeIndex()); + selectTab(re.getCodeIndex()); } if (re.hasCodeLine()) { int line = re.getCodeLine(); diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index 48c800dc2a5..ade19324af7 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -96,12 +96,10 @@ public class Actions { }); public final Action prevTab = new SimpleAction(tr("Previous Tab"), - Keys.ctrlAlt(KeyEvent.VK_LEFT), - () -> editor.sketch.handlePrevCode()); + Keys.ctrlAlt(KeyEvent.VK_LEFT), () -> editor.selectPrevTab()); public final Action nextTab = new SimpleAction(tr("Next Tab"), - Keys.ctrlAlt(KeyEvent.VK_RIGHT), - () -> editor.sketch.handleNextCode()); + Keys.ctrlAlt(KeyEvent.VK_RIGHT), () -> editor.selectNextTab()); Actions() { // Explicitly bind keybindings for the actions with accelerators above @@ -170,10 +168,10 @@ public void mousePressed(MouseEvent e) { popup.show(EditorHeader.this, x, y); } else { - Sketch sketch = editor.getSketch(); - for (int i = 0; i < sketch.getCodeCount(); i++) { + int numTabs = editor.getTabs().size(); + for (int i = 0; i < numTabs; i++) { if ((x > tabLeft[i]) && (x < tabRight[i])) { - sketch.setCurrentCode(i); + editor.selectTab(i); repaint(); } } @@ -321,6 +319,7 @@ public void rebuildMenu() { Sketch sketch = editor.getSketch(); if (sketch != null) { menu.addSeparator(); + int i = 0; for (EditorTab tab : editor.getTabs()) { SketchCode code = tab.getSketchCode(); @@ -328,9 +327,10 @@ public void rebuildMenu() { item = new JMenuItem(code.isExtension(sketch.getDefaultExtension()) ? code.getPrettyName() : code.getFileName()); item.addActionListener((ActionEvent e) -> { - editor.getSketch().setCurrentCode(index); + editor.selectTab(index); }); menu.add(item); + i++; } } } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index cf4f16b44f8..fa25c701426 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -102,7 +102,7 @@ protected void load(boolean forceUpdate) throws IOException { if (current < 0) current = 0; editor.sketchLoaded(this); - setCurrentCode(current, forceUpdate); + editor.selectTab(current); } } @@ -419,7 +419,7 @@ protected void nameCode(String newName) { data.sortCode(); // set the new guy as current - setCurrentCode(newName); + editor.selectTab(editor.findTabIndex(newName)); // update the tabs editor.header.rebuild(); @@ -488,7 +488,7 @@ public void handleDeleteCode() throws IOException { data.removeCode(current); // just set current tab to the main tab - setCurrentCode(0); + editor.selectTab(0); // update the tabs editor.header.repaint(); @@ -496,24 +496,6 @@ public void handleDeleteCode() throws IOException { } } - - /** - * Move to the previous tab. - */ - public void handlePrevCode() { - int prev = editor.getCurrentTabIndex() - 1; - if (prev < 0) prev = data.getCodeCount()-1; - setCurrentCode(prev); - } - - - /** - * Move to the next tab. - */ - public void handleNextCode() { - setCurrentCode((editor.getCurrentTabIndex() + 1) % data.getCodeCount()); - } - /** * Called whenever the modification status of one of the tabs changes. TODO: * Move this code into Editor and improve decoupling from EditorTab @@ -890,7 +872,7 @@ public boolean addFile(File sourceFile) { data.addCode(newCode); data.sortCode(); } - setCurrentCode(filename); + editor.selectTab(editor.findTabIndex(filename)); } return true; } @@ -917,7 +899,7 @@ private void importLibrary(File jarPath) throws IOException { // if the current code is a .java file, insert into current //if (current.flavor == PDE) { if (hasDefaultExtension(editor.getCurrentTab().getSketchCode())) { - setCurrentCode(0); + editor.selectTab(0); } // could also scan the text in the file to see if each import // statement is already in there, but if the user has the import @@ -934,44 +916,6 @@ private void importLibrary(File jarPath) throws IOException { editor.getCurrentTab().setSelection(0, 0); // scroll to start } - - /** - * 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 void setCurrentCode(int which) { - setCurrentCode(which, false); - } - - private void setCurrentCode(int which, boolean forceUpdate) { - if (!forceUpdate && (editor.getCurrentTabIndex() == which)) { - return; - } - - editor.setCode((SketchCodeDocument)editor.getTabs().get(which).getSketchCode().getMetadata()); - editor.header.rebuild(); - } - - - /** - * 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. *

From 851a2e88c2858d515b9d3928e2ef7b929d6584ea Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 15:24:47 +0100 Subject: [PATCH 09/64] Remove SketchCodeDocument This class served no purpose anymore, so it can be removed. The `SketchCode.getMetadata()` and `setMetaData()` methods only served to keep track of a SketchCodeDocument instance (and were no longer used), so these are removed too, just like some SketchCode constructors dealing with this metadata object. --- app/src/processing/app/Sketch.java | 9 ++--- .../processing/app/SketchCodeDocument.java | 33 ------------------- .../src/processing/app/SketchCode.java | 21 ------------ 3 files changed, 2 insertions(+), 61 deletions(-) delete mode 100644 app/src/processing/app/SketchCodeDocument.java diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index fa25c701426..d0ce6c8dd63 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -91,11 +91,6 @@ private void load() throws IOException { protected void load(boolean forceUpdate) throws IOException { data.load(); - for (SketchCode code : data.getCodes()) { - if (code.getMetadata() == null) - code.setMetadata(new SketchCodeDocument(this, code)); - } - // set the main file to be the current tab if (editor != null) { int current = editor.getCurrentTabIndex(); @@ -402,7 +397,7 @@ protected void nameCode(String newName) { return; } ensureExistence(); - SketchCode code = (new SketchCodeDocument(this, newFile)).getCode(); + SketchCode code = new SketchCode(newFile); try { editor.addTab(code, ""); } catch (IOException e) { @@ -862,7 +857,7 @@ public boolean addFile(File sourceFile) { } if (codeExtension != null) { - SketchCode newCode = (new SketchCodeDocument(this, destFile)).getCode(); + SketchCode newCode = new SketchCode(destFile); if (replacement) { data.replaceCode(newCode); diff --git a/app/src/processing/app/SketchCodeDocument.java b/app/src/processing/app/SketchCodeDocument.java deleted file mode 100644 index 0f178570a17..00000000000 --- a/app/src/processing/app/SketchCodeDocument.java +++ /dev/null @@ -1,33 +0,0 @@ -package processing.app; - -import java.io.File; - -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.Document; - -public class SketchCodeDocument { - - private SketchCode code; - private Sketch sketch; - - public SketchCodeDocument(Sketch sketch, SketchCode code) { - this.code = code; - this.sketch = sketch; - this.code.setMetadata(this); - } - - public SketchCodeDocument(Sketch sketch, File file) { - this(sketch, new SketchCode(file)); - } - - public SketchCode getCode() { - return code; - } - - public void setCode(SketchCode code) { - this.code = code; - } - - -} diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index 26a22bc27fc..aee7e62c13b 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -46,8 +46,6 @@ public class SketchCode { */ private File file; - private Object metadata; - /** * Interface for an in-memory storage of text file contents. This is * intended to allow a GUI to keep modified text in memory, and allow @@ -75,16 +73,7 @@ public static interface TextStorage { private TextStorage storage; public SketchCode(File file) { - init(file, null); - } - - public SketchCode(File file, Object metadata) { - init(file, metadata); - } - - private void init(File file, Object metadata) { this.file = file; - this.metadata = metadata; } /** @@ -245,14 +234,4 @@ public void saveAs(File newFile) throws IOException { BaseNoGui.saveFile(storage.getText(), newFile); } - - - public Object getMetadata() { - return metadata; - } - - - public void setMetadata(Object metadata) { - this.metadata = metadata; - } } From f3ab2edef3be91fea3d01cf77d8f13ef102b0f44 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 16:49:31 +0100 Subject: [PATCH 10/64] Remove `SketchData.setName()` It was not used, and since it only updated the `name` attribute, but not the corresponding `file` attribute, nor actually handled renaming actual files, having this method around would actually be harmful, so just drop it. --- arduino-core/src/processing/app/SketchData.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index 21aeecb4822..b5668581573 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -248,10 +248,6 @@ public String getName() { return name; } - public void setName(String name) { - this.name = name; - } - public void clearCodeDocs() { codes.clear(); } From 2d1ffda55a6d713ec393676fce4cee9998269db2 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 17:02:24 +0100 Subject: [PATCH 11/64] Remove unused import --- arduino-core/src/processing/app/SketchCode.java | 1 - 1 file changed, 1 deletion(-) diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index aee7e62c13b..12f9fd680a0 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; From 2edd4a2d476589b04e0375c1ae5f41cdd62c8fd7 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 18:07:05 +0100 Subject: [PATCH 12/64] Let SketchCode track if it is the primary file This makes checking for the primary file easier, without having to know the index of a file in the list of tabs, or relying on the fact that the primary file is always first (it still is, though). This changes some places in Sketch to use the new `SketchCode.isPrimary()` method, but there probably are a lot more places in the code that could be start to use it as well. --- app/src/processing/app/Sketch.java | 18 +++++++-------- .../src/processing/app/SketchCode.java | 22 ++++++++++++++++++- .../src/processing/app/SketchData.java | 3 ++- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index d0ce6c8dd63..7cf54e780e8 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -132,13 +132,12 @@ public void handleNewCode() { */ public void handleRenameCode() { SketchCode current = editor.getCurrentTab().getSketchCode(); - int currentIndex = editor.getCurrentTabIndex(); editor.status.clearState(); // make sure the user didn't hide the sketch folder ensureExistence(); - if (currentIndex == 0 && editor.untitled) { + if (current.isPrimary() && editor.untitled) { Base.showMessage(tr("Sketch is Untitled"), tr("How about saving the sketch first \n" + "before trying to rename it?")); @@ -158,7 +157,7 @@ public void handleRenameCode() { // ask for new name of file (internal to window) // TODO maybe just popup a text area? renamingCode = true; - String prompt = (currentIndex == 0) ? + String prompt = current.isPrimary() ? "New name for sketch:" : "New name for file:"; String oldName = (current.isExtension("ino")) ? current.getPrettyName() : current.getFileName(); @@ -264,7 +263,7 @@ protected void nameCode(String newName) { return; } - if (renamingCode && currentIndex == 0) { + if (renamingCode && current.isPrimary()) { for (SketchCode code : data.getCodes()) { if (sanitaryName.equalsIgnoreCase(code.getPrettyName()) && code.isExtension("cpp")) { @@ -297,7 +296,7 @@ protected void nameCode(String newName) { // } if (renamingCode) { - if (currentIndex == 0) { + if (current.isPrimary()) { // get the new folder name/location String folderName = newName.substring(0, newName.indexOf('.')); File newFolder = new File(data.getFolder().getParentFile(), folderName); @@ -397,7 +396,7 @@ protected void nameCode(String newName) { return; } ensureExistence(); - SketchCode code = new SketchCode(newFile); + SketchCode code = new SketchCode(newFile, false); try { editor.addTab(code, ""); } catch (IOException e) { @@ -426,7 +425,6 @@ protected void nameCode(String newName) { */ public void handleDeleteCode() throws IOException { SketchCode current = editor.getCurrentTab().getSketchCode(); - int currentIndex = editor.getCurrentTabIndex(); editor.status.clearState(); // make sure the user didn't hide the sketch folder ensureExistence(); @@ -443,7 +441,7 @@ public void handleDeleteCode() throws IOException { // confirm deletion with user, yes/no Object[] options = { tr("OK"), tr("Cancel") }; - String prompt = (currentIndex == 0) ? + String prompt = current.isPrimary() ? tr("Are you sure you want to delete this sketch?") : I18n.format(tr("Are you sure you want to delete \"{0}\"?"), current.getFileNameWithExtensionIfNotIno()); @@ -456,7 +454,7 @@ public void handleDeleteCode() throws IOException { options, options[0]); if (result == JOptionPane.YES_OPTION) { - if (currentIndex == 0) { + if (current.isPrimary()) { // need to unset all the modified flags, otherwise tries // to do a save on the handleNew() @@ -857,7 +855,7 @@ public boolean addFile(File sourceFile) { } if (codeExtension != null) { - SketchCode newCode = new SketchCode(destFile); + SketchCode newCode = new SketchCode(destFile, false); if (replacement) { data.replaceCode(newCode); diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index 12f9fd680a0..06d2d3267ae 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -45,6 +45,11 @@ public class SketchCode { */ private File file; + /** + * Is this the primary file in the sketch? + */ + private boolean primary; + /** * Interface for an in-memory storage of text file contents. This is * intended to allow a GUI to keep modified text in memory, and allow @@ -71,8 +76,17 @@ public static interface TextStorage { */ private TextStorage storage; - public SketchCode(File file) { + /** + * Create a new SketchCode + * + * @param file + * The file this SketchCode represents + * @param primary + * Whether this file is the primary file of the sketch + */ + public SketchCode(File file, boolean primary) { this.file = file; + this.primary = primary; } /** @@ -88,6 +102,12 @@ public File getFile() { return file; } + /** + * Is this the primary file in the sketch? + */ + public boolean isPrimary() { + return primary; + } protected boolean fileExists() { return file.exists(); diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index b5668581573..85f70bd777f 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -134,7 +134,8 @@ 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))); + File file = new File(folder, filename); + addCode(new SketchCode(file, file.equals(primaryFile))); } else { System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), filename)); } From 71982ab3c9df809f1c02820255ce2c4dbec86be3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 18:57:57 +0100 Subject: [PATCH 13/64] Simplify `SketchData.removeCode()` and `indexOfCode()` These used to iterate over the list of SketchCodes to find the right one, and if so, let the List do the same again to remove it or find the index. This can be simplified to just let list take care of things instead. Technically, there is a small difference, since `List.remove()` and `List.indexOf()` will check using `equals()`, while the original code used `==`, but these should be effectively the same here. Also, the original code first used `==` to see if the object was present and then let List find it again using `equals()`, so that was a bit inconsistent anyway. --- arduino-core/src/processing/app/SketchData.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index 85f70bd777f..a01659d1617 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -228,21 +228,12 @@ public SketchCode getCode(int i) { } protected void removeCode(SketchCode which) { - for (SketchCode code : codes) { - if (code == which) { - codes.remove(code); - return; - } - } - System.err.println("removeCode: internal error.. could not find code"); + if (!codes.remove(which)) + System.err.println("removeCode: internal error.. could not find code"); } public int indexOfCode(SketchCode who) { - for (SketchCode code : codes) { - if (code == who) - return codes.indexOf(code); - } - return -1; + return codes.indexOf(who); } public String getName() { From a02cd4157aa47ef2581b551b9970985e5f15a933 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 19:04:18 +0100 Subject: [PATCH 14/64] Simplify sorting in SketchData Instead of manually sorting the primary file at the start, and fiddling to keep it there during resorting, this just modifies the sorting comparator used to sort any primary files at the start. This is slightly more generic than needed, also supporting multiple primary files, to at least not break the Comparator preconditions when for some reason there are multiple primary files. --- .../src/processing/app/SketchData.java | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index a01659d1617..02c87755dbc 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -45,6 +45,10 @@ public class SketchData { private static final Comparator CODE_DOCS_COMPARATOR = new Comparator() { @Override public int compare(SketchCode x, SketchCode y) { + if (x.isPrimary() && !y.isPrimary()) + return -1; + if (y.isPrimary() && !x.isPrimary()) + return 1; return x.getFileName().compareTo(y.getFileName()); } }; @@ -146,16 +150,6 @@ protected void load() throws IOException { if (getCodeCount() == 0) throw new IOException(tr("No valid code files found")); - // move the main class to the first tab - // start at 1, if it's at zero, don't bother - for (SketchCode code : getCodes()) { - //if (code[i].file.getName().equals(mainFilename)) { - if (code.getFile().equals(primaryFile)) { - moveCodeToFront(code); - break; - } - } - // sort the entries at the top sortCode(); } @@ -201,11 +195,6 @@ public void addCode(SketchCode sketchCode) { codes.add(sketchCode); } - public void moveCodeToFront(SketchCode codeDoc) { - codes.remove(codeDoc); - codes.add(0, codeDoc); - } - protected void replaceCode(SketchCode newCode) { for (SketchCode code : codes) { if (code.getFileName().equals(newCode.getFileName())) { @@ -216,11 +205,7 @@ protected void replaceCode(SketchCode newCode) { } protected void sortCode() { - if (codes.size() < 2) - return; - SketchCode first = codes.remove(0); Collections.sort(codes, CODE_DOCS_COMPARATOR); - codes.add(0, first); } public SketchCode getCode(int i) { From 4fb35038d21502d79b4b296370f9535cd28ac5ed Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 8 Dec 2015 19:44:57 +0100 Subject: [PATCH 15/64] Print errors while reloading externally edited files These exceptions were silently dropped, which is pretty much never a good idea. --- app/src/processing/app/Base.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 2f9730656c9..e8577c93d83 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -616,7 +616,7 @@ protected void handleActivated(Editor whichEditor) { activeEditor.getCurrentTab().getTextArea().setCaretPosition(previousCaretPosition); } } catch (IOException e) { - // noop + System.err.println(e); } } From 7e3c25137718d30eedd6fed886b62b692e5204a4 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 9 Dec 2015 09:27:25 +0100 Subject: [PATCH 16/64] Clean up sketch loading Previously, the Sketch constructor called its `load()` function, which called the `SketchData.load()` function to load files and then `Editor.sketchLoaded()` to initialize the GUI with the loaded files. When external editing was enabled, `Sketch.load()` was called again when activating the Arduino app, to reload the entire sketch. With this commit, the `Sketch.load()` function is removed, and `SketchData.load()` is called from the SketchData constructor. Instead of Sketch calling `Editor.sketchLoaded()`, that method is renamed to `createTabs()` and called by `Editor.HandleOpenInternal()` directly after creating the Sketch object. Handling of external editor mode has also changed. When the Arduino application is activated, instead of fully reloading the sketch (through the now-absent `Sketch.load()` method), the new `SketchData.reload()` method is called to reload the list of files in the sketch. If it changed, all tabs are re-created. If not, only the current tab is reloaded. When the user switches from one tab to another, that tab is also reloaded. This ensures that the visible tab is always up-to-date, without needlessly reloading all tabs all the time. When external editing mode is enabled or disabled, all tabs are reloaded too, to make sure they are up-to-date. When re-creating all tabs, no attempt is made to preserve the currently selected tab. Since adding or removing files happens rarely, this should not be a problem. When files are changed, the currently selected tab is implicitly preserved (because the tab is reloaded, not recreated). The caret (and thus scroll) position is preserved by temporarily changing the caret update policy, so the caret does not move while the text is swapped out. This happens in `EditorTab.setText()` now, so other callers can also profit from it. To support checking for a changed list of files in `SketchData.reload()`, a `SketchCode.equals()` method is added, that just checks if the filenames are equal. Additionally, the loading of the file list for a sketch has now moved from `SketchData.load()` to `SketchData.listSketchFiles()`, so `reload()` can also use it. At the same time, this loading is greatly simplified by using a sorted Set and `FileUtils.listFiles()`. In external editor mode, to ensure that during compilation the version from disk is always used instead of the in-memory version, EditorTab detaches itself from its SketchCode, so SketchCode has no access to the (possibly outdated) in-memory contents of the file. --- app/src/processing/app/Base.java | 10 +- app/src/processing/app/Editor.java | 11 +- app/src/processing/app/EditorTab.java | 79 +++++++++++--- app/src/processing/app/Sketch.java | 55 +--------- .../src/processing/app/BaseNoGui.java | 6 +- .../src/processing/app/SketchCode.java | 6 +- .../src/processing/app/SketchData.java | 101 +++++++----------- 7 files changed, 129 insertions(+), 139 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index e8577c93d83..94c2afa2405 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -610,11 +610,11 @@ protected void handleActivated(Editor whichEditor) { activeEditor.rebuildRecentSketchesMenu(); if (PreferencesData.getBoolean("editor.external")) { try { - int previousCaretPosition = activeEditor.getCurrentTab().getTextArea().getCaretPosition(); - activeEditor.getSketch().load(true); - if (previousCaretPosition < activeEditor.getCurrentTab().getText().length()) { - activeEditor.getCurrentTab().getTextArea().setCaretPosition(previousCaretPosition); - } + // If the list of files on disk changed, recreate the tabs for them + if (activeEditor.getSketch().reload()) + activeEditor.createTabs(); + else // Let the current tab know it was activated, so it can reload + activeEditor.getCurrentTab().activated(); } catch (IOException e) { System.err.println(e); } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index d6425978c50..f8e9485f9ab 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1611,6 +1611,7 @@ public void selectTab(final int index) { redoAction.updateRedoState(); updateTitle(); header.rebuild(); + getCurrentTab().activated(); // This must be run in the GUI thread SwingUtilities.invokeLater(() -> { @@ -1662,7 +1663,11 @@ public int findTabIndex(final String name) { return -1; } - public void sketchLoaded(Sketch sketch) { + /** + * Create tabs for each of the current sketch's files, removing any existing + * tabs. + */ + public void createTabs() { tabs.clear(); currentTabIndex = -1; tabs.ensureCapacity(sketch.getCodeCount()); @@ -1674,6 +1679,7 @@ public void sketchLoaded(Sketch sketch) { System.err.println(e); } } + selectTab(0); } /** @@ -1988,9 +1994,8 @@ protected boolean handleOpenInternal(File sketchFile) { Base.showWarning(tr("Error"), tr("Could not create the sketch."), e); return false; } + createTabs(); - header.rebuild(); - updateTitle(); // Disable untitled setting from previous document, if any untitled = false; diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 7252d9d9735..c8e60d40272 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -46,6 +46,7 @@ import javax.swing.text.BadLocationException; import javax.swing.text.PlainDocument; import javax.swing.undo.UndoManager; +import javax.swing.text.DefaultCaret; import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit; @@ -69,6 +70,8 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage { protected RTextScrollPane scrollPane; protected SketchCode code; protected boolean modified; + /** Is external editing mode currently enabled? */ + protected boolean external; /** * Create a new EditorTab @@ -100,12 +103,12 @@ public EditorTab(Editor editor, SketchCode code, String contents) RSyntaxDocument document = createDocument(contents); this.textarea = createTextArea(document); this.scrollPane = createScrollPane(this.textarea); + code.setStorage(this); applyPreferences(); add(this.scrollPane, BorderLayout.CENTER); UndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor); ((RSyntaxDocument)textarea.getDocument()).addUndoableEditListener(undo); - code.setStorage(this); } private RSyntaxDocument createDocument(String contents) { @@ -277,19 +280,29 @@ public void applyPreferences() { scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding")); scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers")); - // apply the setting for 'use external editor' - if (PreferencesData.getBoolean("editor.external")) { - // disable line highlight and turn off the caret when disabling - textarea.setBackground(Theme.getColor("editor.external.bgcolor")); - textarea.setHighlightCurrentLine(false); - textarea.setEditable(false); - - } else { - textarea.setBackground(Theme.getColor("editor.bgcolor")); - textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight")); - textarea.setEditable(true); + // apply the setting for 'use external editor', but only if it changed + if (external != PreferencesData.getBoolean("editor.external")) { + external = !external; + if (external) { + // disable line highlight and turn off the caret when disabling + textarea.setBackground(Theme.getColor("editor.external.bgcolor")); + textarea.setHighlightCurrentLine(false); + textarea.setEditable(false); + // Detach from the code, since we are no longer the authoritative source + // for file contents. + code.setStorage(null); + // Reload, in case the file contents already changed. + reload(); + } else { + textarea.setBackground(Theme.getColor("editor.bgcolor")); + textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight")); + textarea.setEditable(true); + code.setStorage(this); + // Reload once just before disabling external mode, to ensure we have + // the latest contents. + reload(); + } } - // apply changes to the font size for the editor textarea.setFont(PreferencesData.getFont("editor.font")); } @@ -302,7 +315,34 @@ public void updateKeywords(PdeKeywords keywords) { document.setTokenMakerFactory(new ArduinoTokenMakerFactory(keywords)); document.setSyntaxStyle(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS); } - + + /** + * Called when this tab is made the current one, or when it is the current one + * and the window is activated. + */ + public void activated() { + // When external editing is enabled, reload the text whenever we get activated. + if (external) { + reload(); + } + } + + /** + * Reload the contents of our file. + */ + private void reload() { + String text; + try { + text = code.load(); + } catch (IOException e) { + System.err.println(I18n.format("Warning: Failed to reload file: \"{0}\"", + code.getFileName())); + return; + } + setText(text); + setModified(false); + } + /** * Get the TextArea object for use (not recommended). This should only * be used in obscure cases that really need to hack the internals of the @@ -339,7 +379,18 @@ public String getText() { * Replace the entire contents of this tab. */ public void setText(String what) { + // Set the caret update policy to NEVER_UPDATE while completely replacing + // the current text. Normally, the caret tracks inserts and deletions, but + // replacing the entire text will always make the caret end up at the end, + // which isn't really useful. With NEVER_UPDATE, the caret will just keep + // its absolute position (number of characters from the start), which isn't + // always perfect, but the best we can do without making a diff of the old + // and new text and some guesswork. + DefaultCaret caret = (DefaultCaret) textarea.getCaret(); + int policy = caret.getUpdatePolicy(); + caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); textarea.setText(what); + caret.setUpdatePolicy(policy); } /** diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 7cf54e780e8..42245ebdb36 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -66,42 +66,8 @@ public class Sketch { public Sketch(Editor _editor, File file) throws IOException { editor = _editor; data = new SketchData(file); - load(); } - - /** - * 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. - */ - private void load() throws IOException { - load(false); - } - - protected void load(boolean forceUpdate) throws IOException { - data.load(); - - // set the main file to be the current tab - if (editor != null) { - int current = editor.getCurrentTabIndex(); - if (current < 0) - current = 0; - editor.sketchLoaded(this); - editor.selectTab(current); - } - } - - private boolean renamingCode; /** @@ -944,24 +910,6 @@ private void importLibrary(File jarPath) throws IOException { public void prepare() throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); - - // TODO record history here - //current.history.record(program, SketchHistory.RUN); - - // if an external editor is being used, need to grab the - // latest version of the code from the file. - if (PreferencesData.getBoolean("editor.external")) { - // history gets screwed by the open.. - //String historySaved = history.lastRecorded; - //handleOpen(sketch); - //history.lastRecorded = historySaved; - - // nuke previous files and settings, just get things loaded - load(true); - } - -// // handle preprocessing the main file's code -// return build(tempBuildFolder.getAbsolutePath()); } /** @@ -1280,6 +1228,9 @@ public boolean isUntitled() { return editor.untitled; } + public boolean reload() throws IOException { + return data.reload(); + } // ................................................................. diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index 11ec4dd46a1..feefc7ac101 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -466,14 +466,13 @@ static public void init(String[] args) throws Exception { boolean success = false; try { // Editor constructor loads the sketch with handleOpenInternal() that - // creates a new Sketch that, in trun, calls load() inside its constructor + // creates a new Sketch that, in turn, builds a SketchData + // inside its constructor. // This translates here as: // SketchData data = new SketchData(file); // File tempBuildFolder = getBuildFolder(); - // data.load(); SketchData data = new SketchData(absoluteFile(parser.getFilenames().get(0))); File tempBuildFolder = getBuildFolder(data); - data.load(); // Sketch.exportApplet() // - calls Sketch.prepare() that calls Sketch.ensureExistence() @@ -520,7 +519,6 @@ static public void init(String[] args) throws Exception { // data.load(); SketchData data = new SketchData(absoluteFile(path)); File tempBuildFolder = getBuildFolder(data); - data.load(); // Sketch.prepare() calls Sketch.ensureExistence() // Sketch.build(verbose) calls Sketch.ensureExistence() and set progressListener and, finally, calls Compiler.build() diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index 06d2d3267ae..dac19456800 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -91,7 +91,8 @@ public SketchCode(File file, boolean primary) { /** * Set an in-memory storage for this file's text, that will be queried - * on compile, save, and whenever the text is needed. + * on compile, save, and whenever the text is needed. null can be + * passed to detach any attached storage. */ public void setStorage(TextStorage text) { this.storage = text; @@ -201,6 +202,9 @@ public boolean isModified() { return false; } + public boolean equals(Object o) { + return (o instanceof SketchCode) && file.equals(((SketchCode) o).file); + } /** * Load this piece of code from a file and return the contents. This diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index 02c87755dbc..bfd44168c5c 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -6,6 +6,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import processing.app.helpers.FileUtils; + import static processing.app.I18n.tr; public class SketchData { @@ -53,7 +55,14 @@ public int compare(SketchCode x, SketchCode y) { } }; - SketchData(File file) { + /** + * Create a new SketchData object, and looks at the sketch directory + * on disk to get populate the list of files in this sketch. + * + * @param file + * The primary file for this sketch. + */ + SketchData(File file) throws IOException { primaryFile = file; // get the name of the sketch by chopping .pde or .java @@ -63,7 +72,9 @@ public int compare(SketchCode x, SketchCode y) { name = mainFilename.substring(0, mainFilename.length() - suffixLength); folder = new File(file.getParent()); - //System.out.println("sketch dir is " + folder); + codeFolder = new File(folder, "code"); + dataFolder = new File(folder, "data"); + codes = listSketchFiles(); } static public File checkSketchFile(File file) { @@ -90,68 +101,42 @@ static public File checkSketchFile(File file) { } /** - * 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. + * Reload the list of files. This checks the sketch directory on disk, + * to see if any files were added or removed. This does *not* check + * the contents of the files, just their presence. + * + * @return true when the list of files was changed, false when it was + * not. */ - protected void load() throws IOException { - codeFolder = new File(folder, "code"); - dataFolder = new File(folder, "data"); - - // get list of files in the sketch folder - String list[] = folder.list(); - if (list == null) { - throw new IOException("Unable to list files from " + folder); + public boolean reload() throws IOException { + List reloaded = listSketchFiles(); + if (!reloaded.equals(codes)) { + codes = reloaded; + return true; } + return false; + } - // reset these because load() may be called after an - // external editor event. (fix for 0099) -// codeDocs = new SketchCodeDoc[list.length]; - clearCodeDocs(); -// data.setCodeDocs(codeDocs); - - for (String filename : list) { - // Ignoring the dot prefix files is especially important to avoid files - // with the ._ prefix on Mac OS X. (You'll see this with Mac files on - // non-HFS drives, i.e. a thumb drive formatted FAT32.) - if (filename.startsWith(".")) continue; - - // Don't let some wacko name a directory blah.pde or bling.java. - if (new File(folder, filename).isDirectory()) continue; - - // figure out the name without any extension - String base = filename; - // now strip off the .pde and .java extensions - for (String extension : EXTENSIONS) { - if (base.toLowerCase().endsWith("." + extension)) { - base = base.substring(0, base.length() - (extension.length() + 1)); - - // 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)) { - File file = new File(folder, filename); - addCode(new SketchCode(file, file.equals(primaryFile))); - } else { - System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), filename)); - } - } + /** + * Scan this sketch's directory for files that should be loaded as + * part of this sketch. Doesn't modify this SketchData instance, just + * returns a filtered and sorted list of File objects ready to be + * passed to the SketchCode constructor. + */ + private List listSketchFiles() throws IOException { + Set result = new TreeSet<>(CODE_DOCS_COMPARATOR); + for (File file : FileUtils.listFiles(folder, false, EXTENSIONS)) { + if (BaseNoGui.isSanitaryName(file.getName())) { + result.add(new SketchCode(file, file.equals(primaryFile))); + } else { + System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), file.getName())); } } - if (getCodeCount() == 0) + if (result.size() == 0) throw new IOException(tr("No valid code files found")); - // sort the entries at the top - sortCode(); + return new ArrayList<>(result); } public void save() throws IOException { @@ -225,10 +210,6 @@ public String getName() { return name; } - public void clearCodeDocs() { - codes.clear(); - } - public File getFolder() { return folder; } From 4976817942f4544038536ba4d7807bd55ed553de Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 9 Dec 2015 11:18:38 +0100 Subject: [PATCH 17/64] Do not show invalid sketch filename warnings on reload With this commit, any warnings about invalid sketch filenames are not shown when the sketch is reloaded. This reloading happens whenever the IDE window is focused, so re-logging warnings all the time isn't really helpful, so this hides them. --- arduino-core/src/processing/app/SketchData.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index bfd44168c5c..02309707e87 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -74,7 +74,7 @@ public int compare(SketchCode x, SketchCode y) { folder = new File(file.getParent()); codeFolder = new File(folder, "code"); dataFolder = new File(folder, "data"); - codes = listSketchFiles(); + codes = listSketchFiles(true); } static public File checkSketchFile(File file) { @@ -109,7 +109,7 @@ static public File checkSketchFile(File file) { * not. */ public boolean reload() throws IOException { - List reloaded = listSketchFiles(); + List reloaded = listSketchFiles(false); if (!reloaded.equals(codes)) { codes = reloaded; return true; @@ -122,13 +122,16 @@ public boolean reload() throws IOException { * part of this sketch. Doesn't modify this SketchData instance, just * returns a filtered and sorted list of File objects ready to be * passed to the SketchCode constructor. + * + * @param showWarnings + * When true, any invalid filenames will show a warning. */ - private List listSketchFiles() throws IOException { + private List listSketchFiles(boolean showWarnings) throws IOException { Set result = new TreeSet<>(CODE_DOCS_COMPARATOR); for (File file : FileUtils.listFiles(folder, false, EXTENSIONS)) { if (BaseNoGui.isSanitaryName(file.getName())) { result.add(new SketchCode(file, file.equals(primaryFile))); - } else { + } else if (showWarnings) { System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), file.getName())); } } From f7d40a0ab68dd5bc965ef3308efc7bc73eabda0c Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 11 Dec 2015 19:14:09 +0100 Subject: [PATCH 18/64] Merge `SketchData.sortCodes()` into `addCode()` By now, all calls to `addCode()` were followed by a call to `sortCodes()`, and it seems like a task for SketchData to keep its list sorted. Previously, this separation made some sense, since `addCode()` was also used while loading a sketch, and you would only want to sort once. Now, sketch loading uses a SortedSet, so this is no longer a requirement. --- app/src/processing/app/Sketch.java | 4 ---- arduino-core/src/processing/app/SketchData.java | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 42245ebdb36..3daecc3e888 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -375,9 +375,6 @@ protected void nameCode(String newName) { data.addCode(code); } - // sort the entries - data.sortCode(); - // set the new guy as current editor.selectTab(editor.findTabIndex(newName)); @@ -829,7 +826,6 @@ public boolean addFile(File sourceFile) { } else { ensureExistence(); data.addCode(newCode); - data.sortCode(); } editor.selectTab(editor.findTabIndex(filename)); } diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index 02309707e87..61a41d72a59 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -181,6 +181,7 @@ public String getMainFilePath() { public void addCode(SketchCode sketchCode) { codes.add(sketchCode); + Collections.sort(codes, CODE_DOCS_COMPARATOR); } protected void replaceCode(SketchCode newCode) { @@ -192,10 +193,6 @@ protected void replaceCode(SketchCode newCode) { } } - protected void sortCode() { - Collections.sort(codes, CODE_DOCS_COMPARATOR); - } - public SketchCode getCode(int i) { return codes.get(i); } From e852b600c7d5a26c9b4ac14c39bd06f75311a437 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 9 Dec 2015 12:17:17 +0100 Subject: [PATCH 19/64] Remove UndoManager stuff from AStyle The only change that happens is a single `setText()` call, which already results in a single undo action. --- app/src/cc/arduino/packages/formatter/AStyle.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index 5550223f7c8..84d4b5793a3 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -91,10 +91,8 @@ public void run() { int line = getLineOfOffset(textArea); int lineOffset = getLineOffset(textArea, line); - textArea.getUndoManager().beginInternalAtomicEdit(); editor.removeAllLineHighlights(); editor.getCurrentTab().setText(formattedText); - textArea.getUndoManager().endInternalAtomicEdit(); if (line != -1 && lineOffset != -1) { try { From 14f214b2e4fd1edac64e3da108914aa2cd68e13e Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 9 Dec 2015 12:21:56 +0100 Subject: [PATCH 20/64] Remove all highlights in `EditorTab.setText()` When completely replacing all text, all highlights will end up at the start of the file. Since keeping them at the right place is tricky (even impossible in some circumstances), just remove them now. This already happened in the autoformat code, so that part can be removed. --- app/src/cc/arduino/packages/formatter/AStyle.java | 1 - app/src/processing/app/EditorTab.java | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index 84d4b5793a3..a0d75d6be32 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -91,7 +91,6 @@ public void run() { int line = getLineOfOffset(textArea); int lineOffset = getLineOffset(textArea, line); - editor.removeAllLineHighlights(); editor.getCurrentTab().setText(formattedText); if (line != -1 && lineOffset != -1) { diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index c8e60d40272..9ff1a585e4e 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -379,6 +379,9 @@ public String getText() { * Replace the entire contents of this tab. */ public void setText(String what) { + // Remove all highlights, since these will all end up at the start of the + // text otherwise. Preserving them is tricky, so better just remove them. + textarea.removeAllLineHighlights(); // Set the caret update policy to NEVER_UPDATE while completely replacing // the current text. Normally, the caret tracks inserts and deletions, but // replacing the entire text will always make the caret end up at the end, From 8c8207c4c3634718bb71579527bc3890b6b48f9c Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 9 Dec 2015 12:27:00 +0100 Subject: [PATCH 21/64] Remove code that preserves caret position during auto format `EditorTab.setText()` now already preserves the caret position. The code used during auto-format tried a bit harder to preserve the position correctly, and probably worked better in a few specific cases, but for most cases they would both end up approximating the caret position anyway. To make the code simpler, better just stick to the simpler approach. --- .../cc/arduino/packages/formatter/AStyle.java | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index a0d75d6be32..70b6717ff66 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -33,10 +33,8 @@ import processing.app.BaseNoGui; import processing.app.Editor; import processing.app.helpers.FileUtils; -import processing.app.syntax.SketchTextArea; import processing.app.tools.Tool; -import javax.swing.text.BadLocationException; import java.io.File; import java.io.IOException; @@ -86,53 +84,12 @@ public void run() { return; } - SketchTextArea textArea = editor.getCurrentTab().getTextArea(); - - int line = getLineOfOffset(textArea); - int lineOffset = getLineOffset(textArea, line); - editor.getCurrentTab().setText(formattedText); - if (line != -1 && lineOffset != -1) { - try { - setCaretPosition(textArea, line, lineOffset); - } catch (BadLocationException e) { - e.printStackTrace(); - } - } - // mark as finished editor.statusNotice(tr("Auto Format finished.")); } - private void setCaretPosition(SketchTextArea textArea, int line, int lineOffset) throws BadLocationException { - int caretPosition; - if (line < textArea.getLineCount()) { - caretPosition = Math.min(textArea.getLineStartOffset(line) + lineOffset, textArea.getLineEndOffset(line) - 1); - } else { - caretPosition = textArea.getText().length() - 1; - } - textArea.setCaretPosition(caretPosition); - } - - private int getLineOffset(SketchTextArea textArea, int line) { - try { - return textArea.getCaretPosition() - textArea.getLineStartOffset(line); - } catch (BadLocationException e) { - e.printStackTrace(); - } - return -1; - } - - private int getLineOfOffset(SketchTextArea textArea) { - try { - return textArea.getLineOfOffset(textArea.getCaretPosition()); - } catch (BadLocationException e) { - e.printStackTrace(); - } - return -1; - } - @Override public String getMenuTitle() { return tr("Auto Format"); From 1db8bf60b1809f93d64ca1d5e7a23676d234bda2 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 11 Dec 2015 17:14:45 +0100 Subject: [PATCH 22/64] Replace `requestFocus()` by `requestFocusInWindow()` where applicable The former gives focus to the window in which a component is present, while the latter only changes the focus within the current window (not focusing the window itself if it is not focused yet). Java documentation recommends changing `requestFocusInWindow()` where possible, due to some platform-dependent behaviour in `requestFocus()`. When focusing the serial monitor and plotter, `requestFocus()` is still used, since then the focused window *should* change. --- app/src/processing/app/Editor.java | 2 +- app/src/processing/app/EditorStatus.java | 2 +- app/src/processing/app/EditorTab.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index f8e9485f9ab..e24e251384c 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1617,7 +1617,7 @@ public void selectTab(final int index) { SwingUtilities.invokeLater(() -> { codePanel.removeAll(); codePanel.add(tabs.get(index), BorderLayout.CENTER); - tabs.get(index).requestFocus(); // get the caret blinking + tabs.get(index).requestFocusInWindow(); // get the caret blinking // For some reason, these are needed. Revalidate says it should be // automatically called when components are added or removed, but without // it, the component switched to is not displayed. repaint() is needed to diff --git a/app/src/processing/app/EditorStatus.java b/app/src/processing/app/EditorStatus.java index b6551c7fa00..62187c77ad2 100644 --- a/app/src/processing/app/EditorStatus.java +++ b/app/src/processing/app/EditorStatus.java @@ -143,7 +143,7 @@ public void edit(String message, String dflt) { editField.setVisible(true); editField.setText(dflt); editField.selectAll(); - editField.requestFocus(); + editField.requestFocusInWindow(); repaint(); } diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 9ff1a585e4e..be2d4ae33d2 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -575,9 +575,9 @@ public String getCurrentKeyword() { } @Override - public void requestFocus() { + public boolean requestFocusInWindow() { /** If focus is requested, focus the textarea instead. */ - textarea.requestFocus(); + return textarea.requestFocusInWindow(); } } \ No newline at end of file From 5e35929552bbaa7e6191fa1597bd7a1ffc122450 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 11 Dec 2015 17:45:38 +0100 Subject: [PATCH 23/64] Remove applet.html handling This was a remnant of Processing, this file has no special meaning for an Arduino sketch, so this code can just be removed. --- app/src/processing/app/Sketch.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 3daecc3e888..074281614b3 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -649,14 +649,6 @@ protected boolean saveAs() throws IOException { Base.copyDir(data.getCodeFolder(), newCodeFolder); } - // copy custom applet.html file if one exists - // http://dev.processing.org/bugs/show_bug.cgi?id=485 - File customHtml = new File(data.getFolder(), "applet.html"); - if (customHtml.exists()) { - File newHtml = new File(newFolder, "applet.html"); - Base.copyFile(customHtml, newHtml); - } - // save the main tab with its new name File newFile = new File(newFolder, newName + ".ino"); data.getCode(0).saveAs(newFile); From 4ef56995c61ac0f40866e14004dc6bf6dd1788d3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 11 Dec 2015 19:18:19 +0100 Subject: [PATCH 24/64] Remove support for a "code" folder in sketches When adding a file to a sketch (using drag and drop, or the Sketch -> Add file... menu item), .o, .a and .so files would be saved into a "code" subdirectory of the sketch. This seems to be a remnant of processing, where also .dll and .jar files could be added to a sketch to be used. In the Arduino IDE, these code files serve no special purpose, and are not treated specially, so it makes no sense to keep this code around. One implication of this is that when "save as" is used, a "code" subdirectory is no longer copied, which might affect people using this "code" subdirectory for other purposes. Similarly, there is support for a "data" subdirectory, in which all other files (that are not sketch source files) are stored, and which is also copied on "save as". Support for this folder is kept intact, since this appears occasionally used (the ESP8266 project uses it to store and upload additional data files, for example). This change was discussed on the mailing list in the "Anyone using "data" and "code" subdirectories in sketches?" thread: https://groups.google.com/a/arduino.cc/forum/#!msg/developers/zPlraPq55ho/ejrLqITnAgAJ --- app/src/processing/app/Sketch.java | 48 ++++--------------- .../src/processing/app/SketchData.java | 10 ---- 2 files changed, 9 insertions(+), 49 deletions(-) diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 074281614b3..f2c883d099e 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -643,12 +643,6 @@ protected boolean saveAs() throws IOException { Base.copyDir(data.getDataFolder(), newDataFolder); } - // re-copy the code folder - if (data.getCodeFolder().exists()) { - File newCodeFolder = new File(newFolder, "code"); - Base.copyDir(data.getCodeFolder(), newCodeFolder); - } - // save the main tab with its new name File newFile = new File(newFolder, newName + ".ino"); data.getCode(0).saveAs(newFile); @@ -730,29 +724,17 @@ public boolean addFile(File sourceFile) { String codeExtension = null; boolean replacement = false; - // if the file appears to be code related, drop it - // into the code folder, instead of the data folder - if (filename.toLowerCase().endsWith(".o") || - filename.toLowerCase().endsWith(".a") || - filename.toLowerCase().endsWith(".so")) { - - //if (!codeFolder.exists()) codeFolder.mkdirs(); - prepareCodeFolder(); - destFile = new File(data.getCodeFolder(), filename); - - } else { - for (String extension : SketchData.EXTENSIONS) { - String lower = filename.toLowerCase(); - if (lower.endsWith("." + extension)) { - destFile = new File(data.getFolder(), filename); - codeExtension = extension; - } - } - if (codeExtension == null) { - prepareDataFolder(); - destFile = new File(data.getDataFolder(), filename); + for (String extension : SketchData.EXTENSIONS) { + String lower = filename.toLowerCase(); + if (lower.endsWith("." + extension)) { + destFile = new File(data.getFolder(), filename); + codeExtension = extension; } } + if (codeExtension == null) { + prepareDataFolder(); + destFile = new File(data.getDataFolder(), filename); + } // check whether this file already exists if (destFile.exists()) { @@ -1175,18 +1157,6 @@ private File prepareDataFolder() { } - /** - * 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. - */ - private File prepareCodeFolder() { - if (!data.getCodeFolder().exists()) { - data.getCodeFolder().mkdirs(); - } - return data.getCodeFolder(); - } - - public SketchCode[] getCodes() { return data.getCodes(); } diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/SketchData.java index 61a41d72a59..3765bb1572f 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/SketchData.java @@ -31,11 +31,6 @@ public class SketchData { */ private File dataFolder; - /** - * code folder location for this sketch (may not exist yet) - */ - private File codeFolder; - /** * Name of sketch, which is the name of main file (without .pde or .java * extension) @@ -72,7 +67,6 @@ public int compare(SketchCode x, SketchCode y) { name = mainFilename.substring(0, mainFilename.length() - suffixLength); folder = new File(file.getParent()); - codeFolder = new File(folder, "code"); dataFolder = new File(folder, "data"); codes = listSketchFiles(true); } @@ -217,8 +211,4 @@ public File getFolder() { public File getDataFolder() { return dataFolder; } - - public File getCodeFolder() { - return codeFolder; - } } From d056f890ed6478a86225bf9baace5345f591e9bb Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 16 Dec 2015 17:22:23 +0100 Subject: [PATCH 25/64] Delete `Sketch.prepare()` The only remaining thing that the method did was call `ensureExistence()`. However, a call the `prepare()` was always followed by a call to `build()`, which already calls `ensureExistence()`, so `prepare()` didn't have any remaining value. --- app/src/processing/app/Editor.java | 1 - app/src/processing/app/Sketch.java | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index e24e251384c..9a774ae2c59 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1771,7 +1771,6 @@ public BuildHandler(boolean verbose, boolean saveHex) { public void run() { try { removeAllLineHighlights(); - sketch.prepare(); sketch.build(verbose, saveHex); statusNotice(tr("Done compiling.")); } catch (PreferencesMapException e) { diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index f2c883d099e..888275d455a 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -873,15 +873,6 @@ private void importLibrary(File jarPath) throws IOException { */ //protected String compile() throws RunnerException { - /** - * When running from the editor, take care of preparations before running - * the build. - */ - public void prepare() throws IOException { - // make sure the user didn't hide the sketch folder - ensureExistence(); - } - /** * Run the build inside the temporary build folder. * @return null if compilation failed, main class name if not @@ -949,8 +940,6 @@ protected boolean exportApplet(boolean usingProgrammer) throws Exception { private boolean exportApplet(String appletPath, boolean usingProgrammer) throws Exception { - prepare(); - // build the sketch editor.status.progressNotice(tr("Compiling sketch...")); String foundName = build(appletPath, false, false); From 1eafdde8f07b06b3ab2ce30f84dc60a627d42e02 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 11:05:16 +0100 Subject: [PATCH 26/64] Rename Sketch and SketchData classes Sketch is now called SketchController, since it didn't really represent a sketch, but just handled the GUI-related stuff for a given sketch (note that it is not strictly a controller in the MVC-sense, but it does have a similar function). SketchData more accurately represented the actual sketch, so it is now called Sketch. Below, the new names are used. Editor now keeps both a current Sketch and SketchController object, and the Sketch object is created by Editor and passed to SketchController, instead passing a File and letting SketchController create the Sketch. Wherever possible, code now uses the Sketch directly (or indirectly, through the new `SketchController.getSketch()`) and the accessors in SketchController that merely forwarded to Sketch have been removed. There are few things that now live in SketchController but should be moved to Sketch (`isModified()`, `isUntitled()`), so some of the code still has a dependency on SketchController that should be removed later. This commit mostly renames classes, methods and variables, it should not change the behaviour in any way. --- app/src/processing/app/Base.java | 14 +- app/src/processing/app/Editor.java | 65 +++--- app/src/processing/app/EditorHeader.java | 8 +- app/src/processing/app/EditorStatus.java | 4 +- app/src/processing/app/EditorTab.java | 9 +- .../{Sketch.java => SketchController.java} | 188 ++++++------------ .../processing/app/macosx/ThinkDifferent.java | 2 +- app/src/processing/app/tools/Archiver.java | 6 +- app/src/processing/app/tools/FixEncoding.java | 6 +- arduino-core/src/cc/arduino/Compiler.java | 6 +- .../src/cc/arduino/UploaderUtils.java | 4 +- .../src/processing/app/BaseNoGui.java | 6 +- .../app/{SketchData.java => Sketch.java} | 7 +- 13 files changed, 138 insertions(+), 187 deletions(-) rename app/src/processing/app/{Sketch.java => SketchController.java} (89%) rename arduino-core/src/processing/app/{SketchData.java => Sketch.java} (97%) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 94c2afa2405..d61d3322075 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -570,7 +570,7 @@ protected void storeSketches() { String path = editor.getSketch().getMainFilePath(); // In case of a crash, save untitled sketches if they contain changes. // (Added this for release 0158, may not be a good idea.) - if (path.startsWith(untitledPath) && !editor.getSketch().isModified()) { + if (path.startsWith(untitledPath) && !editor.getSketchController().isModified()) { continue; } PreferencesData.set("last.sketch" + index + ".path", path); @@ -583,13 +583,13 @@ protected void storeSketches() { PreferencesData.setInteger("last.sketch.count", index); } - protected void storeRecentSketches(Sketch sketch) { + protected void storeRecentSketches(SketchController sketch) { if (sketch.isUntitled()) { return; } Set sketches = new LinkedHashSet(); - sketches.add(sketch.getMainFilePath()); + sketches.add(sketch.getSketch().getMainFilePath()); sketches.addAll(PreferencesData.getCollection("recent.sketches")); PreferencesData.setCollection("recent.sketches", sketches); @@ -876,7 +876,7 @@ protected Editor handleOpen(File file, int[] storedLocation, int[] defaultLocati Editor editor = new Editor(this, file, storedLocation, defaultLocation, BaseNoGui.getPlatform()); // Make sure that the sketch actually loaded - if (editor.getSketch() == null) { + if (editor.getSketchController() == null) { return null; // Just walk away quietly } @@ -888,7 +888,7 @@ protected Editor handleOpen(File file, int[] storedLocation, int[] defaultLocati // Store information on who's open and running // (in case there's a crash or something that can't be recovered) storeSketches(); - storeRecentSketches(editor.getSketch()); + storeRecentSketches(editor.getSketchController()); rebuildRecentSketchesMenuItems(); PreferencesData.save(); } @@ -1184,7 +1184,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent event) { UserLibrary l = (UserLibrary) getValue("library"); try { - activeEditor.getSketch().importLibrary(l); + activeEditor.getSketchController().importLibrary(l); } catch (IOException e) { showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e); } @@ -1719,7 +1719,7 @@ protected void addLibraries(JMenu menu, LibraryList libs) throws IOException { public void actionPerformed(ActionEvent event) { UserLibrary l = (UserLibrary) getValue("library"); try { - activeEditor.getSketch().importLibrary(l); + activeEditor.getSketchController().importLibrary(l); } catch (IOException e) { showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e); } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 9a774ae2c59..4e1f76a48d0 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -84,18 +84,18 @@ public class Editor extends JFrame implements RunnerListener { private ArrayList tabs = new ArrayList<>(); private int currentTabIndex = -1; - private static class ShouldSaveIfModified implements Predicate { + private static class ShouldSaveIfModified implements Predicate { @Override - public boolean test(Sketch sketch) { + public boolean test(SketchController sketch) { return PreferencesData.getBoolean("editor.save_on_verify") && sketch.isModified() && !sketch.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()); } } - private static class ShouldSaveReadOnly implements Predicate { + private static class ShouldSaveReadOnly implements Predicate { @Override - public boolean test(Sketch sketch) { + public boolean test(SketchController sketch) { return sketch.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()); } } @@ -155,6 +155,7 @@ public boolean test(Sketch sketch) { private JSplitPane splitPane; // currently opened program + SketchController sketchController; Sketch sketch; EditorLineStatus lineStatus; @@ -341,7 +342,7 @@ public void windowDeactivated(WindowEvent e) { // Open the document that was passed in boolean loaded = handleOpenInternal(file); - if (!loaded) sketch = null; + if (!loaded) sketchController = null; // System.out.println("t5"); @@ -372,7 +373,7 @@ public boolean importData(JComponent src, Transferable transferable) { List list = (List) transferable.getTransferData(DataFlavor.javaFileListFlavor); for (File file : list) { - if (sketch.addFile(file)) { + if (sketchController.addFile(file)) { successful++; } } @@ -390,7 +391,7 @@ public boolean importData(JComponent src, Transferable transferable) { } else if (piece.startsWith("file:/")) { path = piece.substring(5); } - if (sketch.addFile(new File(path))) { + if (sketchController.addFile(new File(path))) { successful++; } } @@ -701,7 +702,7 @@ public void actionPerformed(ActionEvent e) { item = newJMenuItemAlt(tr("Export compiled Binary"), 'S'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - if (new ShouldSaveReadOnly().test(sketch) && !handleSave(true)) { + if (new ShouldSaveReadOnly().test(sketchController) && !handleSave(true)) { System.out.println(tr("Export canceled, changes must first be saved.")); return; } @@ -739,7 +740,7 @@ public void actionPerformed(ActionEvent e) { item = new JMenuItem(tr("Add File...")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - sketch.handleAddFile(); + sketchController.handleAddFile(); } }); sketchMenu.add(item); @@ -1571,7 +1572,14 @@ private void resetHandlers() { /** - * Gets the current sketch object. + * Gets the current sketch controller. + */ + public SketchController getSketchController() { + return sketchController; + } + + /** + * Gets the current sketch. */ public Sketch getSketch() { return sketch; @@ -1728,9 +1736,9 @@ public void handleRun(final boolean verbose, Runnable verboseHandler, Runnable n handleRun(verbose, new ShouldSaveIfModified(), verboseHandler, nonVerboseHandler); } - private void handleRun(final boolean verbose, Predicate shouldSavePredicate, Runnable verboseHandler, Runnable nonVerboseHandler) { + private void handleRun(final boolean verbose, Predicate shouldSavePredicate, Runnable verboseHandler, Runnable nonVerboseHandler) { internalCloseRunner(); - if (shouldSavePredicate.test(sketch)) { + if (shouldSavePredicate.test(sketchController)) { handleSave(true); } toolbar.activateRun(); @@ -1771,7 +1779,7 @@ public BuildHandler(boolean verbose, boolean saveHex) { public void run() { try { removeAllLineHighlights(); - sketch.build(verbose, saveHex); + sketchController.build(verbose, saveHex); statusNotice(tr("Done compiling.")); } catch (PreferencesMapException e) { statusError(I18n.format( @@ -1838,14 +1846,15 @@ public void internalCloseRunner() { * @return false if canceling the close/quit operation */ protected boolean checkModified() { - if (!sketch.isModified()) return true; + if (!sketchController.isModified()) return true; // As of Processing 1.0.10, this always happens immediately. // http://dev.processing.org/bugs/show_bug.cgi?id=1456 toFront(); - String prompt = I18n.format(tr("Save changes to \"{0}\"? "), sketch.getName()); + String prompt = I18n.format(tr("Save changes to \"{0}\"? "), + sketch.getName()); if (!OSUtils.isMacOS()) { int result = @@ -1932,7 +1941,7 @@ protected boolean handleOpenInternal(File sketchFile) { // in a folder of the same name String fileName = sketchFile.getName(); - File file = SketchData.checkSketchFile(sketchFile); + File file = Sketch.checkSketchFile(sketchFile); if (file == null) { if (!fileName.endsWith(".ino") && !fileName.endsWith(".pde")) { @@ -1988,11 +1997,12 @@ protected boolean handleOpenInternal(File sketchFile) { } try { - sketch = new Sketch(this, file); + sketch = new Sketch(file); } catch (IOException e) { Base.showWarning(tr("Error"), tr("Could not create the sketch."), e); return false; } + sketchController = new SketchController(this, sketch); createTabs(); // Disable untitled setting from previous document, if any @@ -2003,12 +2013,13 @@ protected boolean handleOpenInternal(File sketchFile) { } private void updateTitle() { - if (sketch == null) { + if (sketchController == null) { return; } SketchCode current = getCurrentTab().getSketchCode(); if (sketch.getName().equals(current.getPrettyName())) { - setTitle(I18n.format(tr("{0} | Arduino {1}"), sketch.getName(), BaseNoGui.VERSION_NAME_LONG)); + setTitle(I18n.format(tr("{0} | Arduino {1}"), sketch.getName(), + BaseNoGui.VERSION_NAME_LONG)); } else { setTitle(I18n.format(tr("{0} - {1} | Arduino {2}"), sketch.getName(), current.getFileName(), BaseNoGui.VERSION_NAME_LONG)); @@ -2053,15 +2064,15 @@ private boolean handleSave2() { statusNotice(tr("Saving...")); boolean saved = false; try { - boolean wasReadOnly = sketch.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()); + boolean wasReadOnly = sketchController.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()); String previousMainFilePath = sketch.getMainFilePath(); - saved = sketch.save(); + saved = sketchController.save(); if (saved) { statusNotice(tr("Done Saving.")); if (wasReadOnly) { base.removeRecentSketchPath(previousMainFilePath); } - base.storeRecentSketches(sketch); + base.storeRecentSketches(sketchController); base.rebuildRecentSketchesMenuItems(); } else { statusEmpty(); @@ -2098,8 +2109,8 @@ public boolean handleSaveAs() { //public void run() { statusNotice(tr("Saving...")); try { - if (sketch.saveAs()) { - base.storeRecentSketches(sketch); + if (sketchController.saveAs()) { + base.storeRecentSketches(sketchController); base.rebuildRecentSketchesMenuItems(); statusNotice(tr("Done Saving.")); // Disabling this for 0125, instead rebuild the menu inside @@ -2166,7 +2177,7 @@ private boolean serialPrompt() { */ synchronized public void handleExport(final boolean usingProgrammer) { if (PreferencesData.getBoolean("editor.save_on_verify")) { - if (sketch.isModified() && !sketch.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) { + if (sketchController.isModified() && !sketchController.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) { handleSave(true); } } @@ -2192,7 +2203,7 @@ public void run() { uploading = true; - boolean success = sketch.exportApplet(false); + boolean success = sketchController.exportApplet(false); if (success) { statusNotice(tr("Done uploading.")); } @@ -2287,7 +2298,7 @@ public void run() { uploading = true; - boolean success = sketch.exportApplet(true); + boolean success = sketchController.exportApplet(true); if (success) { statusNotice(tr("Done uploading.")); } diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index ade19324af7..68e50b5e786 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -82,14 +82,14 @@ public class EditorHeader extends JComponent { public class Actions { public final Action newTab = new SimpleAction(tr("New Tab"), Keys.ctrlShift(KeyEvent.VK_N), - () -> editor.getSketch().handleNewCode()); + () -> editor.getSketchController().handleNewCode()); public final Action renameTab = new SimpleAction(tr("Rename"), - () -> editor.getSketch().handleRenameCode()); + () -> editor.getSketchController().handleRenameCode()); public final Action deleteTab = new SimpleAction(tr("Delete"), () -> { try { - editor.getSketch().handleDeleteCode(); + editor.getSketchController().handleDeleteCode(); } catch (IOException e) { e.printStackTrace(); } @@ -184,7 +184,7 @@ public void mousePressed(MouseEvent e) { public void paintComponent(Graphics screen) { if (screen == null) return; - Sketch sketch = editor.getSketch(); + SketchController sketch = editor.getSketchController(); if (sketch == null) return; // ?? Dimension size = getSize(); diff --git a/app/src/processing/app/EditorStatus.java b/app/src/processing/app/EditorStatus.java index 62187c77ad2..cd56c4171c5 100644 --- a/app/src/processing/app/EditorStatus.java +++ b/app/src/processing/app/EditorStatus.java @@ -242,7 +242,7 @@ private void initialize() { // answering to rename/new code question if (mode == EDIT) { // this if() isn't (shouldn't be?) necessary String answer = editField.getText(); - editor.getSketch().nameCode(answer); + editor.getSketchController().nameCode(answer); unedit(); } }); @@ -281,7 +281,7 @@ public void keyTyped(KeyEvent event) { if (c == KeyEvent.VK_ENTER) { // accept the input String answer = editField.getText(); - editor.getSketch().nameCode(answer); + editor.getSketchController().nameCode(answer); unedit(); event.consume(); diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index be2d4ae33d2..9e2971e2412 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -157,7 +157,8 @@ private SketchTextArea createTextArea(RSyntaxDocument document) @Override public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) { try { - editor.platform.openURL(editor.getSketch().getFolder(), hyperlinkEvent.getURL().toExternalForm()); + editor.platform.openURL(editor.getSketch().getFolder(), + hyperlinkEvent.getURL().toExternalForm()); } catch (Exception e) { Base.showWarning(e.getMessage(), e.getMessage(), e); } @@ -357,8 +358,8 @@ public SketchTextArea getTextArea() { /** * Get the sketch this tab is editing a file from. */ - public Sketch getSketch() { - return editor.getSketch(); + public SketchController getSketch() { + return editor.getSketchController(); } /** @@ -415,7 +416,7 @@ private void setModified(boolean value) { if (value != modified) { modified = value; // TODO: Improve decoupling - editor.getSketch().calcModified(); + editor.getSketchController().calcModified(); } } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/SketchController.java similarity index 89% rename from app/src/processing/app/Sketch.java rename to app/src/processing/app/SketchController.java index 888275d455a..3e6f39d34fd 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/SketchController.java @@ -53,19 +53,15 @@ /** - * Stores information about files in the current sketch + * Handles various tasks related to a sketch, in response to user inter-action. */ -public class Sketch { +public class SketchController { private final Editor editor; - private final SketchData data; + private final Sketch sketch; - /** - * path is location of the main .pde file, because this is also - * simplest to use when opening the file from the finder/explorer. - */ - public Sketch(Editor _editor, File file) throws IOException { + public SketchController(Editor _editor, Sketch _sketch) { editor = _editor; - data = new SketchData(file); + sketch = _sketch; } private boolean renamingCode; @@ -147,7 +143,7 @@ protected void nameCode(String newName) { // Add the extension here, this simplifies some of the logic below. if (newName.indexOf('.') == -1) { - newName += "." + getDefaultExtension(); + newName += "." + sketch.getDefaultExtension(); } // if renaming to the same thing as before, just ignore. @@ -186,7 +182,7 @@ protected void nameCode(String newName) { // Don't let the user create the main tab as a .java file instead of .pde if (!isDefaultExtension(newExtension)) { if (renamingCode) { // If creating a new tab, don't show this error - if (current == data.getCode(0)) { // If this is the main tab, disallow + if (current == sketch.getCode(0)) { // If this is the main tab, disallow Base.showWarning(tr("Problem with rename"), tr("The main file can't use an extension.\n" + "(It may be time for your to graduate to a\n" + @@ -208,13 +204,13 @@ protected void nameCode(String newName) { // In Arduino, we want to allow files with the same name but different // extensions, so compare the full names (including extensions). This // might cause problems: http://dev.processing.org/bugs/show_bug.cgi?id=543 - for (SketchCode c : data.getCodes()) { + for (SketchCode c : sketch.getCodes()) { if (newName.equalsIgnoreCase(c.getFileName()) && OSUtils.isWindows()) { Base.showMessage(tr("Error"), I18n.format( tr("A file named \"{0}\" already exists in \"{1}\""), c.getFileName(), - data.getFolder().getAbsolutePath() + sketch.getFolder().getAbsolutePath() )); return; } @@ -223,14 +219,14 @@ protected void nameCode(String newName) { // In Arduino, don't allow a .cpp file with the same name as the sketch, // because the sketch is concatenated into a file with that name as part // of the build process. - if (newName.equals(getName() + ".cpp")) { + if (newName.equals(sketch.getName() + ".cpp")) { Base.showMessage(tr("Error"), tr("You can't have a .cpp file with the same name as the sketch.")); return; } if (renamingCode && current.isPrimary()) { - for (SketchCode code : data.getCodes()) { + for (SketchCode code : sketch.getCodes()) { if (sanitaryName.equalsIgnoreCase(code.getPrettyName()) && code.isExtension("cpp")) { Base.showMessage(tr("Error"), @@ -243,7 +239,7 @@ protected void nameCode(String newName) { } - File newFile = new File(data.getFolder(), newName); + File newFile = new File(sketch.getFolder(), newName); // if (newFile.exists()) { // yay! users will try anything // Base.showMessage("Error", // "A file named \"" + newFile + "\" already exists\n" + @@ -265,7 +261,7 @@ protected void nameCode(String newName) { if (current.isPrimary()) { // get the new folder name/location String folderName = newName.substring(0, newName.indexOf('.')); - File newFolder = new File(data.getFolder().getParentFile(), folderName); + File newFolder = new File(sketch.getFolder().getParentFile(), folderName); if (newFolder.exists()) { Base.showWarning(tr("Cannot Rename"), I18n.format( @@ -303,7 +299,7 @@ protected void nameCode(String newName) { // save each of the other tabs because this is gonna be re-opened try { - for (SketchCode code : data.getCodes()) { + for (SketchCode code : sketch.getCodes()) { code.save(); } } catch (Exception e) { @@ -312,7 +308,7 @@ protected void nameCode(String newName) { } // now rename the sketch folder and re-open - boolean success = data.getFolder().renameTo(newFolder); + boolean success = sketch.getFolder().renameTo(newFolder); if (!success) { Base.showWarning(tr("Error"), tr("Could not rename the sketch. (2)"), null); return; @@ -357,7 +353,7 @@ protected void nameCode(String newName) { I18n.format( "Could not create the file \"{0}\" in \"{1}\"", newFile, - data.getFolder().getAbsolutePath() + sketch.getFolder().getAbsolutePath() ), e); return; } @@ -372,7 +368,7 @@ protected void nameCode(String newName) { ), e); return; } - data.addCode(code); + sketch.addCode(code); } // set the new guy as current @@ -422,7 +418,7 @@ public void handleDeleteCode() throws IOException { // to do a save on the handleNew() // delete the entire sketch - Base.removeDir(data.getFolder()); + Base.removeDir(sketch.getFolder()); // get the changes into the sketchbook menu //sketchbook.rebuildMenus(); @@ -434,14 +430,14 @@ public void handleDeleteCode() throws IOException { } else { // delete the file - if (!current.deleteFile(BaseNoGui.getBuildFolder(data).toPath())) { + if (!current.deleteFile(BaseNoGui.getBuildFolder(sketch).toPath())) { Base.showMessage(tr("Couldn't do it"), I18n.format(tr("Could not delete \"{0}\"."), current.getFileName())); return; } // remove code from the list - data.removeCode(current); + sketch.removeCode(current); // just set current tab to the main tab editor.selectTab(0); @@ -469,7 +465,7 @@ public void calcModified() { public boolean isModified() { - for (SketchCode code : data.getCodes()) { + for (SketchCode code : sketch.getCodes()) { if (code.isModified()) return true; } @@ -492,7 +488,7 @@ public boolean save() throws IOException { } // rename .pde files to .ino - File mainFile = new File(getMainFilePath()); + File mainFile = new File(sketch.getMainFilePath()); File mainFolder = mainFile.getParentFile(); File[] pdeFiles = mainFolder.listFiles((dir, name) -> { return name.toLowerCase().endsWith(".pde"); @@ -528,13 +524,13 @@ public boolean save() throws IOException { } } - data.save(); + sketch.save(); return true; } private boolean renameCodeToInoExtension(File pdeFile) { - for (SketchCode c : data.getCodes()) { + for (SketchCode c : sketch.getCodes()) { if (!c.getFile().equals(pdeFile)) continue; @@ -567,9 +563,9 @@ protected boolean saveAs() throws IOException { // default to the parent folder of where this was // on macs a .getParentFile() method is required - fd.setDirectory(data.getFolder().getParentFile().getAbsolutePath()); + fd.setDirectory(sketch.getFolder().getParentFile().getAbsolutePath()); } - String oldName = data.getName(); + String oldName = sketch.getName(); fd.setFile(oldName); fd.setVisible(true); @@ -578,15 +574,15 @@ protected boolean saveAs() throws IOException { // user canceled selection if (newName == null) return false; - newName = Sketch.checkName(newName); + newName = SketchController.checkName(newName); File newFolder = new File(newParentDir, newName); // make sure there doesn't exist a .cpp file with that name already // but ignore this situation for the first tab, since it's probably being // resaved (with the same name) to another location/folder. - for (int i = 1; i < data.getCodeCount(); i++) { - SketchCode code = data.getCode(i); + for (int i = 1; i < sketch.getCodeCount(); i++) { + SketchCode code = sketch.getCode(i); if (newName.equalsIgnoreCase(code.getPrettyName())) { Base.showMessage(tr("Error"), I18n.format(tr("You can't save the sketch as \"{0}\"\n" + @@ -597,7 +593,7 @@ protected boolean saveAs() throws IOException { } // check if the paths are identical - if (newFolder.equals(data.getFolder())) { + if (newFolder.equals(sketch.getFolder())) { // just use "save" here instead, because the user will have received a // message (from the operating system) about "do you want to replace?" return save(); @@ -606,7 +602,7 @@ protected boolean saveAs() throws IOException { // check to see if the user is trying to save this sketch inside itself try { String newPath = newFolder.getCanonicalPath() + File.separator; - String oldPath = data.getFolder().getCanonicalPath() + File.separator; + String oldPath = sketch.getFolder().getCanonicalPath() + File.separator; if (newPath.indexOf(oldPath) == 0) { Base.showWarning(tr("How very Borges of you"), @@ -631,21 +627,21 @@ protected boolean saveAs() throws IOException { newFolder.mkdirs(); // save the other tabs to their new location - for (SketchCode code : data.getCodes()) { - if (data.indexOfCode(code) == 0) continue; + for (SketchCode code : sketch.getCodes()) { + if (sketch.indexOfCode(code) == 0) continue; File newFile = new File(newFolder, code.getFileName()); code.saveAs(newFile); } // re-copy the data folder (this may take a while.. add progress bar?) - if (data.getDataFolder().exists()) { + if (sketch.getDataFolder().exists()) { File newDataFolder = new File(newFolder, "data"); - Base.copyDir(data.getDataFolder(), newDataFolder); + Base.copyDir(sketch.getDataFolder(), newDataFolder); } // save the main tab with its new name File newFile = new File(newFolder, newName + ".ino"); - data.getCode(0).saveAs(newFile); + sketch.getCode(0).saveAs(newFile); editor.handleOpenUnchecked(newFile, editor.getCurrentTabIndex(), @@ -724,16 +720,16 @@ public boolean addFile(File sourceFile) { String codeExtension = null; boolean replacement = false; - for (String extension : SketchData.EXTENSIONS) { + for (String extension : Sketch.EXTENSIONS) { String lower = filename.toLowerCase(); if (lower.endsWith("." + extension)) { - destFile = new File(data.getFolder(), filename); + destFile = new File(sketch.getFolder(), filename); codeExtension = extension; } } if (codeExtension == null) { prepareDataFolder(); - destFile = new File(data.getDataFolder(), filename); + destFile = new File(sketch.getDataFolder(), filename); } // check whether this file already exists @@ -795,11 +791,11 @@ public boolean addFile(File sourceFile) { SketchCode newCode = new SketchCode(destFile, false); if (replacement) { - data.replaceCode(newCode); + sketch.replaceCode(newCode); } else { ensureExistence(); - data.addCode(newCode); + sketch.addCode(newCode); } editor.selectTab(editor.findTabIndex(filename)); } @@ -879,7 +875,7 @@ private void importLibrary(File jarPath) throws IOException { * @throws RunnerException */ public String build(boolean verbose, boolean save) throws RunnerException, PreferencesMapException, IOException { - return build(BaseNoGui.getBuildFolder(data).getAbsolutePath(), verbose, save); + return build(BaseNoGui.getBuildFolder(sketch).getAbsolutePath(), verbose, save); } /** @@ -900,7 +896,7 @@ private String build(String buildPath, boolean verbose, boolean save) throws Run CompilerProgressListener progressListener = editor.status::progressUpdate; boolean deleteTemp = false; - String pathToSketch = data.getMainFilePath(); + String pathToSketch = sketch.getMainFilePath(); if (isModified()) { // If any files are modified, make a copy of the sketch with the changes // saved, so arduino-builder will see the modifications. @@ -909,8 +905,7 @@ private String build(String buildPath, boolean verbose, boolean save) throws Run } try { - return new Compiler(pathToSketch, data, buildPath).build(progressListener, - save); + return new Compiler(pathToSketch, sketch, buildPath).build(progressListener, save); } finally { // Make sure we clean up any temporary sketch copy if (deleteTemp) @@ -920,17 +915,17 @@ private String build(String buildPath, boolean verbose, boolean save) throws Run private String saveSketchInTempFolder() throws IOException { File tempFolder = FileUtils.createTempFolder("arduino_modified_sketch_"); - FileUtils.copy(getFolder(), tempFolder); + FileUtils.copy(sketch.getFolder(), tempFolder); - for (SketchCode sc : Stream.of(data.getCodes()).filter(SketchCode::isModified).collect(Collectors.toList())) { + for (SketchCode sc : Stream.of(sketch.getCodes()).filter(SketchCode::isModified).collect(Collectors.toList())) { Files.write(Paths.get(tempFolder.getAbsolutePath(), sc.getFileName()), sc.getProgram().getBytes()); } - return Paths.get(tempFolder.getAbsolutePath(), data.getPrimaryFile().getName()).toString(); + return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getName()).toString(); } protected boolean exportApplet(boolean usingProgrammer) throws Exception { - return exportApplet(BaseNoGui.getBuildFolder(data).getAbsolutePath(), usingProgrammer); + return exportApplet(BaseNoGui.getBuildFolder(sketch).getAbsolutePath(), usingProgrammer); } @@ -982,7 +977,7 @@ private boolean upload(String buildPath, String suggestedClassName, boolean usin List warningsAccumulator = new LinkedList<>(); try { - success = new UploaderUtils().upload(data, uploader, buildPath, suggestedClassName, usingProgrammer, false, warningsAccumulator); + success = new UploaderUtils().upload(sketch, uploader, buildPath, suggestedClassName, usingProgrammer, false, warningsAccumulator); } finally { if (uploader.requiresAuthorization() && !success) { PreferencesData.remove(uploader.getAuthorizationKey()); @@ -1007,16 +1002,16 @@ private boolean upload(String buildPath, String suggestedClassName, boolean usin * but not its contents. */ private void ensureExistence() { - if (data.getFolder().exists()) return; + if (sketch.getFolder().exists()) return; Base.showWarning(tr("Sketch Disappeared"), tr("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(); + sketch.getFolder().mkdirs(); - for (SketchCode code : data.getCodes()) { + for (SketchCode code : sketch.getCodes()) { code.save(); // this will force a save } calcModified(); @@ -1037,7 +1032,7 @@ private void ensureExistence() { * volumes or folders without appropriate permissions. */ public boolean isReadOnly(LibraryList libraries, String examplesPath) { - String apath = data.getFolder().getAbsolutePath(); + String apath = sketch.getFolder().getAbsolutePath(); Optional libraryThatIncludesSketch = libraries.stream().filter(lib -> apath.startsWith(lib.getInstalledFolder().getAbsolutePath())).findFirst(); if (libraryThatIncludesSketch.isPresent() && !libraryThatIncludesSketch.get().onGoingDevelopment()) { @@ -1052,7 +1047,7 @@ private boolean sketchIsSystemExample(String apath, String examplesPath) { } private boolean sketchFilesAreReadOnly() { - for (SketchCode code : data.getCodes()) { + for (SketchCode code : sketch.getCodes()) { if (code.isModified() && code.fileReadOnly() && code.fileExists()) { return true; } @@ -1069,7 +1064,7 @@ private boolean sketchFilesAreReadOnly() { * True if the specified code has the default file extension. */ private boolean hasDefaultExtension(SketchCode code) { - return code.isExtension(getDefaultExtension()); + return code.isExtension(sketch.getDefaultExtension()); } @@ -1077,7 +1072,7 @@ private boolean hasDefaultExtension(SketchCode code) { * True if the specified extension is the default file extension. */ private boolean isDefaultExtension(String what) { - return what.equals(getDefaultExtension()); + return what.equals(sketch.getDefaultExtension()); } @@ -1086,15 +1081,7 @@ private boolean isDefaultExtension(String what) { * extensions. */ private boolean validExtension(String what) { - return SketchData.EXTENSIONS.contains(what); - } - - - /** - * Returns the default extension for this editor setup. - */ - public String getDefaultExtension() { - return data.getDefaultExtension(); + return Sketch.EXTENSIONS.contains(what); } static private final List hiddenExtensions = Arrays.asList("ino", "pde"); @@ -1103,66 +1090,15 @@ 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. */ private File prepareDataFolder() { - if (!data.getDataFolder().exists()) { - data.getDataFolder().mkdirs(); + if (!sketch.getDataFolder().exists()) { + sketch.getDataFolder().mkdirs(); } - return data.getDataFolder(); - } - - - 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); + return sketch.getDataFolder(); } @@ -1175,8 +1111,8 @@ public boolean isUntitled() { return editor.untitled; } - public boolean reload() throws IOException { - return data.reload(); + public Sketch getSketch() { + return sketch; } // ................................................................. diff --git a/app/src/processing/app/macosx/ThinkDifferent.java b/app/src/processing/app/macosx/ThinkDifferent.java index 7436591cb2c..e946bdc0fd7 100644 --- a/app/src/processing/app/macosx/ThinkDifferent.java +++ b/app/src/processing/app/macosx/ThinkDifferent.java @@ -75,7 +75,7 @@ public void openFiles(final AppEvent.OpenFilesEvent openFilesEvent) { try { Base.INSTANCE.handleOpen(file); List editors = Base.INSTANCE.getEditors(); - if (editors.size() == 2 && editors.get(0).getSketch().isUntitled()) { + if (editors.size() == 2 && editors.get(0).getSketchController().isUntitled()) { Base.INSTANCE.handleClose(editors.get(0)); } } catch (Exception e) { diff --git a/app/src/processing/app/tools/Archiver.java b/app/src/processing/app/tools/Archiver.java index 34cf4efa89f..7308a4d6de7 100644 --- a/app/src/processing/app/tools/Archiver.java +++ b/app/src/processing/app/tools/Archiver.java @@ -26,7 +26,7 @@ import org.apache.commons.compress.utils.IOUtils; import processing.app.Base; import processing.app.Editor; -import processing.app.Sketch; +import processing.app.SketchController; import java.awt.*; import java.io.File; @@ -69,7 +69,7 @@ public void init(Editor editor) { public void run() { - Sketch sketch = editor.getSketch(); + SketchController sketch = editor.getSketchController(); // first save the sketch so that things don't archive strangely boolean success = false; @@ -84,7 +84,7 @@ public void run() { return; } - File location = sketch.getFolder(); + File location = sketch.getSketch().getFolder(); String name = location.getName(); File parent = new File(location.getParent()); diff --git a/app/src/processing/app/tools/FixEncoding.java b/app/src/processing/app/tools/FixEncoding.java index 3ee5d055406..58621254ff5 100644 --- a/app/src/processing/app/tools/FixEncoding.java +++ b/app/src/processing/app/tools/FixEncoding.java @@ -49,7 +49,7 @@ public void init(Editor editor) { public void run() { - Sketch sketch = editor.getSketch(); + SketchController sketch = editor.getSketchController(); //SketchCode code = sketch.current; if (sketch.isModified()) { @@ -65,8 +65,8 @@ public void run() { } } try { - for (int i = 0; i < sketch.getCodeCount(); i++) { - SketchCode code = sketch.getCode(i); + for (int i = 0; i < sketch.getSketch().getCodeCount(); i++) { + SketchCode code = sketch.getSketch().getCode(i); editor.findTab(code).setText(loadWithLocalEncoding(code.getFile())); } } catch (IOException e) { diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java index bb815575cfc..bee55c2bd83 100644 --- a/arduino-core/src/cc/arduino/Compiler.java +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -109,16 +109,16 @@ enum BuilderAction { private static final Pattern ERROR_FORMAT = Pattern.compile("(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*error:\\s*(.*)\\s*", Pattern.MULTILINE | Pattern.DOTALL); private final String pathToSketch; - private final SketchData sketch; + private final Sketch sketch; private final String buildPath; private final boolean verbose; private RunnerException exception; - public Compiler(SketchData data, String buildPath) { + public Compiler(Sketch data, String buildPath) { this(data.getMainFilePath(), data, buildPath); } - public Compiler(String pathToSketch, SketchData sketch, String buildPath) { + public Compiler(String pathToSketch, Sketch sketch, String buildPath) { this.pathToSketch = pathToSketch; this.sketch = sketch; this.buildPath = buildPath; diff --git a/arduino-core/src/cc/arduino/UploaderUtils.java b/arduino-core/src/cc/arduino/UploaderUtils.java index b243c30d216..e49bcb6730b 100644 --- a/arduino-core/src/cc/arduino/UploaderUtils.java +++ b/arduino-core/src/cc/arduino/UploaderUtils.java @@ -34,7 +34,7 @@ import cc.arduino.packages.UploaderFactory; import processing.app.BaseNoGui; import processing.app.PreferencesData; -import processing.app.SketchData; +import processing.app.Sketch; import processing.app.debug.TargetPlatform; import java.util.LinkedList; @@ -56,7 +56,7 @@ public Uploader getUploaderByPreferences(boolean noUploadPort) { return new UploaderFactory().newUploader(target.getBoards().get(board), boardPort, noUploadPort); } - public boolean upload(SketchData data, Uploader uploader, String buildPath, String suggestedClassName, boolean usingProgrammer, boolean noUploadPort, List warningsAccumulator) throws Exception { + public boolean upload(Sketch data, Uploader uploader, String buildPath, String suggestedClassName, boolean usingProgrammer, boolean noUploadPort, List warningsAccumulator) throws Exception { if (uploader == null) uploader = getUploaderByPreferences(noUploadPort); diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index feefc7ac101..4a9e9cf415b 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -121,7 +121,7 @@ static public String getAvrBasePath() { return path; } - static public File getBuildFolder(SketchData data) throws IOException { + static public File getBuildFolder(Sketch data) throws IOException { File buildFolder; if (PreferencesData.get("build.path") != null) { buildFolder = absoluteFile(PreferencesData.get("build.path")); @@ -471,7 +471,7 @@ static public void init(String[] args) throws Exception { // This translates here as: // SketchData data = new SketchData(file); // File tempBuildFolder = getBuildFolder(); - SketchData data = new SketchData(absoluteFile(parser.getFilenames().get(0))); + Sketch data = new Sketch(absoluteFile(parser.getFilenames().get(0))); File tempBuildFolder = getBuildFolder(data); // Sketch.exportApplet() @@ -517,7 +517,7 @@ static public void init(String[] args) throws Exception { // SketchData data = new SketchData(file); // File tempBuildFolder = getBuildFolder(); // data.load(); - SketchData data = new SketchData(absoluteFile(path)); + Sketch data = new Sketch(absoluteFile(path)); File tempBuildFolder = getBuildFolder(data); // Sketch.prepare() calls Sketch.ensureExistence() diff --git a/arduino-core/src/processing/app/SketchData.java b/arduino-core/src/processing/app/Sketch.java similarity index 97% rename from arduino-core/src/processing/app/SketchData.java rename to arduino-core/src/processing/app/Sketch.java index 3765bb1572f..87546fb742f 100644 --- a/arduino-core/src/processing/app/SketchData.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -10,7 +10,10 @@ import static processing.app.I18n.tr; -public class SketchData { +/** + * This represents a single sketch, consisting of one or more files. + */ +public class Sketch { public static final List SKETCH_EXTENSIONS = Arrays.asList("ino", "pde"); public static final List OTHER_ALLOWED_EXTENSIONS = Arrays.asList("c", "cpp", "h", "hh", "hpp", "s"); @@ -57,7 +60,7 @@ public int compare(SketchCode x, SketchCode y) { * @param file * The primary file for this sketch. */ - SketchData(File file) throws IOException { + Sketch(File file) throws IOException { primaryFile = file; // get the name of the sketch by chopping .pde or .java From 44d6d7678ed61f769abd4754cd39558de0a5d025 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 11:30:44 +0100 Subject: [PATCH 27/64] Move `isModified()` from SketchController to Sketch Also, update any code that uses it, removing the dependency on SketchController entirely if possible. --- app/src/processing/app/Base.java | 2 +- app/src/processing/app/Editor.java | 21 ++++++++++++++----- app/src/processing/app/SketchController.java | 12 ++--------- app/src/processing/app/tools/FixEncoding.java | 6 +++--- arduino-core/src/processing/app/Sketch.java | 11 ++++++++++ 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index d61d3322075..210d0a07777 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -570,7 +570,7 @@ protected void storeSketches() { String path = editor.getSketch().getMainFilePath(); // In case of a crash, save untitled sketches if they contain changes. // (Added this for release 0158, may not be a good idea.) - if (path.startsWith(untitledPath) && !editor.getSketchController().isModified()) { + if (path.startsWith(untitledPath) && !editor.getSketch().isModified()) { continue; } PreferencesData.set("last.sketch" + index + ".path", path); diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 4e1f76a48d0..e0ad4375b76 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -84,11 +84,17 @@ public class Editor extends JFrame implements RunnerListener { private ArrayList tabs = new ArrayList<>(); private int currentTabIndex = -1; - private static class ShouldSaveIfModified implements Predicate { + private static class ShouldSaveIfModified + implements Predicate { @Override - public boolean test(SketchController sketch) { - return PreferencesData.getBoolean("editor.save_on_verify") && sketch.isModified() && !sketch.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()); + public boolean test(SketchController controller) { + return PreferencesData.getBoolean("editor.save_on_verify") + && controller.getSketch().isModified() + && !controller.isReadOnly( + BaseNoGui.librariesIndexer + .getInstalledLibraries(), + BaseNoGui.getExamplesPath()); } } @@ -1846,7 +1852,8 @@ public void internalCloseRunner() { * @return false if canceling the close/quit operation */ protected boolean checkModified() { - if (!sketchController.isModified()) return true; + if (!sketch.isModified()) + return true; // As of Processing 1.0.10, this always happens immediately. // http://dev.processing.org/bugs/show_bug.cgi?id=1456 @@ -2177,7 +2184,11 @@ private boolean serialPrompt() { */ synchronized public void handleExport(final boolean usingProgrammer) { if (PreferencesData.getBoolean("editor.save_on_verify")) { - if (sketchController.isModified() && !sketchController.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) { + if (sketch.isModified() + && !sketchController.isReadOnly( + BaseNoGui.librariesIndexer + .getInstalledLibraries(), + BaseNoGui.getExamplesPath())) { handleSave(true); } } diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 3e6f39d34fd..c64a8194c65 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -457,21 +457,13 @@ public void calcModified() { if (OSUtils.isMacOS()) { // http://developer.apple.com/qa/qa2001/qa1146.html - Object modifiedParam = isModified() ? Boolean.TRUE : Boolean.FALSE; + Object modifiedParam = sketch.isModified() ? Boolean.TRUE : Boolean.FALSE; editor.getRootPane().putClientProperty("windowModified", modifiedParam); editor.getRootPane().putClientProperty("Window.documentModified", modifiedParam); } } - public boolean isModified() { - for (SketchCode code : sketch.getCodes()) { - if (code.isModified()) - return true; - } - return false; - } - /** * Save all code in the current sketch. @@ -897,7 +889,7 @@ private String build(String buildPath, boolean verbose, boolean save) throws Run boolean deleteTemp = false; String pathToSketch = sketch.getMainFilePath(); - if (isModified()) { + if (sketch.isModified()) { // If any files are modified, make a copy of the sketch with the changes // saved, so arduino-builder will see the modifications. pathToSketch = saveSketchInTempFolder(); diff --git a/app/src/processing/app/tools/FixEncoding.java b/app/src/processing/app/tools/FixEncoding.java index 58621254ff5..3ee5d055406 100644 --- a/app/src/processing/app/tools/FixEncoding.java +++ b/app/src/processing/app/tools/FixEncoding.java @@ -49,7 +49,7 @@ public void init(Editor editor) { public void run() { - SketchController sketch = editor.getSketchController(); + Sketch sketch = editor.getSketch(); //SketchCode code = sketch.current; if (sketch.isModified()) { @@ -65,8 +65,8 @@ public void run() { } } try { - for (int i = 0; i < sketch.getSketch().getCodeCount(); i++) { - SketchCode code = sketch.getSketch().getCode(i); + for (int i = 0; i < sketch.getCodeCount(); i++) { + SketchCode code = sketch.getCode(i); editor.findTab(code).setText(loadWithLocalEncoding(code.getFile())); } } catch (IOException e) { diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 87546fb742f..90e3964a373 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -214,4 +214,15 @@ public File getFolder() { public File getDataFolder() { return dataFolder; } + + /** + * Is any of the files in this sketch modified? + */ + public boolean isModified() { + for (SketchCode code : codes) { + if (code.isModified()) + return true; + } + return false; + } } From f9211631cf2adedc93d19ccef56196310c248cb0 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 11:34:44 +0100 Subject: [PATCH 28/64] Use `SketchCode.isPrimary()` in more places This should be more reliable than comparing filenames or assuming the primary file is always at index 0. --- app/src/processing/app/Editor.java | 2 +- app/src/processing/app/SketchController.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index e0ad4375b76..a8599bd55de 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -2024,7 +2024,7 @@ private void updateTitle() { return; } SketchCode current = getCurrentTab().getSketchCode(); - if (sketch.getName().equals(current.getPrettyName())) { + if (current.isPrimary()) { setTitle(I18n.format(tr("{0} | Arduino {1}"), sketch.getName(), BaseNoGui.VERSION_NAME_LONG)); } else { diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index c64a8194c65..8a2fcc9e3ba 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -182,7 +182,7 @@ protected void nameCode(String newName) { // Don't let the user create the main tab as a .java file instead of .pde if (!isDefaultExtension(newExtension)) { if (renamingCode) { // If creating a new tab, don't show this error - if (current == sketch.getCode(0)) { // If this is the main tab, disallow + if (current.isPrimary()) { // If this is the main tab, disallow Base.showWarning(tr("Problem with rename"), tr("The main file can't use an extension.\n" + "(It may be time for your to graduate to a\n" + @@ -573,9 +573,8 @@ protected boolean saveAs() throws IOException { // make sure there doesn't exist a .cpp file with that name already // but ignore this situation for the first tab, since it's probably being // resaved (with the same name) to another location/folder. - for (int i = 1; i < sketch.getCodeCount(); i++) { - SketchCode code = sketch.getCode(i); - if (newName.equalsIgnoreCase(code.getPrettyName())) { + for (SketchCode code : sketch.getCodes()) { + if (!code.isPrimary() && newName.equalsIgnoreCase(code.getPrettyName())) { Base.showMessage(tr("Error"), I18n.format(tr("You can't save the sketch as \"{0}\"\n" + "because the sketch already has a file with that name."), newName @@ -620,7 +619,7 @@ protected boolean saveAs() throws IOException { // save the other tabs to their new location for (SketchCode code : sketch.getCodes()) { - if (sketch.indexOfCode(code) == 0) continue; + if (code.isPrimary()) continue; File newFile = new File(newFolder, code.getFileName()); code.saveAs(newFile); } From 149179802ad871b93cf2d9b1bee09ef21b67be2c Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 11:46:17 +0100 Subject: [PATCH 29/64] Do not find a tab based on filename, when we have a SketchCode Filename-based matching is a bit more fragile, so just do a lookup based on the SketchCode object we already have instead. --- app/src/processing/app/SketchController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 8a2fcc9e3ba..f9ccd160c2d 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -788,7 +788,7 @@ public boolean addFile(File sourceFile) { ensureExistence(); sketch.addCode(newCode); } - editor.selectTab(editor.findTabIndex(filename)); + editor.selectTab(editor.findTabIndex(newCode)); } return true; } From f4847748bf111bd8b753db0dad949477a7e4023f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 11:47:14 +0100 Subject: [PATCH 30/64] In SketchController.nameCode, only switch tabs when adding file When renaming a file, the current tab is already the correct one, so no need to switch. This allows looking up the tab index based on the SketchCode object, instead of doing a filename lookup. --- app/src/processing/app/SketchController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index f9ccd160c2d..8cc957fe719 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -369,11 +369,9 @@ protected void nameCode(String newName) { return; } sketch.addCode(code); + editor.selectTab(editor.findTabIndex(code)); } - // set the new guy as current - editor.selectTab(editor.findTabIndex(newName)); - // update the tabs editor.header.rebuild(); } From 2e9af61f567320d8060a4067f3267a8a5814f642 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 11:49:26 +0100 Subject: [PATCH 31/64] Remove `Editor.findTabIndex(String)`, which is now unused --- app/src/processing/app/Editor.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index a8599bd55de..c8d908f8edb 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1661,22 +1661,6 @@ public int findTabIndex(final SketchCode doc) { return -1; } - /** - * Finds the tab that shows the given file (as returned by - * SketchCode.getFileName() or SketchCode.getPrettyName()). - */ - public int findTabIndex(final String name) { - int i = 0; - for (EditorTab tab : tabs) { - SketchCode code = tab.getSketchCode(); - if (name.equals(code.getFileName()) || - name.equals(code.getPrettyName())) { - return i; - } - } - return -1; - } - /** * Create tabs for each of the current sketch's files, removing any existing * tabs. From 8c4a1254c54731dc53e2588899cd328ad198d6ea Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 12:01:24 +0100 Subject: [PATCH 32/64] Let `Sketch.getPrettyName()` hide extension for .ino and .pde only Before, `getPrettyName()` would return the extension-less name for all files. There were a lot of places that checked for .ino and/or .pde files and and called `getPrettyName()` for those, and `getFileName()` for others. By moving this check into `getPrettyName()`, all those callers become more simple, and more consistent (there were 5 different checks to basically achieve the same thing). There are small changes in behaviour, where .pde is now also hidden but was not before. Also, the print header now shows extensions for other files, which makes it more consistent with the tab names. For cases where the old behaviour was still required, `Sketch.getBaseName()` was added. At the same time, the actual handling of the filenames is simplified by using methods from FileUtils. With this change `Sketch.getFileNameWithExtensionIfNotIno()` and `SketchController.getHiddenExtensions()` are no longer needed and are removed. --- app/src/processing/app/EditorHeader.java | 7 ++--- app/src/processing/app/SketchController.java | 16 +++--------- arduino-core/src/cc/arduino/Compiler.java | 2 +- .../src/processing/app/SketchCode.java | 26 ++++++++++++------- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index 68e50b5e786..de929a86380 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -239,9 +239,7 @@ public void paintComponent(Graphics screen) { int i = 0; for (EditorTab tab : tabs) { SketchCode code = tab.getSketchCode(); - - String codeName = code.isExtension(sketch.getHiddenExtensions()) ? - code.getPrettyName() : code.getFileName(); + String codeName = code.getPrettyName(); // if modified, add the li'l glyph next to the name String text = " " + codeName + (code.isModified() ? " \u00A7" : " "); @@ -324,8 +322,7 @@ public void rebuildMenu() { for (EditorTab tab : editor.getTabs()) { SketchCode code = tab.getSketchCode(); final int index = i++; - item = new JMenuItem(code.isExtension(sketch.getDefaultExtension()) ? - code.getPrettyName() : code.getFileName()); + item = new JMenuItem(code.getPrettyName()); item.addActionListener((ActionEvent e) -> { editor.selectTab(index); }); diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 8cc957fe719..5f8fd1773e4 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -42,7 +42,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Optional; @@ -121,8 +120,7 @@ public void handleRenameCode() { renamingCode = true; String prompt = current.isPrimary() ? "New name for sketch:" : "New name for file:"; - String oldName = (current.isExtension("ino")) ? current.getPrettyName() - : current.getFileName(); + String oldName = current.getPrettyName(); editor.status.edit(prompt, oldName); } @@ -227,7 +225,7 @@ protected void nameCode(String newName) { if (renamingCode && current.isPrimary()) { for (SketchCode code : sketch.getCodes()) { - if (sanitaryName.equalsIgnoreCase(code.getPrettyName()) && + if (sanitaryName.equalsIgnoreCase(code.getBaseName()) && code.isExtension("cpp")) { Base.showMessage(tr("Error"), I18n.format(tr("You can't rename the sketch to \"{0}\"\n" @@ -401,7 +399,7 @@ public void handleDeleteCode() throws IOException { String prompt = current.isPrimary() ? tr("Are you sure you want to delete this sketch?") : I18n.format(tr("Are you sure you want to delete \"{0}\"?"), - current.getFileNameWithExtensionIfNotIno()); + current.getPrettyName()); int result = JOptionPane.showOptionDialog(editor, prompt, tr("Delete"), @@ -572,7 +570,7 @@ protected boolean saveAs() throws IOException { // but ignore this situation for the first tab, since it's probably being // resaved (with the same name) to another location/folder. for (SketchCode code : sketch.getCodes()) { - if (!code.isPrimary() && newName.equalsIgnoreCase(code.getPrettyName())) { + if (!code.isPrimary() && newName.equalsIgnoreCase(code.getBaseName())) { Base.showMessage(tr("Error"), I18n.format(tr("You can't save the sketch as \"{0}\"\n" + "because the sketch already has a file with that name."), newName @@ -1073,12 +1071,6 @@ private boolean validExtension(String what) { return Sketch.EXTENSIONS.contains(what); } - static private final List hiddenExtensions = Arrays.asList("ino", "pde"); - - public List getHiddenExtensions() { - return hiddenExtensions; - } - /** * 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. diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java index bee55c2bd83..006a530c0d5 100644 --- a/arduino-core/src/cc/arduino/Compiler.java +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -566,7 +566,7 @@ public void message(String s) { if (exception != null) { SketchCode code = sketch.getCode(exception.getCodeIndex()); - String fileName = (code.isExtension("ino") || code.isExtension("pde")) ? code.getPrettyName() : code.getFileName(); + String fileName = code.getPrettyName(); int lineNum = exception.getCodeLine() + 1; s = fileName + ":" + lineNum + ": error: " + error + msg; } diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index dac19456800..576ccbac212 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -161,22 +161,30 @@ protected boolean renameTo(File what) { } + /* + * Returns the filename include extension. + */ public String getFileName() { return file.getName(); } - + /** + * Returns the filename without extension for normal sketch files + * (Sketch.SKETCH_EXTENSIONS) and the filename with extension for all + * others. + */ public String getPrettyName() { - String prettyName = getFileName(); - int dot = prettyName.lastIndexOf('.'); - return prettyName.substring(0, dot); + if (isExtension(Sketch.SKETCH_EXTENSIONS)) + return getBaseName(); + else + return getFileName(); } - public String getFileNameWithExtensionIfNotIno() { - if (getFileName().endsWith(".ino")) { - return getPrettyName(); - } - return getFileName(); + /** + * Returns the filename without extension + */ + public String getBaseName() { + return FileUtils.splitFilename(file).basename; } public boolean isExtension(String... extensions) { From 331b83e1548df42712dadf4028a53390485c0987 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 12:13:59 +0100 Subject: [PATCH 33/64] Simplify SketchController.addFile using FileUtils.hasExtension --- app/src/processing/app/SketchController.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 5f8fd1773e4..dcc846243b5 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -704,19 +704,15 @@ public void handleAddFile() { public boolean addFile(File sourceFile) { String filename = sourceFile.getName(); File destFile = null; - String codeExtension = null; + boolean isData = false; boolean replacement = false; - for (String extension : Sketch.EXTENSIONS) { - String lower = filename.toLowerCase(); - if (lower.endsWith("." + extension)) { - destFile = new File(sketch.getFolder(), filename); - codeExtension = extension; - } - } - if (codeExtension == null) { + if (FileUtils.hasExtension(sourceFile, Sketch.EXTENSIONS)) { + destFile = new File(sketch.getFolder(), filename); + } else { prepareDataFolder(); destFile = new File(sketch.getDataFolder(), filename); + isData = true; } // check whether this file already exists @@ -752,7 +748,7 @@ public boolean addFile(File sourceFile) { } // make sure they aren't the same file - if ((codeExtension == null) && sourceFile.equals(destFile)) { + if (isData && sourceFile.equals(destFile)) { Base.showWarning(tr("You can't fool me"), tr("This file has already been copied to the\n" + "location from which where you're trying to add it.\n" + @@ -774,7 +770,7 @@ public boolean addFile(File sourceFile) { } } - if (codeExtension != null) { + if (!isData) { SketchCode newCode = new SketchCode(destFile, false); if (replacement) { From 98c0818ae18a78756f7ac8c78f70dda6ae378983 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 12:50:09 +0100 Subject: [PATCH 34/64] Clean up SketchController.nameCode a bit This lets it use FileUtils.splitFilename and reference Sketch.EXTENSIONS and the new Sketch.DEFAULT_SKETCH_EXTENSION directly, allowing to remove a few helper functions. --- app/src/processing/app/SketchController.java | 42 +++++--------------- arduino-core/src/processing/app/Sketch.java | 3 +- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index dcc846243b5..72bc52a8074 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -141,7 +141,7 @@ protected void nameCode(String newName) { // Add the extension here, this simplifies some of the logic below. if (newName.indexOf('.') == -1) { - newName += "." + sketch.getDefaultExtension(); + newName += "." + Sketch.DEFAULT_SKETCH_EXTENSION; } // if renaming to the same thing as before, just ignore. @@ -168,17 +168,17 @@ protected void nameCode(String newName) { return; } - String newExtension = newName.substring(dot+1).toLowerCase(); - if (!validExtension(newExtension)) { + FileUtils.SplitFile split = FileUtils.splitFilename(newName); + if (!Sketch.EXTENSIONS.contains(split.extension)) { Base.showWarning(tr("Problem with rename"), - I18n.format( - tr("\".{0}\" is not a valid extension."), newExtension - ), null); + I18n.format(tr("\".{0}\" is not a valid extension."), + split.extension), + null); return; } // Don't let the user create the main tab as a .java file instead of .pde - if (!isDefaultExtension(newExtension)) { + if (!split.extension.equals(Sketch.DEFAULT_SKETCH_EXTENSION)) { if (renamingCode) { // If creating a new tab, don't show this error if (current.isPrimary()) { // If this is the main tab, disallow Base.showWarning(tr("Problem with rename"), @@ -190,14 +190,9 @@ protected void nameCode(String newName) { } } - // dots are allowed for the .pde and .java, but not in the name - // make sure the user didn't name things poo.time.pde - // or something like that (nothing against poo time) - String shortName = newName.substring(0, dot); - String sanitaryName = BaseNoGui.sanitizeName(shortName); - if (!shortName.equals(sanitaryName)) { - newName = sanitaryName + "." + newExtension; - } + // Sanitize name + String sanitaryName = BaseNoGui.sanitizeName(split.basename); + newName = sanitaryName + "." + split.extension; // In Arduino, we want to allow files with the same name but different // extensions, so compare the full names (including extensions). This @@ -1050,23 +1045,6 @@ private boolean hasDefaultExtension(SketchCode code) { return code.isExtension(sketch.getDefaultExtension()); } - - /** - * True if the specified extension is the default file extension. - */ - private boolean isDefaultExtension(String what) { - return what.equals(sketch.getDefaultExtension()); - } - - - /** - * Check this extension (no dots, please) against the list of valid - * extensions. - */ - private boolean validExtension(String what) { - return Sketch.EXTENSIONS.contains(what); - } - /** * 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. diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 90e3964a373..a598841ab6a 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -15,7 +15,8 @@ */ public class Sketch { - public static final List SKETCH_EXTENSIONS = Arrays.asList("ino", "pde"); + public static final String DEFAULT_SKETCH_EXTENSION = "ino"; + public static final List SKETCH_EXTENSIONS = Arrays.asList(DEFAULT_SKETCH_EXTENSION, "pde"); public static final List OTHER_ALLOWED_EXTENSIONS = Arrays.asList("c", "cpp", "h", "hh", "hpp", "s"); public static final List EXTENSIONS = Stream.concat(SKETCH_EXTENSIONS.stream(), OTHER_ALLOWED_EXTENSIONS.stream()).collect(Collectors.toList()); From a20d6205cbccd4692dce7c939220094c4d4ace2d Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 12:56:49 +0100 Subject: [PATCH 35/64] Let importLibrary use `Sketch.SKETCH_EXTENSIONS` For determining if the current file was a sketch file, it previously (indirectly) used a hardcoded "ino" comparison. Now, it uses `SKETCH_EXTENSIONS` so it also applies to .pde files and the hardcoded "ino" (and the methods leading up to it) can be removed. --- app/src/processing/app/SketchController.java | 15 +++------------ arduino-core/src/processing/app/Sketch.java | 7 ------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 72bc52a8074..6844126f5ee 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -801,9 +801,10 @@ private void importLibrary(File jarPath) throws IOException { // import statements into the main sketch file (code[0]) // if the current code is a .java file, insert into current //if (current.flavor == PDE) { - if (hasDefaultExtension(editor.getCurrentTab().getSketchCode())) { + SketchCode code = editor.getCurrentTab().getSketchCode(); + if (code.isExtension(Sketch.SKETCH_EXTENSIONS)) editor.selectTab(0); - } + // could also scan the text in the file to see if each import // statement is already in there, but if the user has the import // commented out, then this will be a problem. @@ -1035,16 +1036,6 @@ private boolean sketchFilesAreReadOnly() { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - // 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. - */ - private boolean hasDefaultExtension(SketchCode code) { - return code.isExtension(sketch.getDefaultExtension()); - } - /** * 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. diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index a598841ab6a..fd312b7a027 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -155,13 +155,6 @@ public SketchCode[] getCodes() { return codes.toArray(new SketchCode[0]); } - /** - * Returns the default extension for this editor setup. - */ - public String getDefaultExtension() { - return "ino"; - } - /** * Returns a file object for the primary .pde of this sketch. */ From a84b989c5fa4420d686da64874d222c57c0eb551 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 13:00:47 +0100 Subject: [PATCH 36/64] Move `SketchController.prepareDataFolder()` to Sketch --- app/src/processing/app/SketchController.java | 12 +----------- arduino-core/src/processing/app/Sketch.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 6844126f5ee..9729c70e43c 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -705,7 +705,7 @@ public boolean addFile(File sourceFile) { if (FileUtils.hasExtension(sourceFile, Sketch.EXTENSIONS)) { destFile = new File(sketch.getFolder(), filename); } else { - prepareDataFolder(); + sketch.prepareDataFolder(); destFile = new File(sketch.getDataFolder(), filename); isData = true; } @@ -1036,16 +1036,6 @@ private boolean sketchFilesAreReadOnly() { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /** - * 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. - */ - private File prepareDataFolder() { - if (!sketch.getDataFolder().exists()) { - sketch.getDataFolder().mkdirs(); - } - return sketch.getDataFolder(); - } private void setUntitled(boolean u) { diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index fd312b7a027..a7e2850afdb 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -140,6 +140,18 @@ private List listSketchFiles(boolean showWarnings) throws IOExceptio return new ArrayList<>(result); } + /** + * 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 (!dataFolder.exists()) { + dataFolder.mkdirs(); + } + return dataFolder; + } + public void save() throws IOException { for (SketchCode code : getCodes()) { if (code.isModified()) From f6d9cc64a8f32190a816839817f936926009612f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 15:25:00 +0100 Subject: [PATCH 37/64] Store a SketchCode instance in RunnerException Previously, the index of the SketchCode instance in the list kept by Sketch was kept, which isn't really robust. With this change, Sketch.indexOfCode is no longer needed and is removed. --- app/src/processing/app/Editor.java | 4 +-- arduino-core/src/cc/arduino/Compiler.java | 5 ++- arduino-core/src/processing/app/Sketch.java | 4 --- .../processing/app/debug/RunnerException.java | 34 +++++++++---------- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index c8d908f8edb..c8744c4fcea 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -2614,8 +2614,8 @@ public void statusError(Exception e) { if (e instanceof RunnerException) { RunnerException re = (RunnerException) e; - if (re.hasCodeIndex()) { - selectTab(re.getCodeIndex()); + if (re.hasCodeFile()) { + selectTab(findTabIndex(re.getCodeFile())); } if (re.hasCodeLine()) { int line = re.getCodeLine(); diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java index 006a530c0d5..aab66e8c0c8 100644 --- a/arduino-core/src/cc/arduino/Compiler.java +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -565,8 +565,7 @@ public void message(String s) { RunnerException exception = placeException(error, pieces[1], PApplet.parseInt(pieces[2]) - 1); if (exception != null) { - SketchCode code = sketch.getCode(exception.getCodeIndex()); - String fileName = code.getPrettyName(); + String fileName = exception.getCodeFile().getPrettyName(); int lineNum = exception.getCodeLine() + 1; s = fileName + ":" + lineNum + ": error: " + error + msg; } @@ -597,7 +596,7 @@ public void message(String s) { private RunnerException placeException(String message, String fileName, int line) { for (SketchCode code : sketch.getCodes()) { if (new File(fileName).getName().equals(code.getFileName())) { - return new RunnerException(message, sketch.indexOfCode(code), line); + return new RunnerException(message, code, line); } } return null; diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index a7e2850afdb..e0d9429f407 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -205,10 +205,6 @@ protected void removeCode(SketchCode which) { System.err.println("removeCode: internal error.. could not find code"); } - public int indexOfCode(SketchCode who) { - return codes.indexOf(who); - } - public String getName() { return name; } diff --git a/arduino-core/src/processing/app/debug/RunnerException.java b/arduino-core/src/processing/app/debug/RunnerException.java index 0a67d1e80ef..c5aa7952051 100644 --- a/arduino-core/src/processing/app/debug/RunnerException.java +++ b/arduino-core/src/processing/app/debug/RunnerException.java @@ -23,6 +23,7 @@ package processing.app.debug; +import processing.app.SketchCode; /** * An exception with a line number attached that occurs @@ -31,7 +32,7 @@ @SuppressWarnings("serial") public class RunnerException extends Exception { protected String message; - protected int codeIndex; + protected SketchCode codeFile; protected int codeLine; protected int codeColumn; protected boolean showStackTrace; @@ -42,23 +43,23 @@ public RunnerException(String message) { } public RunnerException(String message, boolean showStackTrace) { - this(message, -1, -1, -1, showStackTrace); + this(message, null, -1, -1, showStackTrace); } - public RunnerException(String message, int file, int line) { + public RunnerException(String message, SketchCode file, int line) { this(message, file, line, -1, true); } - public RunnerException(String message, int file, int line, int column) { + public RunnerException(String message, SketchCode file, int line, int column) { this(message, file, line, column, true); } - public RunnerException(String message, int file, int line, int column, + public RunnerException(String message, SketchCode file, int line, int column, boolean showStackTrace) { this.message = message; - this.codeIndex = file; + this.codeFile = file; this.codeLine = line; this.codeColumn = column; this.showStackTrace = showStackTrace; @@ -84,18 +85,17 @@ public void setMessage(String message) { } - public int getCodeIndex() { - return codeIndex; + public SketchCode getCodeFile() { + return codeFile; } - public void setCodeIndex(int index) { - codeIndex = index; + public void setCodeFile(SketchCode file) { + codeFile = file; } - - - public boolean hasCodeIndex() { - return codeIndex != -1; + + public boolean hasCodeFile() { + return codeFile != null; } @@ -107,8 +107,7 @@ public int getCodeLine() { public void setCodeLine(int line) { this.codeLine = line; } - - + public boolean hasCodeLine() { return codeLine != -1; } @@ -117,8 +116,7 @@ public boolean hasCodeLine() { public void setCodeColumn(int column) { this.codeColumn = column; } - - + public int getCodeColumn() { return codeColumn; } From 05323fce6d799db9912b06c26a308d143facfd19 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 16:06:02 +0100 Subject: [PATCH 38/64] Rename SketchCode to SketchFile That name more accurately reflects its purpose: It represents a single file within a sketch. This just updates the class name and variable names referring to these objects and some comments, so no behaviour should change. --- app/src/processing/app/Editor.java | 26 +++---- app/src/processing/app/EditorHeader.java | 10 +-- app/src/processing/app/EditorTab.java | 38 ++++----- app/src/processing/app/SketchController.java | 78 +++++++++---------- app/src/processing/app/tools/FixEncoding.java | 4 +- arduino-core/src/cc/arduino/Compiler.java | 6 +- arduino-core/src/processing/app/Sketch.java | 60 +++++++------- .../app/{SketchCode.java => SketchFile.java} | 16 ++-- .../processing/app/debug/RunnerException.java | 14 ++-- 9 files changed, 126 insertions(+), 126 deletions(-) rename arduino-core/src/processing/app/{SketchCode.java => SketchFile.java} (94%) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index c8744c4fcea..b1c9ee5fc32 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1649,13 +1649,13 @@ public void selectPrevTab() { selectTab((currentTabIndex - 1 + tabs.size()) % tabs.size()); } - public EditorTab findTab(final SketchCode doc) { - return tabs.get(findTabIndex(doc)); + public EditorTab findTab(final SketchFile file) { + return tabs.get(findTabIndex(file)); } - public int findTabIndex(final SketchCode doc) { + public int findTabIndex(final SketchFile file) { for (int i = 0; i < tabs.size(); ++i) { - if (tabs.get(i).getSketchCode() == doc) + if (tabs.get(i).getSketchFile() == file) return i; } return -1; @@ -1669,9 +1669,9 @@ public void createTabs() { tabs.clear(); currentTabIndex = -1; tabs.ensureCapacity(sketch.getCodeCount()); - for (SketchCode code : sketch.getCodes()) { + for (SketchFile file : sketch.getFiles()) { try { - addTab(code, null); + addTab(file, null); } catch(IOException e) { // TODO: Improve / move error handling System.err.println(e); @@ -1683,15 +1683,15 @@ public void createTabs() { /** * Add a new tab. * - * @param code + * @param file * The file to show in the tab. * @param contents - * The contents to show in the tab, or null to load the - * contents from the given file. + * The contents to show in the tab, or null to load the contents from + * the given file. * @throws IOException */ - protected void addTab(SketchCode code, String contents) throws IOException { - EditorTab tab = new EditorTab(this, code, contents); + protected void addTab(SketchFile file, String contents) throws IOException { + EditorTab tab = new EditorTab(this, file, contents); tabs.add(tab); } @@ -2007,7 +2007,7 @@ private void updateTitle() { if (sketchController == null) { return; } - SketchCode current = getCurrentTab().getSketchCode(); + SketchFile current = getCurrentTab().getSketchFile(); if (current.isPrimary()) { setTitle(I18n.format(tr("{0} | Arduino {1}"), sketch.getName(), BaseNoGui.VERSION_NAME_LONG)); @@ -2570,7 +2570,7 @@ private void handlePrint() { printerJob.setPrintable(getCurrentTab().getTextArea()); } // set the name of the job to the code name - printerJob.setJobName(getCurrentTab().getSketchCode().getPrettyName()); + printerJob.setJobName(getCurrentTab().getSketchFile().getPrettyName()); if (printerJob.printDialog()) { try { diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index de929a86380..1407ebeae20 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -238,11 +238,11 @@ public void paintComponent(Graphics screen) { int x = 6; // offset from left edge of the component int i = 0; for (EditorTab tab : tabs) { - SketchCode code = tab.getSketchCode(); - String codeName = code.getPrettyName(); + SketchFile file = tab.getSketchFile(); + String filename = file.getPrettyName(); // if modified, add the li'l glyph next to the name - String text = " " + codeName + (code.isModified() ? " \u00A7" : " "); + String text = " " + filename + (file.isModified() ? " \u00A7" : " "); Graphics2D g2 = (Graphics2D) g; int textWidth = (int) @@ -320,9 +320,9 @@ public void rebuildMenu() { int i = 0; for (EditorTab tab : editor.getTabs()) { - SketchCode code = tab.getSketchCode(); + SketchFile file = tab.getSketchFile(); final int index = i++; - item = new JMenuItem(code.getPrettyName()); + item = new JMenuItem(file.getPrettyName()); item.addActionListener((ActionEvent e) -> { editor.selectTab(index); }); diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 9e2971e2412..5c6f74daede 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -64,11 +64,11 @@ /** * Single tab, editing a single file, in the main window. */ -public class EditorTab extends JPanel implements SketchCode.TextStorage { +public class EditorTab extends JPanel implements SketchFile.TextStorage { protected Editor editor; protected SketchTextArea textarea; protected RTextScrollPane scrollPane; - protected SketchCode code; + protected SketchFile file; protected boolean modified; /** Is external editing mode currently enabled? */ protected boolean external; @@ -78,32 +78,32 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage { * * @param editor * The Editor this tab runs in - * @param code + * @param file * The file to display in this tab * @param contents - * Initial contents to display in this tab. Can be used when - * editing a file that doesn't exist yet. If null is passed, - * code.load() is called and displayed instead. + * Initial contents to display in this tab. Can be used when editing + * a file that doesn't exist yet. If null is passed, code.load() is + * called and displayed instead. * @throws IOException */ - public EditorTab(Editor editor, SketchCode code, String contents) + public EditorTab(Editor editor, SketchFile file, String contents) throws IOException { super(new BorderLayout()); // Load initial contents contents from file if nothing was specified. if (contents == null) { - contents = code.load(); + contents = file.load(); modified = false; } else { modified = true; } this.editor = editor; - this.code = code; + this.file = file; RSyntaxDocument document = createDocument(contents); this.textarea = createTextArea(document); this.scrollPane = createScrollPane(this.textarea); - code.setStorage(this); + file.setStorage(this); applyPreferences(); add(this.scrollPane, BorderLayout.CENTER); @@ -291,14 +291,14 @@ public void applyPreferences() { textarea.setEditable(false); // Detach from the code, since we are no longer the authoritative source // for file contents. - code.setStorage(null); + file.setStorage(null); // Reload, in case the file contents already changed. reload(); } else { textarea.setBackground(Theme.getColor("editor.bgcolor")); textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight")); textarea.setEditable(true); - code.setStorage(this); + file.setStorage(this); // Reload once just before disabling external mode, to ensure we have // the latest contents. reload(); @@ -334,10 +334,10 @@ public void activated() { private void reload() { String text; try { - text = code.load(); + text = file.load(); } catch (IOException e) { System.err.println(I18n.format("Warning: Failed to reload file: \"{0}\"", - code.getFileName())); + file.getFileName())); return; } setText(text); @@ -363,10 +363,10 @@ public SketchController getSketch() { } /** - * Get the SketchCodeDocument that is being edited in this tab. + * Get the SketchFile that is being edited in this tab. */ - public SketchCode getSketchCode() { - return this.code; + public SketchFile getSketchFile() { + return this.file; } /** @@ -405,8 +405,8 @@ public boolean isModified() { } /** - * Clear modified status. Should only be called by SketchCode through - * the TextStorage interface. + * Clear modified status. Should only be called by SketchFile through the + * TextStorage interface. */ public void clearModified() { setModified(false); diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 9729c70e43c..e10ebeb552b 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -92,7 +92,7 @@ public void handleNewCode() { * Handler for the Rename Code menu option. */ public void handleRenameCode() { - SketchCode current = editor.getCurrentTab().getSketchCode(); + SketchFile current = editor.getCurrentTab().getSketchFile(); editor.status.clearState(); // make sure the user didn't hide the sketch folder @@ -133,7 +133,7 @@ public void handleRenameCode() { * where they diverge. */ protected void nameCode(String newName) { - SketchCode current = editor.getCurrentTab().getSketchCode(); + SketchFile current = editor.getCurrentTab().getSketchFile(); int currentIndex = editor.getCurrentTabIndex(); // make sure the user didn't hide the sketch folder @@ -197,12 +197,12 @@ protected void nameCode(String newName) { // In Arduino, we want to allow files with the same name but different // extensions, so compare the full names (including extensions). This // might cause problems: http://dev.processing.org/bugs/show_bug.cgi?id=543 - for (SketchCode c : sketch.getCodes()) { - if (newName.equalsIgnoreCase(c.getFileName()) && OSUtils.isWindows()) { + for (SketchFile file : sketch.getFiles()) { + if (newName.equalsIgnoreCase(file.getFileName()) && OSUtils.isWindows()) { Base.showMessage(tr("Error"), I18n.format( tr("A file named \"{0}\" already exists in \"{1}\""), - c.getFileName(), + file.getFileName(), sketch.getFolder().getAbsolutePath() )); return; @@ -219,9 +219,9 @@ protected void nameCode(String newName) { } if (renamingCode && current.isPrimary()) { - for (SketchCode code : sketch.getCodes()) { - if (sanitaryName.equalsIgnoreCase(code.getBaseName()) && - code.isExtension("cpp")) { + for (SketchFile file : sketch.getFiles()) { + if (sanitaryName.equalsIgnoreCase(file.getBaseName()) && + file.isExtension("cpp")) { Base.showMessage(tr("Error"), I18n.format(tr("You can't rename the sketch to \"{0}\"\n" + "because the sketch already has a .cpp file with that name."), @@ -272,7 +272,7 @@ protected void nameCode(String newName) { // first get the contents of the editor text area if (current.isModified()) { try { - // save this new SketchCode + // save this new SketchFile current.save(); } catch (Exception e) { Base.showWarning(tr("Error"), tr("Could not rename the sketch. (0)"), e); @@ -292,8 +292,8 @@ protected void nameCode(String newName) { // save each of the other tabs because this is gonna be re-opened try { - for (SketchCode code : sketch.getCodes()) { - code.save(); + for (SketchFile file : sketch.getFiles()) { + file.save(); } } catch (Exception e) { Base.showWarning(tr("Error"), tr("Could not rename the sketch. (1)"), e); @@ -351,9 +351,9 @@ protected void nameCode(String newName) { return; } ensureExistence(); - SketchCode code = new SketchCode(newFile, false); + SketchFile file = new SketchFile(newFile, false); try { - editor.addTab(code, ""); + editor.addTab(file, ""); } catch (IOException e) { Base.showWarning(tr("Error"), I18n.format( @@ -361,8 +361,8 @@ protected void nameCode(String newName) { ), e); return; } - sketch.addCode(code); - editor.selectTab(editor.findTabIndex(code)); + sketch.addFile(file); + editor.selectTab(editor.findTabIndex(file)); } // update the tabs @@ -374,7 +374,7 @@ protected void nameCode(String newName) { * Remove a piece of code from the sketch and from the disk. */ public void handleDeleteCode() throws IOException { - SketchCode current = editor.getCurrentTab().getSketchCode(); + SketchFile current = editor.getCurrentTab().getSketchFile(); editor.status.clearState(); // make sure the user didn't hide the sketch folder ensureExistence(); @@ -428,7 +428,7 @@ public void handleDeleteCode() throws IOException { } // remove code from the list - sketch.removeCode(current); + sketch.removeFile(current); // just set current tab to the main tab editor.selectTab(0); @@ -513,13 +513,13 @@ public boolean save() throws IOException { private boolean renameCodeToInoExtension(File pdeFile) { - for (SketchCode c : sketch.getCodes()) { - if (!c.getFile().equals(pdeFile)) + for (SketchFile file : sketch.getFiles()) { + if (!file.getFile().equals(pdeFile)) continue; String pdeName = pdeFile.getPath(); pdeName = pdeName.substring(0, pdeName.length() - 4) + ".ino"; - return c.renameTo(new File(pdeName)); + return file.renameTo(new File(pdeName)); } return false; } @@ -564,8 +564,8 @@ protected boolean saveAs() throws IOException { // make sure there doesn't exist a .cpp file with that name already // but ignore this situation for the first tab, since it's probably being // resaved (with the same name) to another location/folder. - for (SketchCode code : sketch.getCodes()) { - if (!code.isPrimary() && newName.equalsIgnoreCase(code.getBaseName())) { + for (SketchFile file : sketch.getFiles()) { + if (!file.isPrimary() && newName.equalsIgnoreCase(file.getBaseName())) { Base.showMessage(tr("Error"), I18n.format(tr("You can't save the sketch as \"{0}\"\n" + "because the sketch already has a file with that name."), newName @@ -609,10 +609,10 @@ protected boolean saveAs() throws IOException { newFolder.mkdirs(); // save the other tabs to their new location - for (SketchCode code : sketch.getCodes()) { - if (code.isPrimary()) continue; - File newFile = new File(newFolder, code.getFileName()); - code.saveAs(newFile); + for (SketchFile file : sketch.getFiles()) { + if (file.isPrimary()) continue; + File newFile = new File(newFolder, file.getFileName()); + file.saveAs(newFile); } // re-copy the data folder (this may take a while.. add progress bar?) @@ -623,7 +623,7 @@ protected boolean saveAs() throws IOException { // save the main tab with its new name File newFile = new File(newFolder, newName + ".ino"); - sketch.getCode(0).saveAs(newFile); + sketch.getFile(0).saveAs(newFile); editor.handleOpenUnchecked(newFile, editor.getCurrentTabIndex(), @@ -766,16 +766,16 @@ public boolean addFile(File sourceFile) { } if (!isData) { - SketchCode newCode = new SketchCode(destFile, false); + SketchFile newFile = new SketchFile(destFile, false); if (replacement) { - sketch.replaceCode(newCode); + sketch.replaceFile(newFile); } else { ensureExistence(); - sketch.addCode(newCode); + sketch.addFile(newFile); } - editor.selectTab(editor.findTabIndex(newCode)); + editor.selectTab(editor.findTabIndex(newFile)); } return true; } @@ -801,8 +801,8 @@ private void importLibrary(File jarPath) throws IOException { // import statements into the main sketch file (code[0]) // if the current code is a .java file, insert into current //if (current.flavor == PDE) { - SketchCode code = editor.getCurrentTab().getSketchCode(); - if (code.isExtension(Sketch.SKETCH_EXTENSIONS)) + SketchFile file = editor.getCurrentTab().getSketchFile(); + if (file.isExtension(Sketch.SKETCH_EXTENSIONS)) editor.selectTab(0); // could also scan the text in the file to see if each import @@ -896,8 +896,8 @@ private String saveSketchInTempFolder() throws IOException { File tempFolder = FileUtils.createTempFolder("arduino_modified_sketch_"); FileUtils.copy(sketch.getFolder(), tempFolder); - for (SketchCode sc : Stream.of(sketch.getCodes()).filter(SketchCode::isModified).collect(Collectors.toList())) { - Files.write(Paths.get(tempFolder.getAbsolutePath(), sc.getFileName()), sc.getProgram().getBytes()); + for (SketchFile file : Stream.of(sketch.getFiles()).filter(SketchFile::isModified).collect(Collectors.toList())) { + Files.write(Paths.get(tempFolder.getAbsolutePath(), file.getFileName()), file.getProgram().getBytes()); } return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getName()).toString(); @@ -990,8 +990,8 @@ private void ensureExistence() { try { sketch.getFolder().mkdirs(); - for (SketchCode code : sketch.getCodes()) { - code.save(); // this will force a save + for (SketchFile file : sketch.getFiles()) { + file.save(); // this will force a save } calcModified(); @@ -1026,8 +1026,8 @@ private boolean sketchIsSystemExample(String apath, String examplesPath) { } private boolean sketchFilesAreReadOnly() { - for (SketchCode code : sketch.getCodes()) { - if (code.isModified() && code.fileReadOnly() && code.fileExists()) { + for (SketchFile file : sketch.getFiles()) { + if (file.isModified() && file.fileReadOnly() && file.fileExists()) { return true; } } diff --git a/app/src/processing/app/tools/FixEncoding.java b/app/src/processing/app/tools/FixEncoding.java index 3ee5d055406..fa91f11c294 100644 --- a/app/src/processing/app/tools/FixEncoding.java +++ b/app/src/processing/app/tools/FixEncoding.java @@ -66,8 +66,8 @@ public void run() { } try { for (int i = 0; i < sketch.getCodeCount(); i++) { - SketchCode code = sketch.getCode(i); - editor.findTab(code).setText(loadWithLocalEncoding(code.getFile())); + SketchFile file = sketch.getFile(i); + editor.findTab(file).setText(loadWithLocalEncoding(file.getFile())); } } catch (IOException e) { String msg = diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java index aab66e8c0c8..c12e4329c53 100644 --- a/arduino-core/src/cc/arduino/Compiler.java +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -594,9 +594,9 @@ public void message(String s) { } private RunnerException placeException(String message, String fileName, int line) { - for (SketchCode code : sketch.getCodes()) { - if (new File(fileName).getName().equals(code.getFileName())) { - return new RunnerException(message, code, line); + for (SketchFile file : sketch.getFiles()) { + if (new File(fileName).getName().equals(file.getFileName())) { + return new RunnerException(message, file, line); } } return null; diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index e0d9429f407..1526b3a5d1c 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -41,11 +41,11 @@ public class Sketch { */ private String name; - private List codes = new ArrayList(); + private List files = new ArrayList(); - private static final Comparator CODE_DOCS_COMPARATOR = new Comparator() { + private static final Comparator CODE_DOCS_COMPARATOR = new Comparator() { @Override - public int compare(SketchCode x, SketchCode y) { + public int compare(SketchFile x, SketchFile y) { if (x.isPrimary() && !y.isPrimary()) return -1; if (y.isPrimary() && !x.isPrimary()) @@ -72,7 +72,7 @@ public int compare(SketchCode x, SketchCode y) { folder = new File(file.getParent()); dataFolder = new File(folder, "data"); - codes = listSketchFiles(true); + files = listSketchFiles(true); } static public File checkSketchFile(File file) { @@ -107,9 +107,9 @@ static public File checkSketchFile(File file) { * not. */ public boolean reload() throws IOException { - List reloaded = listSketchFiles(false); - if (!reloaded.equals(codes)) { - codes = reloaded; + List reloaded = listSketchFiles(false); + if (!reloaded.equals(files)) { + files = reloaded; return true; } return false; @@ -119,16 +119,16 @@ public boolean reload() throws IOException { * Scan this sketch's directory for files that should be loaded as * part of this sketch. Doesn't modify this SketchData instance, just * returns a filtered and sorted list of File objects ready to be - * passed to the SketchCode constructor. + * passed to the SketchFile constructor. * * @param showWarnings * When true, any invalid filenames will show a warning. */ - private List listSketchFiles(boolean showWarnings) throws IOException { - Set result = new TreeSet<>(CODE_DOCS_COMPARATOR); + private List listSketchFiles(boolean showWarnings) throws IOException { + Set result = new TreeSet<>(CODE_DOCS_COMPARATOR); for (File file : FileUtils.listFiles(folder, false, EXTENSIONS)) { if (BaseNoGui.isSanitaryName(file.getName())) { - result.add(new SketchCode(file, file.equals(primaryFile))); + result.add(new SketchFile(file, file.equals(primaryFile))); } else if (showWarnings) { System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), file.getName())); } @@ -153,18 +153,18 @@ public File prepareDataFolder() { } public void save() throws IOException { - for (SketchCode code : getCodes()) { - if (code.isModified()) - code.save(); + for (SketchFile file : getFiles()) { + if (file.isModified()) + file.save(); } } public int getCodeCount() { - return codes.size(); + return files.size(); } - public SketchCode[] getCodes() { - return codes.toArray(new SketchCode[0]); + public SketchFile[] getFiles() { + return files.toArray(new SketchFile[0]); } /** @@ -182,26 +182,26 @@ public String getMainFilePath() { //return code[0].file.getAbsolutePath(); } - public void addCode(SketchCode sketchCode) { - codes.add(sketchCode); - Collections.sort(codes, CODE_DOCS_COMPARATOR); + public void addFile(SketchFile file) { + files.add(file); + Collections.sort(files, CODE_DOCS_COMPARATOR); } - protected void replaceCode(SketchCode newCode) { - for (SketchCode code : codes) { - if (code.getFileName().equals(newCode.getFileName())) { - codes.set(codes.indexOf(code), newCode); + protected void replaceFile(SketchFile newCode) { + for (SketchFile file : files) { + if (file.getFileName().equals(newCode.getFileName())) { + files.set(files.indexOf(file), newCode); return; } } } - public SketchCode getCode(int i) { - return codes.get(i); + public SketchFile getFile(int i) { + return files.get(i); } - protected void removeCode(SketchCode which) { - if (!codes.remove(which)) + protected void removeFile(SketchFile which) { + if (!files.remove(which)) System.err.println("removeCode: internal error.. could not find code"); } @@ -221,8 +221,8 @@ public File getDataFolder() { * Is any of the files in this sketch modified? */ public boolean isModified() { - for (SketchCode code : codes) { - if (code.isModified()) + for (SketchFile file : files) { + if (file.isModified()) return true; } return false; diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchFile.java similarity index 94% rename from arduino-core/src/processing/app/SketchCode.java rename to arduino-core/src/processing/app/SketchFile.java index 576ccbac212..a817feccce7 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchFile.java @@ -1,5 +1,5 @@ /* - SketchCode - data class for a single file inside a sketch + SketchFile - data class for a single file inside a sketch Part of the Processing project - http://processing.org Copyright (c) 2004-08 Ben Fry and Casey Reas @@ -36,9 +36,9 @@ import static processing.app.I18n.tr; /** - * Represents a single tab of a sketch. + * Represents a file within a sketch. */ -public class SketchCode { +public class SketchFile { /** * File object for where this code is located @@ -53,7 +53,7 @@ public class SketchCode { /** * Interface for an in-memory storage of text file contents. This is * intended to allow a GUI to keep modified text in memory, and allow - * SketchCode to check for changes when needed. + * SketchFile to check for changes when needed. */ public static interface TextStorage { /** Get the current text */ @@ -77,14 +77,14 @@ public static interface TextStorage { private TextStorage storage; /** - * Create a new SketchCode + * Create a new SketchFile * * @param file - * The file this SketchCode represents + * The file this SketchFile represents * @param primary * Whether this file is the primary file of the sketch */ - public SketchCode(File file, boolean primary) { + public SketchFile(File file, boolean primary) { this.file = file; this.primary = primary; } @@ -211,7 +211,7 @@ public boolean isModified() { } public boolean equals(Object o) { - return (o instanceof SketchCode) && file.equals(((SketchCode) o).file); + return (o instanceof SketchFile) && file.equals(((SketchFile) o).file); } /** diff --git a/arduino-core/src/processing/app/debug/RunnerException.java b/arduino-core/src/processing/app/debug/RunnerException.java index c5aa7952051..5a60ac5a48b 100644 --- a/arduino-core/src/processing/app/debug/RunnerException.java +++ b/arduino-core/src/processing/app/debug/RunnerException.java @@ -23,7 +23,7 @@ package processing.app.debug; -import processing.app.SketchCode; +import processing.app.SketchFile; /** * An exception with a line number attached that occurs @@ -32,7 +32,7 @@ @SuppressWarnings("serial") public class RunnerException extends Exception { protected String message; - protected SketchCode codeFile; + protected SketchFile codeFile; protected int codeLine; protected int codeColumn; protected boolean showStackTrace; @@ -46,17 +46,17 @@ public RunnerException(String message, boolean showStackTrace) { this(message, null, -1, -1, showStackTrace); } - public RunnerException(String message, SketchCode file, int line) { + public RunnerException(String message, SketchFile file, int line) { this(message, file, line, -1, true); } - public RunnerException(String message, SketchCode file, int line, int column) { + public RunnerException(String message, SketchFile file, int line, int column) { this(message, file, line, column, true); } - public RunnerException(String message, SketchCode file, int line, int column, + public RunnerException(String message, SketchFile file, int line, int column, boolean showStackTrace) { this.message = message; this.codeFile = file; @@ -85,12 +85,12 @@ public void setMessage(String message) { } - public SketchCode getCodeFile() { + public SketchFile getCodeFile() { return codeFile; } - public void setCodeFile(SketchCode file) { + public void setCodeFile(SketchFile file) { codeFile = file; } From 56fa4cc38dffd26152083e599b5e3ad8cd2715f7 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 16:18:02 +0100 Subject: [PATCH 39/64] Remove Editor.stopHandler This handler was only always assigned the DefaultStopHandler, which did nothing. It was called in a few places, but since it never does anything, better remove it. For properly supporting stopping of external processes, some better architecture should be added instead. --- app/src/processing/app/Base.java | 11 ----------- app/src/processing/app/Editor.java | 26 -------------------------- 2 files changed, 37 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 210d0a07777..048b53e2cca 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -767,8 +767,6 @@ public void handleNewReplace() { if (!activeEditor.checkModified()) { return; // sketch was modified, and user canceled } - // Close the running window, avoid window boogers with multiple sketches - activeEditor.internalCloseRunner(); // Actually replace things handleNewReplaceImpl(); @@ -793,8 +791,6 @@ public void handleOpenReplace(File file) { if (!activeEditor.checkModified()) { return; // sketch was modified, and user canceled } - // Close the running window, avoid window boogers with multiple sketches - activeEditor.internalCloseRunner(); boolean loaded = activeEditor.handleOpenInternal(file); if (!loaded) { @@ -957,9 +953,6 @@ public boolean handleClose(Editor editor) { return false; } - // Close the running window, avoid window boogers with multiple sketches - editor.internalCloseRunner(); - if (editors.size() == 1) { // This will store the sketch count as zero editors.remove(editor); @@ -1014,10 +1007,6 @@ public boolean handleQuit() { } if (handleQuitEach()) { - // make sure running sketches close before quitting - for (Editor editor : editors) { - editor.internalCloseRunner(); - } // Save out the current prefs state PreferencesData.save(); diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index b1c9ee5fc32..b1547fff435 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -191,7 +191,6 @@ public boolean test(SketchController sketch) { Runnable presentHandler; private Runnable runAndSaveHandler; private Runnable presentAndSaveHandler; - private Runnable stopHandler; Runnable exportHandler; private Runnable exportAppHandler; @@ -1568,7 +1567,6 @@ private void resetHandlers() { presentHandler = new BuildHandler(true); runAndSaveHandler = new BuildHandler(false, true); presentAndSaveHandler = new BuildHandler(true, true); - stopHandler = new DefaultStopHandler(); exportHandler = new DefaultExportHandler(); exportAppHandler = new DefaultExportAppHandler(); } @@ -1727,7 +1725,6 @@ public void handleRun(final boolean verbose, Runnable verboseHandler, Runnable n } private void handleRun(final boolean verbose, Predicate shouldSavePredicate, Runnable verboseHandler, Runnable nonVerboseHandler) { - internalCloseRunner(); if (shouldSavePredicate.test(sketchController)) { handleSave(true); } @@ -1795,13 +1792,6 @@ public void addLineHighlight(int line) throws BadLocationException { getCurrentTab().getTextArea().setCaretPosition(getCurrentTab().getTextArea().getLineStartOffset(line)); } - private class DefaultStopHandler implements Runnable { - public void run() { - // TODO - // DAM: we should try to kill the compilation or upload process here. - } - } - /** * Implements Sketch → Stop, or pressing Stop on the toolbar. @@ -1809,8 +1799,6 @@ public void run() { private void handleStop() { // called by menu or buttons // toolbar.activate(EditorToolbar.STOP); - internalCloseRunner(); - toolbar.deactivateRun(); // toolbar.deactivate(EditorToolbar.STOP); @@ -1818,19 +1806,6 @@ private void handleStop() { // called by menu or buttons toFront(); } - - /** - * Handle internal shutdown of the runner. - */ - public void internalCloseRunner() { - - if (stopHandler != null) - try { - stopHandler.run(); - } catch (Exception e) { } - } - - /** * Check if the sketch is modified and ask user to save changes. * @return false if canceling the close/quit operation @@ -1912,7 +1887,6 @@ protected boolean checkModified() { */ protected void handleOpenUnchecked(File file, int codeIndex, int selStart, int selStop, int scrollPos) { - internalCloseRunner(); handleOpenInternal(file); // Replacing a document that may be untitled. If this is an actual // untitled document, then editor.untitled will be set by Base. From 022aa4d767ccdef8407a0c10e78f5a379560f6ad Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 16:21:57 +0100 Subject: [PATCH 40/64] Remove `Base.handle*Replace()` These methods dealt with opening up a sketch inside an existing editor window, but they were unused - all sketches open in a new window now. --- app/src/processing/app/Base.java | 40 -------------------------------- 1 file changed, 40 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 048b53e2cca..b5876aba4c5 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -760,46 +760,6 @@ public void handleNew() throws Exception { } - /** - * Replace the sketch in the current window with a new untitled document. - */ - public void handleNewReplace() { - if (!activeEditor.checkModified()) { - return; // sketch was modified, and user canceled - } - - // Actually replace things - handleNewReplaceImpl(); - } - - - protected void handleNewReplaceImpl() { - try { - File file = createNewUntitled(); - if (file != null) { - activeEditor.handleOpenInternal(file); - activeEditor.untitled = true; - } - - } catch (IOException e) { - activeEditor.statusError(e); - } - } - - - public void handleOpenReplace(File file) { - if (!activeEditor.checkModified()) { - return; // sketch was modified, and user canceled - } - - boolean loaded = activeEditor.handleOpenInternal(file); - if (!loaded) { - // replace the document without checking if that's ok - handleNewReplaceImpl(); - } - } - - /** * Prompt for a sketch to open, and open it in a new window. * From 7e83b778dad365d12d0b1cf31346c139b746a556 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 17 Dec 2015 17:18:41 +0100 Subject: [PATCH 41/64] Allow .cpp files named after the primary .ino file This limitation was added a long time ago, when the build system did not cope with this. The current build system handles this situation just fine, so this limitation can be lifted. --- app/src/processing/app/SketchController.java | 35 -------------------- 1 file changed, 35 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index e10ebeb552b..b90e965b138 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -209,28 +209,6 @@ protected void nameCode(String newName) { } } - // In Arduino, don't allow a .cpp file with the same name as the sketch, - // because the sketch is concatenated into a file with that name as part - // of the build process. - if (newName.equals(sketch.getName() + ".cpp")) { - Base.showMessage(tr("Error"), - tr("You can't have a .cpp file with the same name as the sketch.")); - return; - } - - if (renamingCode && current.isPrimary()) { - for (SketchFile file : sketch.getFiles()) { - if (sanitaryName.equalsIgnoreCase(file.getBaseName()) && - file.isExtension("cpp")) { - Base.showMessage(tr("Error"), - I18n.format(tr("You can't rename the sketch to \"{0}\"\n" - + "because the sketch already has a .cpp file with that name."), - sanitaryName)); - return; - } - } - } - File newFile = new File(sketch.getFolder(), newName); // if (newFile.exists()) { // yay! users will try anything @@ -561,19 +539,6 @@ protected boolean saveAs() throws IOException { File newFolder = new File(newParentDir, newName); - // make sure there doesn't exist a .cpp file with that name already - // but ignore this situation for the first tab, since it's probably being - // resaved (with the same name) to another location/folder. - for (SketchFile file : sketch.getFiles()) { - if (!file.isPrimary() && newName.equalsIgnoreCase(file.getBaseName())) { - Base.showMessage(tr("Error"), - I18n.format(tr("You can't save the sketch as \"{0}\"\n" + - "because the sketch already has a file with that name."), newName - )); - return false; - } - } - // check if the paths are identical if (newFolder.equals(sketch.getFolder())) { // just use "save" here instead, because the user will have received a From c651d6525a479de86f24caa0038c9e81fe0521c3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 18 Dec 2015 15:13:54 +0100 Subject: [PATCH 42/64] Do not store the "data" folder in Sketch Instead, just the File object when requested. It is not used during normal operation (just when adding files, or using save as), so no point in already creating the object in the constructor. --- arduino-core/src/processing/app/Sketch.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 1526b3a5d1c..5873f4bedc0 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -30,11 +30,6 @@ public class Sketch { */ private File folder; - /** - * data folder location for this sketch (may not exist yet) - */ - private File dataFolder; - /** * Name of sketch, which is the name of main file (without .pde or .java * extension) @@ -71,7 +66,6 @@ public int compare(SketchFile x, SketchFile y) { name = mainFilename.substring(0, mainFilename.length() - suffixLength); folder = new File(file.getParent()); - dataFolder = new File(folder, "data"); files = listSketchFiles(true); } @@ -146,6 +140,7 @@ private List listSketchFiles(boolean showWarnings) throws IOExceptio * about to be used. */ public File prepareDataFolder() { + File dataFolder = getDataFolder(); if (!dataFolder.exists()) { dataFolder.mkdirs(); } @@ -214,7 +209,7 @@ public File getFolder() { } public File getDataFolder() { - return dataFolder; + return new File(folder, "data"); } /** From 4735dc5f700af5a26f352d92e592a04899a1fede Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 18 Dec 2015 15:21:30 +0100 Subject: [PATCH 43/64] Let Sketch.getPrimaryFile return a SketchFile Previously, it returned a File object, which the Sketch separately stored from the primary SketchFile. By letting it just return the SketchFile, and let callers query that for the filename, Sketch does not need to store the File object itself and there is less chance of info getting out of sync. --- app/src/processing/app/SketchController.java | 2 +- arduino-core/src/cc/arduino/Compiler.java | 2 +- arduino-core/src/processing/app/Sketch.java | 18 ++++++------------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index b90e965b138..73ae22f9141 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -865,7 +865,7 @@ private String saveSketchInTempFolder() throws IOException { Files.write(Paths.get(tempFolder.getAbsolutePath(), file.getFileName()), file.getProgram().getBytes()); } - return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getName()).toString(); + return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getFileName()).toString(); } protected boolean exportApplet(boolean usingProgrammer) throws Exception { diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java index c12e4329c53..5040ea9f278 100644 --- a/arduino-core/src/cc/arduino/Compiler.java +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -155,7 +155,7 @@ public String build(CompilerProgressListener progListener, boolean exportHex) th size(prefs); - return sketch.getPrimaryFile().getName(); + return sketch.getPrimaryFile().getFileName(); } private String VIDPID() { diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 5873f4bedc0..beb2f3c750b 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -20,11 +20,6 @@ public class Sketch { public static final List OTHER_ALLOWED_EXTENSIONS = Arrays.asList("c", "cpp", "h", "hh", "hpp", "s"); public static final List EXTENSIONS = Stream.concat(SKETCH_EXTENSIONS.stream(), OTHER_ALLOWED_EXTENSIONS.stream()).collect(Collectors.toList()); - /** - * main pde file for this sketch. - */ - private File primaryFile; - /** * folder that contains this sketch */ @@ -57,8 +52,6 @@ public int compare(SketchFile x, SketchFile y) { * The primary file for this sketch. */ Sketch(File file) throws IOException { - primaryFile = file; - // get the name of the sketch by chopping .pde or .java // off of the main file name String mainFilename = primaryFile.getName(); @@ -122,7 +115,9 @@ private List listSketchFiles(boolean showWarnings) throws IOExceptio Set result = new TreeSet<>(CODE_DOCS_COMPARATOR); for (File file : FileUtils.listFiles(folder, false, EXTENSIONS)) { if (BaseNoGui.isSanitaryName(file.getName())) { - result.add(new SketchFile(file, file.equals(primaryFile))); + FileUtils.SplitFile split = FileUtils.splitFilename(file); + boolean isPrimary = split.basename.equals(folder.getName()) && SKETCH_EXTENSIONS.contains(split.extension); + result.add(new SketchFile(file, isPrimary)); } else if (showWarnings) { System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), file.getName())); } @@ -165,16 +160,15 @@ public SketchFile[] getFiles() { /** * Returns a file object for the primary .pde of this sketch. */ - public File getPrimaryFile() { - return primaryFile; + public SketchFile getPrimaryFile() { + return files.get(0); } /** * Returns path to the main .pde file for this sketch. */ public String getMainFilePath() { - return primaryFile.getAbsolutePath(); - //return code[0].file.getAbsolutePath(); + return getPrimaryFile().getFile().getAbsolutePath(); } public void addFile(SketchFile file) { From 934d7ac856d750e36f78fa8a44d1f94af2de0ddc Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 18 Dec 2015 16:23:30 +0100 Subject: [PATCH 44/64] Change `Compiler.pathToSketch` from String to File Keeping filenames as File objects for as long as possible is generally a good idea and this removes a dependency on `Sketch.getMainFilePath()`, so it can be removed later. --- app/src/processing/app/SketchController.java | 8 ++++---- arduino-core/src/cc/arduino/Compiler.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 73ae22f9141..7c40b5a8fa2 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -840,7 +840,7 @@ private String build(String buildPath, boolean verbose, boolean save) throws Run CompilerProgressListener progressListener = editor.status::progressUpdate; boolean deleteTemp = false; - String pathToSketch = sketch.getMainFilePath(); + File pathToSketch = sketch.getPrimaryFile().getFile(); if (sketch.isModified()) { // If any files are modified, make a copy of the sketch with the changes // saved, so arduino-builder will see the modifications. @@ -853,11 +853,11 @@ private String build(String buildPath, boolean verbose, boolean save) throws Run } finally { // Make sure we clean up any temporary sketch copy if (deleteTemp) - FileUtils.recursiveDelete(new File(pathToSketch).getParentFile()); + FileUtils.recursiveDelete(pathToSketch.getParentFile()); } } - private String saveSketchInTempFolder() throws IOException { + private File saveSketchInTempFolder() throws IOException { File tempFolder = FileUtils.createTempFolder("arduino_modified_sketch_"); FileUtils.copy(sketch.getFolder(), tempFolder); @@ -865,7 +865,7 @@ private String saveSketchInTempFolder() throws IOException { Files.write(Paths.get(tempFolder.getAbsolutePath(), file.getFileName()), file.getProgram().getBytes()); } - return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getFileName()).toString(); + return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getFileName()).toFile(); } protected boolean exportApplet(boolean usingProgrammer) throws Exception { diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java index 5040ea9f278..73dcaffcd13 100644 --- a/arduino-core/src/cc/arduino/Compiler.java +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -108,17 +108,17 @@ enum BuilderAction { private static final Pattern ERROR_FORMAT = Pattern.compile("(.+\\.\\w+):(\\d+)(:\\d+)*:\\s*error:\\s*(.*)\\s*", Pattern.MULTILINE | Pattern.DOTALL); - private final String pathToSketch; + private final File pathToSketch; private final Sketch sketch; private final String buildPath; private final boolean verbose; private RunnerException exception; public Compiler(Sketch data, String buildPath) { - this(data.getMainFilePath(), data, buildPath); + this(data.getPrimaryFile().getFile(), data, buildPath); } - public Compiler(String pathToSketch, Sketch sketch, String buildPath) { + public Compiler(File pathToSketch, Sketch sketch, String buildPath) { this.pathToSketch = pathToSketch; this.sketch = sketch; this.buildPath = buildPath; @@ -241,7 +241,7 @@ private void callArduinoBuilder(TargetBoard board, TargetPlatform platform, Targ commandLine.addArgument("-verbose", false); } - commandLine.addArgument("\"" + pathToSketch + "\"", false); + commandLine.addArgument("\"" + pathToSketch.getAbsolutePath() + "\"", false); if (verbose) { System.out.println(commandLine); From 79da6d05e4860783b624c5dc8a1ac45aa07c79a3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 18 Dec 2015 16:27:16 +0100 Subject: [PATCH 45/64] Do not store the sketch name in Sketch Sketch already stores the sketch folder, and the sketch name should be identical to the folder name. In the case where the filename passed to the sketch constructor is not the primary .ino file (named after the sketch), this will slightly change behaviour. However, the calling code should prevent this from happening, and with the old code, some internal assumptions were probably violated, so this changes makes handling this situation a bit more robust. Since the actual filename passed to Sketch is no longer used, it is no longer required to pass the name of the primary .ino file. At some point, the constructor should probably be changed to accept a folder name instead of a filename, but that would require a lot of changes to trace this back through the code, so this is something for later. --- arduino-core/src/processing/app/Sketch.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index beb2f3c750b..704364b888a 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -25,12 +25,6 @@ public class Sketch { */ private File folder; - /** - * Name of sketch, which is the name of main file (without .pde or .java - * extension) - */ - private String name; - private List files = new ArrayList(); private static final Comparator CODE_DOCS_COMPARATOR = new Comparator() { @@ -49,15 +43,9 @@ public int compare(SketchFile x, SketchFile y) { * on disk to get populate the list of files in this sketch. * * @param file - * The primary file for this sketch. + * Any file inside the sketch directory. */ Sketch(File file) throws IOException { - // get the name of the sketch by chopping .pde or .java - // off of the main file name - String mainFilename = primaryFile.getName(); - int suffixLength = getDefaultExtension().length() + 1; - name = mainFilename.substring(0, mainFilename.length() - suffixLength); - folder = new File(file.getParent()); files = listSketchFiles(true); } @@ -195,7 +183,7 @@ protected void removeFile(SketchFile which) { } public String getName() { - return name; + return folder.getName(); } public File getFolder() { From 5c00e39cec63d33d7b418da0e081efc4cfd0259b Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 18 Dec 2015 17:29:42 +0100 Subject: [PATCH 46/64] Remove `Base.copyDir()` There was already a nearly identical `FileUtils.copy()` that copies directories recursively. The only difference is that now hidden files *are* copied, but version control files (according the list in FileUtils) are not. Since this only affects the copying of the "data" directory during save as, this should not be much of a problem. --- app/src/processing/app/Base.java | 28 -------------------- app/src/processing/app/SketchController.java | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index b5876aba4c5..ba1cab8f585 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -2123,34 +2123,6 @@ static public void saveFile(String str, File file) throws IOException { } - /** - * Copy a folder from one place to another. This ignores all dot files and - * folders found in the source directory, to avoid copying silly .DS_Store - * files and potentially troublesome .svn folders. - */ - static public void copyDir(File sourceDir, - File targetDir) throws IOException { - targetDir.mkdirs(); - String files[] = sourceDir.list(); - if (files == null) { - throw new IOException("Unable to list files from " + sourceDir); - } - for (String file : files) { - // Ignore dot files (.DS_Store), dot folders (.svn) while copying - if (file.charAt(0) == '.') continue; - //if (files[i].equals(".") || files[i].equals("..")) continue; - File source = new File(sourceDir, file); - File target = new File(targetDir, file); - if (source.isDirectory()) { - //target.mkdirs(); - copyDir(source, target); - target.setLastModified(source.lastModified()); - } else { - copyFile(source, target); - } - } - } - /** * Remove all files in a directory and the directory itself. diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 7c40b5a8fa2..88d16f63bbd 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -583,7 +583,7 @@ protected boolean saveAs() throws IOException { // re-copy the data folder (this may take a while.. add progress bar?) if (sketch.getDataFolder().exists()) { File newDataFolder = new File(newFolder, "data"); - Base.copyDir(sketch.getDataFolder(), newDataFolder); + FileUtils.copy(sketch.getDataFolder(), newDataFolder); } // save the main tab with its new name From abe35e62d6646d6196b2ee86f14e962bc034a879 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 21 Dec 2015 16:48:59 +0100 Subject: [PATCH 47/64] Clean up pde to ino renaming This makes a few related changes: - `FileUtils.replaceExtension()` is introduced to handle replacing the .pde extension with .ino. - Instead of iterating .pde files on disk, this iterates SketchFiles in memory, saving another lookup from filename -> SketchFile later. - `SketchController.renameCodeToInoExtension()` is removed. Now it no longer needs to look up the SketchFile and FileUtils handles the extension replacement, this method did not have any reason to exist anymore. - Instead of hardcoding the .pde extension, a new Sketch.OLD_SKETCH_EXTENSIONS constant is introduced. --- app/src/processing/app/SketchController.java | 33 +++++++------------ arduino-core/src/processing/app/Sketch.java | 4 +-- .../src/processing/app/helpers/FileUtils.java | 27 +++++++++++++++ 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 88d16f63bbd..34466f7b205 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Optional; @@ -449,13 +450,13 @@ public boolean save() throws IOException { } // rename .pde files to .ino - File mainFile = new File(sketch.getMainFilePath()); - File mainFolder = mainFile.getParentFile(); - File[] pdeFiles = mainFolder.listFiles((dir, name) -> { - return name.toLowerCase().endsWith(".pde"); - }); + List oldFiles = new ArrayList<>(); + for (SketchFile file : sketch.getFiles()) { + if (file.isExtension(Sketch.OLD_SKETCH_EXTENSIONS)) + oldFiles.add(file); + } - if (pdeFiles != null && pdeFiles.length > 0) { + if (oldFiles.size() > 0) { if (PreferencesData.get("editor.update_extension") == null) { Object[] options = {tr("OK"), tr("Cancel")}; int result = JOptionPane.showOptionDialog(editor, @@ -480,8 +481,10 @@ public boolean save() throws IOException { if (PreferencesData.getBoolean("editor.update_extension")) { // Do rename of all .pde files to new .ino extension - for (File pdeFile : pdeFiles) - renameCodeToInoExtension(pdeFile); + for (SketchFile file : oldFiles) { + File newName = FileUtils.replaceExtension(file.getFile(), Sketch.DEFAULT_SKETCH_EXTENSION); + file.renameTo(newName); + } } } @@ -489,20 +492,6 @@ public boolean save() throws IOException { return true; } - - private boolean renameCodeToInoExtension(File pdeFile) { - for (SketchFile file : sketch.getFiles()) { - if (!file.getFile().equals(pdeFile)) - continue; - - String pdeName = pdeFile.getPath(); - pdeName = pdeName.substring(0, pdeName.length() - 4) + ".ino"; - return file.renameTo(new File(pdeName)); - } - return false; - } - - /** * Handles 'Save As' for a sketch. *

diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 704364b888a..134fb1f02a8 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -14,9 +14,9 @@ * This represents a single sketch, consisting of one or more files. */ public class Sketch { - public static final String DEFAULT_SKETCH_EXTENSION = "ino"; - public static final List SKETCH_EXTENSIONS = Arrays.asList(DEFAULT_SKETCH_EXTENSION, "pde"); + public static final List OLD_SKETCH_EXTENSIONS = Arrays.asList("pde"); + public static final List SKETCH_EXTENSIONS = Stream.concat(Stream.of(DEFAULT_SKETCH_EXTENSION), OLD_SKETCH_EXTENSIONS.stream()).collect(Collectors.toList()); public static final List OTHER_ALLOWED_EXTENSIONS = Arrays.asList("c", "cpp", "h", "hh", "hpp", "s"); public static final List EXTENSIONS = Stream.concat(SKETCH_EXTENSIONS.stream(), OTHER_ALLOWED_EXTENSIONS.stream()).collect(Collectors.toList()); diff --git a/arduino-core/src/processing/app/helpers/FileUtils.java b/arduino-core/src/processing/app/helpers/FileUtils.java index 4083a0a69de..a4fdb53e767 100644 --- a/arduino-core/src/processing/app/helpers/FileUtils.java +++ b/arduino-core/src/processing/app/helpers/FileUtils.java @@ -245,6 +245,26 @@ public static boolean hasExtension(File file, List extensions) { return extensions.contains(extension.toLowerCase()); } + /** + * Returns the given filename with the extension replaced by the one + * given. If the filename does not have an extension yet, one is + * added. + */ + public static String replaceExtension(String filename, String extension) { + SplitFile split = splitFilename(filename); + split.extension = extension; + return split.join(); + } + + /** + * Returns the given filename with the extension replaced by the one + * given. If the filename does not have an extension yet, one is + * added. + */ + public static File replaceExtension(File file, String extension) { + return new File(file.getParentFile(), replaceExtension(file.getName(), extension)); + } + /** * The result of a splitFilename call. */ @@ -256,6 +276,13 @@ public SplitFile(String basename, String extension) { public String basename; public String extension; + + public String join() { + if (extension.equals("")) + return basename; + else + return basename + "." + extension; + } } /** From 1c3f317927c09e1766a9d3f32cb802f2b4c6f6e1 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 21 Dec 2015 16:54:56 +0100 Subject: [PATCH 48/64] Use `File.getParentFile()` in Sketch constructor No need to call the File constructor ourselves, if `File.getParentFile()` can just do that for us. --- arduino-core/src/processing/app/Sketch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 134fb1f02a8..5c24db1e09d 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -46,7 +46,7 @@ public int compare(SketchFile x, SketchFile y) { * Any file inside the sketch directory. */ Sketch(File file) throws IOException { - folder = new File(file.getParent()); + folder = file.getParentFile(); files = listSketchFiles(true); } From 915df8b296f688bc08205bd5a951a7c7efafca98 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Mon, 21 Dec 2015 17:00:50 +0100 Subject: [PATCH 49/64] Refactor file adding and renaming, and save as handling This commits replaces a significant part of the code handling these features. A lot of responsibilities are moved from SketchController to Sketch, though the code involved is rewritten mostly. Most of the handling now happens inside Sketch, including various checks against the new filename. Basically SketchController processes the user input to decide what needs to be done, and Sketch checks if it can be done and does it. If problems occur, an IOException is thrown, using a translated error message that is shown by SketchController as-is. This might not be the best way to transfer error messages (regular IOExceptions might contain less-friendly messages), so this might need further improvement later. In addition to moving around code and responsibilities, this code also changes behaviour in some places: - Because Sketch and SketchFile are now in control of renames and saves, they can update their internal state after a rename. This removes the need for reloading the entire sketch after a rename or save as and allows `Editor.handleOpenUnchecked()` to be removed. - When renaming the entire sketch, all files used to be saved before renaming, since the sketch would be re-opened after renaming. Since the re-opening no longer happens, there is no longer a need to save the sketch, so any unsaved changes remain unsaved in the editor after renaming the sketch. - When renaming or adding new files, duplicate filenames are detected. Initially, this happened case sensitively, but it was later changed to use case insensitive matching to prevent problems on Windows (where filenames cannot differ in just case). To prevent complexity, this did not distinguish between systems. In commit 5fbf9621f6 (Sketch rename: allowig a case change rename if NOT on windows), the intention was to only do case insensitive checking on Windows, but it effectively disabled all checking on other systems, making the check not catch duplicate filenames at all. With this commit, all these checks are done using `File.equals()` instead of comparing strings, which is already aware of the case sensitivity of the platform and should act accordingly. - Some error messages were changed. - When adding a file, an empty file is not created directly, but only a SketchFile and EditorTab is added. When the sketch is saved, the file is created. - When importing a file that already exists (thus overwriting it), instead of replacing the SketchFile instance, this just lets the EditorTab reload its contents. This was broken since the introduction of EditorTab. The file would be replaced, but not this was not reflected in the editor, which is now fixed. This change allows `Sketch.replaceFile()` to be removed. - When importing a file that does not exist yet (thus adding it), a tab is now also added for it (in addition to a SketchFile). This was broken since the introduction of EditorTab, and would result in the file being added, but not shown in the editor. This commit adds a `Sketch.renameFileTo()` method, to rename a single file within the sketch. It would be better to integrate its contents into `Sketch.renameTo()`, but that does not have access to the `Sketch` instance it is contained in. This will be changed in a future commit. --- app/src/processing/app/Editor.java | 39 +-- app/src/processing/app/EditorTab.java | 2 +- app/src/processing/app/SketchController.java | 258 ++++-------------- arduino-core/src/processing/app/Sketch.java | 174 +++++++++++- .../src/processing/app/SketchFile.java | 13 +- .../src/processing/app/helpers/FileUtils.java | 23 +- 6 files changed, 269 insertions(+), 240 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index b1547fff435..e37a25b32eb 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1651,6 +1651,13 @@ public EditorTab findTab(final SketchFile file) { return tabs.get(findTabIndex(file)); } + /** + * Finds the index of the tab showing the given file. Matches the file against + * EditorTab.getSketchFile() using ==. + * + * @returns The index of the tab for the given file, or -1 if no such tab was + * found. + */ public int findTabIndex(final SketchFile file) { for (int i = 0; i < tabs.size(); ++i) { if (tabs.get(i).getSketchFile() == file) @@ -1659,6 +1666,21 @@ public int findTabIndex(final SketchFile file) { return -1; } + /** + * Finds the index of the tab showing the given file. Matches the file against + * EditorTab.getSketchFile().getFile() using equals. + * + * @returns The index of the tab for the given file, or -1 if no such tab was + * found. + */ + public int findTabIndex(final File file) { + for (int i = 0; i < tabs.size(); ++i) { + if (tabs.get(i).getSketchFile().getFile().equals(file)) + return i; + } + return -1; + } + /** * Create tabs for each of the current sketch's files, removing any existing * tabs. @@ -1880,23 +1902,6 @@ protected boolean checkModified() { } } - - /** - * Open a sketch from a particular path, but don't check to save changes. - * Used by Sketch.saveAs() to re-open a sketch after the "Save As" - */ - protected void handleOpenUnchecked(File file, int codeIndex, - int selStart, int selStop, int scrollPos) { - handleOpenInternal(file); - // Replacing a document that may be untitled. If this is an actual - // untitled document, then editor.untitled will be set by Base. - untitled = false; - - selectTab(codeIndex); - getCurrentTab().setSelection(selStart, selStop); - getCurrentTab().setScrollPosition(scrollPos); - } - /** * Second stage of open, occurs after having checked to see if the * modifications (if any) to the previous sketch need to be saved. diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 5c6f74daede..2954102d9f3 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -331,7 +331,7 @@ public void activated() { /** * Reload the contents of our file. */ - private void reload() { + public void reload() { String text; try { text = file.load(); diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 34466f7b205..b3ee39dff9f 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -134,213 +134,79 @@ public void handleRenameCode() { * where they diverge. */ protected void nameCode(String newName) { - SketchFile current = editor.getCurrentTab().getSketchFile(); - int currentIndex = editor.getCurrentTabIndex(); - // make sure the user didn't hide the sketch folder ensureExistence(); - // Add the extension here, this simplifies some of the logic below. - if (newName.indexOf('.') == -1) { - newName += "." + Sketch.DEFAULT_SKETCH_EXTENSION; - } - - // if renaming to the same thing as before, just ignore. - // also ignoring case here, because i don't want to write - // a bunch of special stuff for each platform - // (osx is case insensitive but preserving, windows insensitive, - // *nix is sensitive and preserving.. argh) - if (renamingCode) { - if (newName.equalsIgnoreCase(current.getFileName()) - && OSUtils.isWindows()) { - // exit quietly for the 'rename' case. - // if it's a 'new' then an error will occur down below - return; - } - } - newName = newName.trim(); if (newName.equals("")) return; - int dot = newName.indexOf('.'); - if (dot == 0) { + if (newName.charAt(0) == '.') { Base.showWarning(tr("Problem with rename"), tr("The name cannot start with a period."), null); return; } FileUtils.SplitFile split = FileUtils.splitFilename(newName); + if (split.extension.equals("")) + split.extension = Sketch.DEFAULT_SKETCH_EXTENSION; + if (!Sketch.EXTENSIONS.contains(split.extension)) { - Base.showWarning(tr("Problem with rename"), - I18n.format(tr("\".{0}\" is not a valid extension."), - split.extension), - null); + String msg = I18n.format(tr("\".{0}\" is not a valid extension."), + split.extension); + Base.showWarning(tr("Problem with rename"), msg, null); return; } - // Don't let the user create the main tab as a .java file instead of .pde - if (!split.extension.equals(Sketch.DEFAULT_SKETCH_EXTENSION)) { - if (renamingCode) { // If creating a new tab, don't show this error - if (current.isPrimary()) { // If this is the main tab, disallow - Base.showWarning(tr("Problem with rename"), - tr("The main file can't use an extension.\n" + - "(It may be time for your to graduate to a\n" + - "\"real\" programming environment)"), null); - return; - } - } - } - // Sanitize name - String sanitaryName = BaseNoGui.sanitizeName(split.basename); - newName = sanitaryName + "." + split.extension; - - // In Arduino, we want to allow files with the same name but different - // extensions, so compare the full names (including extensions). This - // might cause problems: http://dev.processing.org/bugs/show_bug.cgi?id=543 - for (SketchFile file : sketch.getFiles()) { - if (newName.equalsIgnoreCase(file.getFileName()) && OSUtils.isWindows()) { - Base.showMessage(tr("Error"), - I18n.format( - tr("A file named \"{0}\" already exists in \"{1}\""), - file.getFileName(), - sketch.getFolder().getAbsolutePath() - )); - return; - } - } - - - File newFile = new File(sketch.getFolder(), newName); -// if (newFile.exists()) { // yay! users will try anything -// Base.showMessage("Error", -// "A file named \"" + newFile + "\" already exists\n" + -// "in \"" + folder.getAbsolutePath() + "\""); -// return; -// } - -// File newFileHidden = new File(folder, newName + ".x"); -// if (newFileHidden.exists()) { -// // don't let them get away with it if they try to create something -// // with the same name as something hidden -// Base.showMessage("No Way", -// "A hidden tab with the same name already exists.\n" + -// "Use \"Unhide\" to bring it back."); -// return; -// } + split.basename = BaseNoGui.sanitizeName(split.basename); + newName = split.join(); if (renamingCode) { - if (current.isPrimary()) { - // get the new folder name/location - String folderName = newName.substring(0, newName.indexOf('.')); - File newFolder = new File(sketch.getFolder().getParentFile(), folderName); - if (newFolder.exists()) { - Base.showWarning(tr("Cannot Rename"), - I18n.format( - tr("Sorry, a sketch (or folder) named " + - "\"{0}\" already exists."), - newName - ), null); - return; - } - - // unfortunately this can't be a "save as" because that - // only copies the sketch files and the data folder - // however this *will* first save the sketch, then rename - - // first get the contents of the editor text area - if (current.isModified()) { - try { - // save this new SketchFile - current.save(); - } catch (Exception e) { - Base.showWarning(tr("Error"), tr("Could not rename the sketch. (0)"), e); - return; - } - } + SketchFile current = editor.getCurrentTab().getSketchFile(); - if (!current.renameTo(newFile)) { - Base.showWarning(tr("Error"), - I18n.format( - tr("Could not rename \"{0}\" to \"{1}\""), - current.getFileName(), - newFile.getName() - ), null); + if (current.isPrimary()) { + if (!split.extension.equals(Sketch.DEFAULT_SKETCH_EXTENSION)) { + Base.showWarning(tr("Problem with rename"), + tr("The main file cannot use an extension"), null); return; } - // save each of the other tabs because this is gonna be re-opened + // Primary file, rename the entire sketch + final File parent = sketch.getFolder().getParentFile(); + File newFolder = new File(parent, split.basename); try { - for (SketchFile file : sketch.getFiles()) { - file.save(); - } - } catch (Exception e) { - Base.showWarning(tr("Error"), tr("Could not rename the sketch. (1)"), e); - return; - } - - // now rename the sketch folder and re-open - boolean success = sketch.getFolder().renameTo(newFolder); - if (!success) { - Base.showWarning(tr("Error"), tr("Could not rename the sketch. (2)"), null); + sketch.renameTo(newFolder); + } catch (IOException e) { + // This does not pass on e, to prevent showing a backtrace for + // "normal" errors. + Base.showWarning(tr("Error"), e.getMessage(), null); return; } - // if successful, set base properties for the sketch - - File newMainFile = new File(newFolder, newName + ".ino"); - // having saved everything and renamed the folder and the main .pde, - // use the editor to re-open the sketch to re-init state - // (unfortunately this will kill positions for carets etc) - editor.handleOpenUnchecked(newMainFile, - currentIndex, - editor.getCurrentTab().getSelectionStart(), - editor.getCurrentTab().getSelectionStop(), - editor.getCurrentTab().getScrollPosition()); - - // get the changes into the sketchbook menu - // (re-enabled in 0115 to fix bug #332) editor.base.rebuildSketchbookMenus(); - - } else { // else if something besides code[0] - if (!current.renameTo(newFile)) { - Base.showWarning(tr("Error"), - I18n.format( - tr("Could not rename \"{0}\" to \"{1}\""), - current.getFileName(), - newFile.getName() - ), null); + } else { + // Non-primary file, rename just that file + try { + sketch.renameFileTo(current, newName); + } catch (IOException e) { + // This does not pass on e, to prevent showing a backtrace for + // "normal" errors. + Base.showWarning(tr("Error"), e.getMessage(), null); return; } } } else { // creating a new file + SketchFile file; try { - if (!newFile.createNewFile()) { - // Already checking for IOException, so make our own. - throw new IOException(tr("createNewFile() returned false")); - } - } catch (IOException e) { - Base.showWarning(tr("Error"), - I18n.format( - "Could not create the file \"{0}\" in \"{1}\"", - newFile, - sketch.getFolder().getAbsolutePath() - ), e); - return; - } - ensureExistence(); - SketchFile file = new SketchFile(newFile, false); - try { + file = sketch.addFile(newName); editor.addTab(file, ""); } catch (IOException e) { - Base.showWarning(tr("Error"), - I18n.format( - "Failed to open tab for new file" - ), e); + // This does not pass on e, to prevent showing a backtrace for + // "normal" errors. + Base.showWarning(tr("Error"), e.getMessage(), null); return; } - sketch.addFile(file); editor.selectTab(editor.findTabIndex(file)); } @@ -559,35 +425,17 @@ protected boolean saveAs() throws IOException { // in fact, you can't do this on windows because the file dialog // will instead put you inside the folder, but it happens on osx a lot. - // now make a fresh copy of the folder - newFolder.mkdirs(); - - // save the other tabs to their new location - for (SketchFile file : sketch.getFiles()) { - if (file.isPrimary()) continue; - File newFile = new File(newFolder, file.getFileName()); - file.saveAs(newFile); - } - - // re-copy the data folder (this may take a while.. add progress bar?) - if (sketch.getDataFolder().exists()) { - File newDataFolder = new File(newFolder, "data"); - FileUtils.copy(sketch.getDataFolder(), newDataFolder); + try { + sketch.saveAs(newFolder); + } catch (IOException e) { + // This does not pass on e, to prevent showing a backtrace for "normal" + // errors. + Base.showWarning(tr("Error"), e.getMessage(), null); } - - // save the main tab with its new name - File newFile = new File(newFolder, newName + ".ino"); - sketch.getFile(0).saveAs(newFile); - - editor.handleOpenUnchecked(newFile, - editor.getCurrentTabIndex(), - editor.getCurrentTab().getSelectionStart(), - editor.getCurrentTab().getSelectionStop(), - editor.getCurrentTab().getScrollPosition()); - // Name changed, rebuild the sketch menus //editor.sketchbook.rebuildMenusAsync(); editor.base.rebuildSketchbookMenus(); + editor.header.rebuild(); // Make sure that it's not an untitled sketch setUntitled(false); @@ -720,16 +568,24 @@ public boolean addFile(File sourceFile) { } if (!isData) { - SketchFile newFile = new SketchFile(destFile, false); - + int tabIndex; if (replacement) { - sketch.replaceFile(newFile); - + tabIndex = editor.findTabIndex(destFile); + editor.getTabs().get(tabIndex).reload(); } else { - ensureExistence(); - sketch.addFile(newFile); + SketchFile sketchFile; + try { + sketchFile = sketch.addFile(destFile.getName()); + editor.addTab(sketchFile, null); + } catch (IOException e) { + // This does not pass on e, to prevent showing a backtrace for + // "normal" errors. + Base.showWarning(tr("Error"), e.getMessage(), null); + return false; + } + tabIndex = editor.findTabIndex(sketchFile); } - editor.selectTab(editor.findTabIndex(newFile)); + editor.selectTab(tabIndex); } return true; } diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 5c24db1e09d..b9f22341176 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -159,20 +159,6 @@ public String getMainFilePath() { return getPrimaryFile().getFile().getAbsolutePath(); } - public void addFile(SketchFile file) { - files.add(file); - Collections.sort(files, CODE_DOCS_COMPARATOR); - } - - protected void replaceFile(SketchFile newCode) { - for (SketchFile file : files) { - if (file.getFileName().equals(newCode.getFileName())) { - files.set(files.indexOf(file), newCode); - return; - } - } - } - public SketchFile getFile(int i) { return files.get(i); } @@ -204,4 +190,164 @@ public boolean isModified() { } return false; } + + /** + * Finds the file with the given filename and returns its index. + * Returns -1 when the file was not found. + */ + public int findFileIndex(File filename) { + int i = 0; + for (SketchFile file : files) { + if (file.getFile().equals(filename)) + return i; + i++; + } + return -1; + } + + /** + * Check if renaming/saving this sketch to the given folder would + * cause a problem because: 1. The new folder already exists 2. + * Renaming the primary file would cause a conflict with an existing + * file. If so, an IOEXception is thrown. If not, the name of the new + * primary file is returned. + */ + protected File checkNewFoldername(File newFolder) throws IOException { + String newPrimary = FileUtils.addExtension(newFolder.getName(), DEFAULT_SKETCH_EXTENSION); + // Verify the new folder does not exist yet + if (newFolder.exists()) { + String msg = I18n.format(tr("Sorry, the folder \"{0}\" already exists."), newFolder.getAbsoluteFile()); + throw new IOException(msg); + } + + // If the folder is actually renamed (as opposed to moved somewhere + // else), check for conflicts using the new filename, but the + // existing folder name. + if(newFolder.getName() != folder.getName()) + checkNewFilename(new File(folder, newPrimary)); + + return new File(newFolder, newPrimary); + } + + /** + * Check if renaming or adding a file would cause a problem because + * the file already exists in this sketch. If so, an IOEXception is + * thrown. + * + * @param newFile + * The filename of the new file, or the new name for an + * existing file. + */ + protected void checkNewFilename(File newFile) throws IOException { + // Verify that the sketch doesn't have a filem with the new name + // already, other than the current primary (index 0) + if (findFileIndex(newFile) >= 0) { + String msg = I18n.format(tr("The sketch already contains a file named \"{0}\""), newFile.getName()); + throw new IOException(msg); + } + + } + + /** + * Rename this sketch' folder to the given name. Unlike saveAs(), this + * moves the sketch directory, not leaving anything in the old place. + * This operation does not *save* the sketch, so the files on disk are + * moved, but not modified. + * + * @param newFolder + * The new folder name for this sketch. The new primary + * file's name will be derived from this. + * + * @throws IOException + * When a problem occurs. The error message should be + * already translated. + */ + public void renameTo(File newFolder) throws IOException { + // Check intended rename (throws if there is a problem) + File newPrimary = checkNewFoldername(newFolder); + + // Rename the sketch folder + if (!getFolder().renameTo(newFolder)) + throw new IOException(tr("Failed to rename sketch folder")); + + folder = newFolder; + + // Tell each file about its new name + for (SketchFile file : files) + file.renamedTo(new File(newFolder, file.getFileName())); + + // And finally, rename the primary file + if (!getPrimaryFile().renameTo(newPrimary)) + throw new IOException(tr("Failed to rename primary sketch file")); + } + + /** + * Rename the given file to get the given name. + * + * @param sketchfile + * The SketchFile to be renamed. + * @param newName + * The new name, including extension, excluding directory + * name. + * @throws IOException + * When a problem occurs, or is expected to occur. The error + * message should be already translated. + */ + public void renameFileTo(SketchFile sketchfile, String newName) throws IOException { + File newFile = new File(folder, newName); + checkNewFilename(newFile); + sketchfile.renameTo(newFile); + } + + public SketchFile addFile(String newName) throws IOException { + // Check the name will not cause any conflicts + File newFile = new File(folder, newName); + checkNewFilename(newFile); + + // Add a new sketchFile + SketchFile sketchFile = new SketchFile(newFile, false); + files.add(sketchFile); + Collections.sort(files, CODE_DOCS_COMPARATOR); + + return sketchFile; + } + + /** + * Save this sketch under the new name given. Unlike renameTo(), this + * leaves the existing sketch in place. + * + * @param newFolder + * The new folder name for this sketch. The new primary + * file's name will be derived from this. + * + * @throws IOException + * When a problem occurs. The error message should be + * already translated. + */ + public void saveAs(File newFolder) throws IOException { + // Check intented rename (throws if there is a problem) + File newPrimary = checkNewFoldername(newFolder); + + // Create the folder + if (!newFolder.mkdirs()) { + String msg = I18n.format(tr("Could not create directory \"{0}\""), newFolder.getAbsolutePath()); + throw new IOException(msg); + } + + // Save the files to their new location + for (SketchFile file : files) { + if (file.isPrimary()) + file.saveAs(newPrimary); + else + file.saveAs(new File(newFolder, file.getFileName())); + } + + folder = newFolder; + + // Copy the data folder (this may take a while.. add progress bar?) + if (getDataFolder().exists()) { + File newDataFolder = new File(newFolder, "data"); + FileUtils.copy(getDataFolder(), newDataFolder); + } + } } diff --git a/arduino-core/src/processing/app/SketchFile.java b/arduino-core/src/processing/app/SketchFile.java index a817feccce7..b1a4a77a717 100644 --- a/arduino-core/src/processing/app/SketchFile.java +++ b/arduino-core/src/processing/app/SketchFile.java @@ -154,12 +154,18 @@ private boolean deleteCompiledFilesFrom(Path tempBuildFolder) throws IOException protected boolean renameTo(File what) { boolean success = file.renameTo(what); - if (success) { - file = what; - } + if (success) + renamedTo(what); return success; } + /** + * Should be called when this file was renamed and renameTo could not + * be used (e.g. when renaming the entire sketch directory). + */ + protected void renamedTo(File what) { + file = what; + } /* * Returns the filename include extension. @@ -264,5 +270,6 @@ public void saveAs(File newFile) throws IOException { return; /* Nothing to do */ BaseNoGui.saveFile(storage.getText(), newFile); + renamedTo(newFile); } } diff --git a/arduino-core/src/processing/app/helpers/FileUtils.java b/arduino-core/src/processing/app/helpers/FileUtils.java index a4fdb53e767..fdfdf313428 100644 --- a/arduino-core/src/processing/app/helpers/FileUtils.java +++ b/arduino-core/src/processing/app/helpers/FileUtils.java @@ -265,6 +265,24 @@ public static File replaceExtension(File file, String extension) { return new File(file.getParentFile(), replaceExtension(file.getName(), extension)); } + /** + * Adds an extension to the given filename. If it already contains + * one, an additional extension is added. If the extension is the + * empty string, the file is returned unmodified. + */ + public static String addExtension(String filename, String extension) { + return extension.equals("") ? filename : (filename + "." + extension); + } + + /** + * Adds an extension to the given filename. If it already contains + * one, an additional extension is added. If the extension is the + * empty string, the file is returned unmodified. + */ + public static File addExtension(File file, String extension) { + return new File(file.getParentFile(), addExtension(file.getName(), extension)); + } + /** * The result of a splitFilename call. */ @@ -278,10 +296,7 @@ public SplitFile(String basename, String extension) { public String extension; public String join() { - if (extension.equals("")) - return basename; - else - return basename + "." + extension; + return addExtension(basename, extension); } } From ce06179a51cc05c4a8486de00958fb8cf8ffcd01 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 16:31:31 +0100 Subject: [PATCH 50/64] Remove `Base.removeDir()` and `Base.removeDescendants()` These methods shouldn't really be in Base (or BaseNoGui, which did the actual work), especially since there is already a `FileUtils.recursiveDelete()` which just does the same thing. This commit removes the code from Base and BaseNoGui and instead uses the method from FileUtils. There is one difference between these methods: the Base methods did not delete files if the "compiler.save_build_files" preference was set. However, the Base methods were only used when deleting a sketch, or deleting an existing folder before overwriting it on save as, so this preference didn't actually do what it was supposed to anyway, so dropping it shouldn't be a problem. --- app/src/processing/app/Base.java | 20 --------- app/src/processing/app/SketchController.java | 4 +- .../src/processing/app/BaseNoGui.java | 43 ------------------- 3 files changed, 2 insertions(+), 65 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index ba1cab8f585..db93aaf16a6 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -2123,26 +2123,6 @@ static public void saveFile(String str, File file) throws IOException { } - - /** - * Remove all files in a directory and the directory itself. - */ - static public void removeDir(File dir) { - BaseNoGui.removeDir(dir); - } - - - /** - * Recursively remove all files within a directory, - * used with removeDir(), or when the contents of a dir - * should be removed, but not the directory itself. - * (i.e. when cleaning temp files from lib/build) - */ - static public void removeDescendants(File dir) { - BaseNoGui.removeDescendants(dir); - } - - /** * Calculate the size of the contents of a folder. * Used to determine whether sketches are empty or not. diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index b3ee39dff9f..6b9f3681734 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -254,7 +254,7 @@ public void handleDeleteCode() throws IOException { // to do a save on the handleNew() // delete the entire sketch - Base.removeDir(sketch.getFolder()); + FileUtils.recursiveDelete(sketch.getFolder()); // get the changes into the sketchbook menu //sketchbook.rebuildMenus(); @@ -420,7 +420,7 @@ protected boolean saveAs() throws IOException { // its contents before copying everything over // (user will have already been warned) if (newFolder.exists()) { - Base.removeDir(newFolder); + FileUtils.recursiveDelete(newFolder); } // in fact, you can't do this on windows because the file dialog // will instead put you inside the folder, but it happens on osx a lot. diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index 4a9e9cf415b..1d9ada73de2 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -991,49 +991,6 @@ static public void initParameters(String args[]) throws Exception { PreferencesData.init(absoluteFile(preferencesFile)); } - /** - * Recursively remove all files within a directory, - * used with removeDir(), or when the contents of a dir - * should be removed, but not the directory itself. - * (i.e. when cleaning temp files from lib/build) - */ - static public void removeDescendants(File dir) { - if (!dir.exists()) return; - - String files[] = dir.list(); - if (files == null) { - return; - } - - for (String file : files) { - if (file.equals(".") || file.equals("..")) continue; - File dead = new File(dir, file); - if (!dead.isDirectory()) { - if (!PreferencesData.getBoolean("compiler.save_build_files")) { - if (!dead.delete()) { - // temporarily disabled - System.err.println(I18n.format(tr("Could not delete {0}"), dead)); - } - } - } else { - removeDir(dead); - //dead.delete(); - } - } - } - - /** - * Remove all files in a directory and the directory itself. - */ - static public void removeDir(File dir) { - if (dir.exists()) { - removeDescendants(dir); - if (!dir.delete()) { - System.err.println(I18n.format(tr("Could not delete {0}"), dir)); - } - } - } - /** * Produce a sanitized name that fits our standards for likely to work. *

From 15e6e04f2c60ee38f9d7bf0848a1b569da8fcdb3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 16:35:18 +0100 Subject: [PATCH 51/64] Remove unused `Base.listFiles()` methods --- app/src/processing/app/Base.java | 42 -------------------------------- 1 file changed, 42 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index db93aaf16a6..592860b6f8b 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -2148,48 +2148,6 @@ static public int calcFolderSize(File folder) { return size; } - - /** - * Recursively creates a list of all files within the specified folder, - * and returns a list of their relative paths. - * Ignores any files/folders prefixed with a dot. - */ - static public String[] listFiles(String path, boolean relative) { - return listFiles(new File(path), relative); - } - - - static public String[] listFiles(File folder, boolean relative) { - String path = folder.getAbsolutePath(); - Vector vector = new Vector(); - listFiles(relative ? (path + File.separator) : "", path, vector); - String outgoing[] = new String[vector.size()]; - vector.copyInto(outgoing); - return outgoing; - } - - - static protected void listFiles(String basePath, - String path, Vector vector) { - File folder = new File(path); - String list[] = folder.list(); - if (list == null) return; - - for (int i = 0; i < list.length; i++) { - if (list[i].charAt(0) == '.') continue; - - File file = new File(path, list[i]); - String newPath = file.getAbsolutePath(); - if (newPath.startsWith(basePath)) { - newPath = newPath.substring(basePath.length()); - } - vector.add(newPath); - if (file.isDirectory()) { - listFiles(basePath, newPath, vector); - } - } - } - public void handleAddLibrary() { JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home")); fileChooser.setDialogTitle(tr("Select a zip file or a folder containing the library you'd like to add")); From 8689273616731f99488c1708dfe50b26e8286034 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 16:42:14 +0100 Subject: [PATCH 52/64] Let SketchFile store a reference to the Sketch it belongs to This allows simplifying some other things later. --- arduino-core/src/processing/app/Sketch.java | 4 ++-- arduino-core/src/processing/app/SketchFile.java | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index b9f22341176..3e7e7554909 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -105,7 +105,7 @@ private List listSketchFiles(boolean showWarnings) throws IOExceptio if (BaseNoGui.isSanitaryName(file.getName())) { FileUtils.SplitFile split = FileUtils.splitFilename(file); boolean isPrimary = split.basename.equals(folder.getName()) && SKETCH_EXTENSIONS.contains(split.extension); - result.add(new SketchFile(file, isPrimary)); + result.add(new SketchFile(this, file, isPrimary)); } else if (showWarnings) { System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), file.getName())); } @@ -305,7 +305,7 @@ public SketchFile addFile(String newName) throws IOException { checkNewFilename(newFile); // Add a new sketchFile - SketchFile sketchFile = new SketchFile(newFile, false); + SketchFile sketchFile = new SketchFile(this, newFile, false); files.add(sketchFile); Collections.sort(files, CODE_DOCS_COMPARATOR); diff --git a/arduino-core/src/processing/app/SketchFile.java b/arduino-core/src/processing/app/SketchFile.java index b1a4a77a717..4a73bd249af 100644 --- a/arduino-core/src/processing/app/SketchFile.java +++ b/arduino-core/src/processing/app/SketchFile.java @@ -45,6 +45,11 @@ public class SketchFile { */ private File file; + /** + * The sketch this file belongs to. + */ + private Sketch sketch; + /** * Is this the primary file in the sketch? */ @@ -79,12 +84,15 @@ public static interface TextStorage { /** * Create a new SketchFile * + * @param sketch + * The sketch this file belongs to * @param file * The file this SketchFile represents * @param primary * Whether this file is the primary file of the sketch */ - public SketchFile(File file, boolean primary) { + public SketchFile(Sketch sketch, File file, boolean primary) { + this.sketch = sketch; this.file = file; this.primary = primary; } From 087620ff5e58b525063b5a875d355092b82d1844 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 16:51:49 +0100 Subject: [PATCH 53/64] Merge `Sketch.renameFileTo()` into `SketchFile.renameTo()` Now that SketchFile keeps a reference to its Sketch, `SketchFile.renameTo()` can call `Sketch.checkNewFilename()`, so there is no need for the renaming itself to go through Sketch. This changes the parameter for `SketchFile.renameTo()` from File to String, to enforce that only the filename is changed, not the directory name. --- app/src/processing/app/SketchController.java | 4 ++-- arduino-core/src/processing/app/Sketch.java | 20 +--------------- .../src/processing/app/SketchFile.java | 24 +++++++++++++++---- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 6b9f3681734..f667c10c88c 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -187,7 +187,7 @@ protected void nameCode(String newName) { } else { // Non-primary file, rename just that file try { - sketch.renameFileTo(current, newName); + current.renameTo(newName); } catch (IOException e) { // This does not pass on e, to prevent showing a backtrace for // "normal" errors. @@ -349,7 +349,7 @@ public boolean save() throws IOException { // Do rename of all .pde files to new .ino extension for (SketchFile file : oldFiles) { File newName = FileUtils.replaceExtension(file.getFile(), Sketch.DEFAULT_SKETCH_EXTENSION); - file.renameTo(newName); + file.renameTo(newName.getName()); } } } diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 3e7e7554909..ee6dee70e5f 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -277,27 +277,9 @@ public void renameTo(File newFolder) throws IOException { file.renamedTo(new File(newFolder, file.getFileName())); // And finally, rename the primary file - if (!getPrimaryFile().renameTo(newPrimary)) - throw new IOException(tr("Failed to rename primary sketch file")); + getPrimaryFile().renameTo(newPrimary.getName()); } - /** - * Rename the given file to get the given name. - * - * @param sketchfile - * The SketchFile to be renamed. - * @param newName - * The new name, including extension, excluding directory - * name. - * @throws IOException - * When a problem occurs, or is expected to occur. The error - * message should be already translated. - */ - public void renameFileTo(SketchFile sketchfile, String newName) throws IOException { - File newFile = new File(folder, newName); - checkNewFilename(newFile); - sketchfile.renameTo(newFile); - } public SketchFile addFile(String newName) throws IOException { // Check the name will not cause any conflicts diff --git a/arduino-core/src/processing/app/SketchFile.java b/arduino-core/src/processing/app/SketchFile.java index 4a73bd249af..607e87005e2 100644 --- a/arduino-core/src/processing/app/SketchFile.java +++ b/arduino-core/src/processing/app/SketchFile.java @@ -160,11 +160,25 @@ private boolean deleteCompiledFilesFrom(Path tempBuildFolder) throws IOException return true; } - protected boolean renameTo(File what) { - boolean success = file.renameTo(what); - if (success) - renamedTo(what); - return success; + /** + * Rename the given file to get the given name. + * + * @param newName + * The new name, including extension, excluding directory + * name. + * @throws IOException + * When a problem occurs, or is expected to occur. The error + * message should be already translated. + */ + public void renameTo(String newName) throws IOException { + File newFile = new File(file.getParentFile(), newName); + sketch.checkNewFilename(newFile); + if (file.renameTo(newFile)) { + renamedTo(newFile); + } else { + String msg = I18n.format(tr("Failed to rename \"{0}\" to \"{1}\""), file.getName(), newName); + throw new IOException(msg); + } } /** From 0b79fe105e6d6fc5397b31e49d5a0b5aebfff5cb Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 17:01:10 +0100 Subject: [PATCH 54/64] Move sketch deletion from SketchController into Sketch This isn't much code, but it makes deletion more consistent with renaming and saving with the SketchController handling the UI part and Sketch actually doing the delete. --- app/src/processing/app/SketchController.java | 14 +------------- arduino-core/src/processing/app/Sketch.java | 7 +++++++ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index f667c10c88c..1b98e9704ee 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -250,20 +250,8 @@ public void handleDeleteCode() throws IOException { options[0]); if (result == JOptionPane.YES_OPTION) { if (current.isPrimary()) { - // need to unset all the modified flags, otherwise tries - // to do a save on the handleNew() - - // delete the entire sketch - FileUtils.recursiveDelete(sketch.getFolder()); - - // get the changes into the sketchbook menu - //sketchbook.rebuildMenus(); - - // make a new sketch, and i think this will rebuild the sketch menu - //editor.handleNewUnchecked(); - //editor.handleClose2(); + sketch.delete(); editor.base.handleClose(editor); - } else { // delete the file if (!current.deleteFile(BaseNoGui.getBuildFolder(sketch).toPath())) { diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index ee6dee70e5f..7c24c5e46de 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -332,4 +332,11 @@ public void saveAs(File newFolder) throws IOException { FileUtils.copy(getDataFolder(), newDataFolder); } } + + /** + * Deletes this entire sketch from disk. + */ + void delete() { + FileUtils.recursiveDelete(folder); + } } From 6a420d1649de83962187317c333560335cbe0c40 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 17:02:49 +0100 Subject: [PATCH 55/64] Rename `SketchFile.deleteFile()` to `delete()` The extra "File" in the name was a bit redundant, and this is more consistent with `Sketch.delete()`. --- app/src/processing/app/SketchController.java | 2 +- arduino-core/src/processing/app/SketchFile.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 1b98e9704ee..2eb29203e01 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -254,7 +254,7 @@ public void handleDeleteCode() throws IOException { editor.base.handleClose(editor); } else { // delete the file - if (!current.deleteFile(BaseNoGui.getBuildFolder(sketch).toPath())) { + if (!current.delete(BaseNoGui.getBuildFolder(sketch).toPath())) { Base.showMessage(tr("Couldn't do it"), I18n.format(tr("Could not delete \"{0}\"."), current.getFileName())); return; diff --git a/arduino-core/src/processing/app/SketchFile.java b/arduino-core/src/processing/app/SketchFile.java index 607e87005e2..8fba3429601 100644 --- a/arduino-core/src/processing/app/SketchFile.java +++ b/arduino-core/src/processing/app/SketchFile.java @@ -128,7 +128,7 @@ protected boolean fileReadOnly() { } - protected boolean deleteFile(Path tempBuildFolder) throws IOException { + protected boolean delete(Path tempBuildFolder) throws IOException { if (!file.delete()) { return false; } From 300e0d71b4189865f99b865cf12be589f875d12e Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 17:04:53 +0100 Subject: [PATCH 56/64] Let `SketchFile.delete()` call `Sketch.removeFile()` Previously, callers of `SketchFile.delete()` would also call `Sketch.removeFile()`, but letting SketchFile handle this is more robust. This is possible now that SketchFile keeps a reference to Sketch and makes updating the Sketch file list less fragile. Eventually this might be further decoupled by letting SketchFile broadcast a "deleted" event instead. --- app/src/processing/app/SketchController.java | 3 --- arduino-core/src/processing/app/SketchFile.java | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 2eb29203e01..eb4737942c5 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -260,9 +260,6 @@ public void handleDeleteCode() throws IOException { return; } - // remove code from the list - sketch.removeFile(current); - // just set current tab to the main tab editor.selectTab(0); diff --git a/arduino-core/src/processing/app/SketchFile.java b/arduino-core/src/processing/app/SketchFile.java index 8fba3429601..aac1c3489f4 100644 --- a/arduino-core/src/processing/app/SketchFile.java +++ b/arduino-core/src/processing/app/SketchFile.java @@ -142,6 +142,8 @@ protected boolean delete(Path tempBuildFolder) throws IOException { } } + sketch.removeFile(this); + return true; } From 39dea53bae30a20481da4def40ce2ca13625d3d3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 17:11:30 +0100 Subject: [PATCH 57/64] Let SketchFile figure out if it is primary by itself Previously, the caller of the constructor did this, but now that SketchFile keeps a reference to its containing sketch, it can figure it out itself. --- arduino-core/src/processing/app/Sketch.java | 6 ++---- arduino-core/src/processing/app/SketchFile.java | 6 ++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 7c24c5e46de..e372b7a3208 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -103,9 +103,7 @@ private List listSketchFiles(boolean showWarnings) throws IOExceptio Set result = new TreeSet<>(CODE_DOCS_COMPARATOR); for (File file : FileUtils.listFiles(folder, false, EXTENSIONS)) { if (BaseNoGui.isSanitaryName(file.getName())) { - FileUtils.SplitFile split = FileUtils.splitFilename(file); - boolean isPrimary = split.basename.equals(folder.getName()) && SKETCH_EXTENSIONS.contains(split.extension); - result.add(new SketchFile(this, file, isPrimary)); + result.add(new SketchFile(this, file)); } else if (showWarnings) { System.err.println(I18n.format(tr("File name {0} is invalid: ignored"), file.getName())); } @@ -287,7 +285,7 @@ public SketchFile addFile(String newName) throws IOException { checkNewFilename(newFile); // Add a new sketchFile - SketchFile sketchFile = new SketchFile(this, newFile, false); + SketchFile sketchFile = new SketchFile(this, newFile); files.add(sketchFile); Collections.sort(files, CODE_DOCS_COMPARATOR); diff --git a/arduino-core/src/processing/app/SketchFile.java b/arduino-core/src/processing/app/SketchFile.java index aac1c3489f4..19d30006533 100644 --- a/arduino-core/src/processing/app/SketchFile.java +++ b/arduino-core/src/processing/app/SketchFile.java @@ -91,10 +91,12 @@ public static interface TextStorage { * @param primary * Whether this file is the primary file of the sketch */ - public SketchFile(Sketch sketch, File file, boolean primary) { + public SketchFile(Sketch sketch, File file) { this.sketch = sketch; this.file = file; - this.primary = primary; + FileUtils.SplitFile split = FileUtils.splitFilename(file); + this.primary = split.basename.equals(sketch.getFolder().getName()) + && Sketch.SKETCH_EXTENSIONS.contains(split.extension); } /** From dfe842d5d1c7ce233fe6da89b60e1d501307442f Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 17:41:37 +0100 Subject: [PATCH 58/64] Store the build path used in Sketch Previously, everywhere where it was needed, the path was requested from BaseNoGui. Because the path is based on a hash of the sketch filename, every caller would get the same path for the same sketch. However, it makes more sense to store the path used for a given sketch inside the Sketch object. This prevents having to pass around or regenerate the build path everywhere, and no longer requires the build path to be deterministic (though it still is in this commit). This allows removing some methods and constructors of which two versions were available - one with a build path argument and one without. --- app/src/processing/app/SketchController.java | 60 +++---------------- arduino-core/src/cc/arduino/Compiler.java | 11 ++-- .../src/cc/arduino/UploaderUtils.java | 4 +- .../src/processing/app/BaseNoGui.java | 22 +------ arduino-core/src/processing/app/Sketch.java | 31 ++++++++++ 5 files changed, 50 insertions(+), 78 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index eb4737942c5..6c0ad2638c4 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -254,7 +254,7 @@ public void handleDeleteCode() throws IOException { editor.base.handleClose(editor); } else { // delete the file - if (!current.delete(BaseNoGui.getBuildFolder(sketch).toPath())) { + if (!current.delete(sketch.getBuildPath().toPath())) { Base.showMessage(tr("Couldn't do it"), I18n.format(tr("Could not delete \"{0}\"."), current.getFileName())); return; @@ -615,43 +615,6 @@ private void importLibrary(File jarPath) throws IOException { editor.getCurrentTab().setSelection(0, 0); // scroll to start } - /** - * 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 { - - /** - * Run the build inside the temporary build folder. - * @return null if compilation failed, main class name if not - * @throws RunnerException - */ - public String build(boolean verbose, boolean save) throws RunnerException, PreferencesMapException, IOException { - return build(BaseNoGui.getBuildFolder(sketch).getAbsolutePath(), verbose, save); - } - /** * Preprocess and compile all the code for this sketch. * @@ -661,7 +624,7 @@ public String build(boolean verbose, boolean save) throws RunnerException, Prefe * * @return null if compilation failed, main class name if not */ - private String build(String buildPath, boolean verbose, boolean save) throws RunnerException, PreferencesMapException, IOException { + public String build(boolean verbose, boolean save) throws RunnerException, PreferencesMapException, IOException { // run the preprocessor editor.status.progressUpdate(20); @@ -679,7 +642,7 @@ private String build(String buildPath, boolean verbose, boolean save) throws Run } try { - return new Compiler(pathToSketch, sketch, buildPath).build(progressListener, save); + return new Compiler(pathToSketch, sketch).build(progressListener, save); } finally { // Make sure we clean up any temporary sketch copy if (deleteTemp) @@ -698,20 +661,13 @@ private File saveSketchInTempFolder() throws IOException { return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getFileName()).toFile(); } - protected boolean exportApplet(boolean usingProgrammer) throws Exception { - return exportApplet(BaseNoGui.getBuildFolder(sketch).getAbsolutePath(), usingProgrammer); - } - - /** * Handle export to applet. */ - private boolean exportApplet(String appletPath, boolean usingProgrammer) - throws Exception { - + protected boolean exportApplet(boolean usingProgrammer) throws Exception { // build the sketch editor.status.progressNotice(tr("Compiling sketch...")); - String foundName = build(appletPath, false, false); + String foundName = build(false, false); // (already reported) error during export, exit this function if (foundName == null) return false; @@ -725,12 +681,12 @@ private boolean exportApplet(String appletPath, boolean usingProgrammer) // } editor.status.progressNotice(tr("Uploading...")); - boolean success = upload(appletPath, foundName, usingProgrammer); + boolean success = upload(foundName, usingProgrammer); editor.status.progressUpdate(100); return success; } - private boolean upload(String buildPath, String suggestedClassName, boolean usingProgrammer) throws Exception { + private boolean upload(String suggestedClassName, boolean usingProgrammer) throws Exception { Uploader uploader = new UploaderUtils().getUploaderByPreferences(false); @@ -751,7 +707,7 @@ private boolean upload(String buildPath, String suggestedClassName, boolean usin List warningsAccumulator = new LinkedList<>(); try { - success = new UploaderUtils().upload(sketch, uploader, buildPath, suggestedClassName, usingProgrammer, false, warningsAccumulator); + success = new UploaderUtils().upload(sketch, uploader, suggestedClassName, usingProgrammer, false, warningsAccumulator); } finally { if (uploader.requiresAuthorization() && !success) { PreferencesData.remove(uploader.getAuthorizationKey()); diff --git a/arduino-core/src/cc/arduino/Compiler.java b/arduino-core/src/cc/arduino/Compiler.java index 73dcaffcd13..e0788cf7b6a 100644 --- a/arduino-core/src/cc/arduino/Compiler.java +++ b/arduino-core/src/cc/arduino/Compiler.java @@ -110,22 +110,23 @@ enum BuilderAction { private final File pathToSketch; private final Sketch sketch; - private final String buildPath; + private String buildPath; private final boolean verbose; private RunnerException exception; - public Compiler(Sketch data, String buildPath) { - this(data.getPrimaryFile().getFile(), data, buildPath); + public Compiler(Sketch data) { + this(data.getPrimaryFile().getFile(), data); } - public Compiler(File pathToSketch, Sketch sketch, String buildPath) { + public Compiler(File pathToSketch, Sketch sketch) { this.pathToSketch = pathToSketch; this.sketch = sketch; - this.buildPath = buildPath; this.verbose = PreferencesData.getBoolean("build.verbose"); } public String build(CompilerProgressListener progListener, boolean exportHex) throws RunnerException, PreferencesMapException, IOException { + this.buildPath = sketch.getBuildPath().getAbsolutePath(); + TargetBoard board = BaseNoGui.getTargetBoard(); if (board == null) { throw new RunnerException("Board is not selected"); diff --git a/arduino-core/src/cc/arduino/UploaderUtils.java b/arduino-core/src/cc/arduino/UploaderUtils.java index e49bcb6730b..a80eaf5065c 100644 --- a/arduino-core/src/cc/arduino/UploaderUtils.java +++ b/arduino-core/src/cc/arduino/UploaderUtils.java @@ -56,7 +56,7 @@ public Uploader getUploaderByPreferences(boolean noUploadPort) { return new UploaderFactory().newUploader(target.getBoards().get(board), boardPort, noUploadPort); } - public boolean upload(Sketch data, Uploader uploader, String buildPath, String suggestedClassName, boolean usingProgrammer, boolean noUploadPort, List warningsAccumulator) throws Exception { + public boolean upload(Sketch data, Uploader uploader, String suggestedClassName, boolean usingProgrammer, boolean noUploadPort, List warningsAccumulator) throws Exception { if (uploader == null) uploader = getUploaderByPreferences(noUploadPort); @@ -75,7 +75,7 @@ public boolean upload(Sketch data, Uploader uploader, String buildPath, String s } try { - success = uploader.uploadUsingPreferences(data.getFolder(), buildPath, suggestedClassName, usingProgrammer, warningsAccumulator); + success = uploader.uploadUsingPreferences(data.getFolder(), data.getBuildPath().getAbsolutePath(), suggestedClassName, usingProgrammer, warningsAccumulator); } finally { if (uploader.requiresAuthorization() && !success) { PreferencesData.remove(uploader.getAuthorizationKey()); diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java index 1d9ada73de2..cb71cc6a36c 100644 --- a/arduino-core/src/processing/app/BaseNoGui.java +++ b/arduino-core/src/processing/app/BaseNoGui.java @@ -12,7 +12,6 @@ import cc.arduino.packages.DiscoveryManager; import cc.arduino.packages.Uploader; import com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.logging.impl.LogFactoryImpl; import org.apache.commons.logging.impl.NoOpLog; @@ -28,7 +27,6 @@ import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; -import java.nio.file.Files; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -121,18 +119,6 @@ static public String getAvrBasePath() { return path; } - static public File getBuildFolder(Sketch data) throws IOException { - File buildFolder; - if (PreferencesData.get("build.path") != null) { - buildFolder = absoluteFile(PreferencesData.get("build.path")); - Files.createDirectories(buildFolder.toPath()); - } else { - buildFolder = FileUtils.createTempFolder("build", DigestUtils.md5Hex(data.getMainFilePath()) + ".tmp"); - DeleteFilesOnShutdown.add(buildFolder); - } - return buildFolder; - } - static public PreferencesMap getBoardPreferences() { TargetBoard board = getTargetBoard(); if (board == null) @@ -472,7 +458,6 @@ static public void init(String[] args) throws Exception { // SketchData data = new SketchData(file); // File tempBuildFolder = getBuildFolder(); Sketch data = new Sketch(absoluteFile(parser.getFilenames().get(0))); - File tempBuildFolder = getBuildFolder(data); // Sketch.exportApplet() // - calls Sketch.prepare() that calls Sketch.ensureExistence() @@ -481,7 +466,7 @@ static public void init(String[] args) throws Exception { if (!data.getFolder().exists()) { showError(tr("No sketch"), tr("Can't find the sketch in the specified path"), null); } - String suggestedClassName = new Compiler(data, tempBuildFolder.getAbsolutePath()).build(null, false); + String suggestedClassName = new Compiler(data).build(null, false); if (suggestedClassName == null) { showError(tr("Error while verifying"), tr("An error occurred while verifying the sketch"), null); } @@ -490,7 +475,7 @@ static public void init(String[] args) throws Exception { Uploader uploader = new UploaderUtils().getUploaderByPreferences(parser.isNoUploadPort()); if (uploader.requiresAuthorization() && !PreferencesData.has(uploader.getAuthorizationKey())) showError("...", "...", null); try { - success = new UploaderUtils().upload(data, uploader, tempBuildFolder.getAbsolutePath(), suggestedClassName, parser.isDoUseProgrammer(), parser.isNoUploadPort(), warningsAccumulator); + success = new UploaderUtils().upload(data, uploader, suggestedClassName, parser.isDoUseProgrammer(), parser.isNoUploadPort(), warningsAccumulator); showMessage(tr("Done uploading"), tr("Done uploading")); } finally { if (uploader.requiresAuthorization() && !success) { @@ -518,7 +503,6 @@ static public void init(String[] args) throws Exception { // File tempBuildFolder = getBuildFolder(); // data.load(); Sketch data = new Sketch(absoluteFile(path)); - File tempBuildFolder = getBuildFolder(data); // Sketch.prepare() calls Sketch.ensureExistence() // Sketch.build(verbose) calls Sketch.ensureExistence() and set progressListener and, finally, calls Compiler.build() @@ -526,7 +510,7 @@ static public void init(String[] args) throws Exception { // if (!data.getFolder().exists()) showError(...); // String ... = Compiler.build(data, tempBuildFolder.getAbsolutePath(), tempBuildFolder, null, verbose); if (!data.getFolder().exists()) showError(tr("No sketch"), tr("Can't find the sketch in the specified path"), null); - String suggestedClassName = new Compiler(data, tempBuildFolder.getAbsolutePath()).build(null, false); + String suggestedClassName = new Compiler(data).build(null, false); if (suggestedClassName == null) showError(tr("Error while verifying"), tr("An error occurred while verifying the sketch"), null); showMessage(tr("Done compiling"), tr("Done compiling")); } catch (Exception e) { diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index e372b7a3208..6e0078f21e6 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -2,10 +2,14 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.codec.digest.DigestUtils; + +import cc.arduino.files.DeleteFilesOnShutdown; import processing.app.helpers.FileUtils; import static processing.app.I18n.tr; @@ -27,6 +31,8 @@ public class Sketch { private List files = new ArrayList(); + private File buildPath; + private static final Comparator CODE_DOCS_COMPARATOR = new Comparator() { @Override public int compare(SketchFile x, SketchFile y) { @@ -161,6 +167,31 @@ public SketchFile getFile(int i) { return files.get(i); } + /** + * Gets the build path for this sketch. The first time this is called, + * a build path is generated and created and the same path is returned + * on all subsequent calls. + * + * This takes into account the build.path preference. If it is set, + * that path is always returned, and the directory is *not* deleted on + * shutdown. If the preference is not set, a pathname in a + * temporary directory is generated, which is automatically deleted on + * shutdown. + */ + public File getBuildPath() throws IOException { + if (buildPath == null) { + if (PreferencesData.get("build.path") != null) { + buildPath = BaseNoGui.absoluteFile(PreferencesData.get("build.path")); + Files.createDirectories(buildPath.toPath()); + } else { + buildPath = FileUtils.createTempFolder("build", DigestUtils.md5Hex(getMainFilePath()) + ".tmp"); + DeleteFilesOnShutdown.add(buildPath); + } + } + + return buildPath; + } + protected void removeFile(SketchFile which) { if (!files.remove(which)) System.err.println("removeCode: internal error.. could not find code"); From 8e954d2f1705f80cead5ab94e61baf3fc4f88154 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 17:47:48 +0100 Subject: [PATCH 59/64] Randomize the the build path name Previously, this used a hash of the sketch filename, so the same build path would be generated for the same sketch between multiple compilations. Now that the build path is stored, this requirement has disappeared, so a random filename can be generated again. While here, this commit also changes the prefix from "build" to "arduino_build_", which makes it a bit more clear what the directory's purpose is. --- app/src/processing/app/SketchController.java | 1 - arduino-core/src/processing/app/Sketch.java | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index 6c0ad2638c4..c6a3a5a9107 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -27,7 +27,6 @@ import cc.arduino.CompilerProgressListener; import cc.arduino.UploaderUtils; import cc.arduino.packages.Uploader; -import org.apache.commons.codec.digest.DigestUtils; import processing.app.debug.RunnerException; import processing.app.forms.PasswordAuthorizationDialog; import processing.app.helpers.FileUtils; diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 6e0078f21e6..41e6484b1b2 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -7,8 +7,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.codec.digest.DigestUtils; - import cc.arduino.files.DeleteFilesOnShutdown; import processing.app.helpers.FileUtils; @@ -174,7 +172,7 @@ public SketchFile getFile(int i) { * * This takes into account the build.path preference. If it is set, * that path is always returned, and the directory is *not* deleted on - * shutdown. If the preference is not set, a pathname in a + * shutdown. If the preference is not set, a random pathname in a * temporary directory is generated, which is automatically deleted on * shutdown. */ @@ -184,7 +182,7 @@ public File getBuildPath() throws IOException { buildPath = BaseNoGui.absoluteFile(PreferencesData.get("build.path")); Files.createDirectories(buildPath.toPath()); } else { - buildPath = FileUtils.createTempFolder("build", DigestUtils.md5Hex(getMainFilePath()) + ".tmp"); + buildPath = FileUtils.createTempFolder("arduino_build_"); DeleteFilesOnShutdown.add(buildPath); } } From 7b1d0c658f77ba2934d304aea6990bc922735f77 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Dec 2015 17:54:22 +0100 Subject: [PATCH 60/64] In `Base.handleOpen()`, compare Files rather than Strings Comparing a File object automatically takes care of filesystem case sensitivity, whereas strings do not, so this makes the comparison slightly more reliable. --- app/src/processing/app/Base.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 592860b6f8b..f74f454bee5 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -821,9 +821,8 @@ protected Editor handleOpen(File file, int[] storedLocation, int[] defaultLocati if (!file.exists()) return null; // Cycle through open windows to make sure that it's not already open. - String path = file.getAbsolutePath(); for (Editor editor : editors) { - if (editor.getSketch().getMainFilePath().equals(path)) { + if (editor.getSketch().getPrimaryFile().getFile().equals(file)) { editor.toFront(); return editor; } From c74065a46ab4b1863949265d94385dd81e5599a9 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 30 Dec 2015 15:57:37 +0100 Subject: [PATCH 61/64] Update comment This comment still talked about Processing related stuff which doesn't really apply anymore. --- app/src/processing/app/SketchController.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java index c6a3a5a9107..8c6dd1c8519 100644 --- a/app/src/processing/app/SketchController.java +++ b/app/src/processing/app/SketchController.java @@ -471,15 +471,11 @@ public 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. - *

+ * + * Supported code files will be copied to the sketch folder. All other files + * will be copied to the "data" folder (which is created if it does not exist + * yet). + * * @return true if successful. */ public boolean addFile(File sourceFile) { From bf1784143895af5392900fe534d6ae84bcf550dc Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 30 Dec 2015 18:03:39 +0100 Subject: [PATCH 62/64] TOMERGE: Fix EditorTab.setText Turns out the approached used to keep caret and scroll position only keeps the scroll position. More code is needed to also keep caret position. --- app/src/processing/app/EditorTab.java | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 2954102d9f3..1770cbbfbce 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -390,11 +390,32 @@ public void setText(String what) { // its absolute position (number of characters from the start), which isn't // always perfect, but the best we can do without making a diff of the old // and new text and some guesswork. + // Note that we cannot use textarea.setText() here, since that first removes + // text and then inserts the new text. Even with NEVER_UPDATE, the caret + // always makes sure to stay valid, so first removing all text makes it + // reset to 0. Also note that simply saving and restoring the caret position + // will work, but then the scroll position might change in response to the + // caret position. DefaultCaret caret = (DefaultCaret) textarea.getCaret(); int policy = caret.getUpdatePolicy(); caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); - textarea.setText(what); - caret.setUpdatePolicy(policy); + try { + RSyntaxDocument doc = (RSyntaxDocument)textarea.getDocument(); + int oldLength = doc.getLength(); + // The undo manager already seems to group the insert and remove together + // automatically, but better be explicit about it. + textarea.getUndoManager().beginInternalAtomicEdit(); + try { + doc.insertString(oldLength, what, null); + doc.remove(0, oldLength); + } catch (BadLocationException e) { + System.err.println("Unexpected failure replacing text"); + } finally { + textarea.getUndoManager().endInternalAtomicEdit(); + } + } finally { + caret.setUpdatePolicy(policy); + } } /** From 3c8a3b9b062521c7572740d2ad494329e5ddb985 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 30 Dec 2015 20:27:08 +0100 Subject: [PATCH 63/64] TOMERGE: Fix undo menu items Turns out I dropped a setUndoManager call somewhere, as well typed Redo instead of Undo somewhere. --- app/src/processing/app/Editor.java | 2 +- app/src/processing/app/EditorTab.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index e37a25b32eb..b5f96aef2b1 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1496,7 +1496,7 @@ public UndoAction() { public void actionPerformed(ActionEvent e) { try { - getCurrentTab().handleRedo(); + getCurrentTab().handleUndo(); } catch (CannotUndoException ex) { //System.out.println("Unable to undo: " + ex); //ex.printStackTrace(); diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 1770cbbfbce..3ac09bc8054 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -53,6 +53,7 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; import org.fife.ui.rtextarea.Gutter; import org.fife.ui.rtextarea.RTextScrollPane; +import org.fife.ui.rtextarea.RUndoManager; import processing.app.helpers.DocumentTextChangeListener; import processing.app.syntax.ArduinoTokenMakerFactory; @@ -107,8 +108,10 @@ public EditorTab(Editor editor, SketchFile file, String contents) applyPreferences(); add(this.scrollPane, BorderLayout.CENTER); - UndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor); - ((RSyntaxDocument)textarea.getDocument()).addUndoableEditListener(undo); + RUndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, + this.editor); + document.addUndoableEditListener(undo); + textarea.setUndoManager(undo); } private RSyntaxDocument createDocument(String contents) { From 8300081ad93ecba62cce5ddb14a77259a990ea01 Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Mon, 11 Jan 2016 11:51:10 +0100 Subject: [PATCH 64/64] TOBEMERGED: fix error in commit 39ffb7f7 --- app/src/processing/app/EditorHeader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index 1407ebeae20..b0e3c9c0300 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -327,7 +327,6 @@ public void rebuildMenu() { editor.selectTab(index); }); menu.add(item); - i++; } } }