Skip to content

Commit 8725bb1

Browse files
matthijskooijmanfacchinm
authored andcommitted
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.
1 parent 283ccc1 commit 8725bb1

File tree

7 files changed

+151
-140
lines changed

7 files changed

+151
-140
lines changed

app/src/processing/app/Base.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -608,11 +608,11 @@ protected void handleActivated(Editor whichEditor) {
608608
activeEditor.rebuildRecentSketchesMenu();
609609
if (PreferencesData.getBoolean("editor.external")) {
610610
try {
611-
int previousCaretPosition = activeEditor.getCurrentTab().getTextArea().getCaretPosition();
612-
activeEditor.getSketch().load(true);
613-
if (previousCaretPosition < activeEditor.getCurrentTab().getText().length()) {
614-
activeEditor.getCurrentTab().getTextArea().setCaretPosition(previousCaretPosition);
615-
}
611+
// If the list of files on disk changed, recreate the tabs for them
612+
if (activeEditor.getSketch().reload())
613+
activeEditor.createTabs();
614+
else // Let the current tab know it was activated, so it can reload
615+
activeEditor.getCurrentTab().activated();
616616
} catch (IOException e) {
617617
System.err.println(e);
618618
}

app/src/processing/app/Editor.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,7 @@ public void selectTab(final int index) {
16021602
redoAction.updateRedoState();
16031603
updateTitle();
16041604
header.rebuild();
1605+
getCurrentTab().activated();
16051606

16061607
// This must be run in the GUI thread
16071608
SwingUtilities.invokeLater(() -> {
@@ -1653,7 +1654,11 @@ public int findTabIndex(final String name) {
16531654
return -1;
16541655
}
16551656

1656-
public void sketchLoaded(Sketch sketch) {
1657+
/**
1658+
* Create tabs for each of the current sketch's files, removing any existing
1659+
* tabs.
1660+
*/
1661+
public void createTabs() {
16571662
tabs.clear();
16581663
currentTabIndex = -1;
16591664
tabs.ensureCapacity(sketch.getCodeCount());
@@ -1665,6 +1670,7 @@ public void sketchLoaded(Sketch sketch) {
16651670
System.err.println(e);
16661671
}
16671672
}
1673+
selectTab(0);
16681674
}
16691675

16701676
/**
@@ -1980,9 +1986,8 @@ protected boolean handleOpenInternal(File sketchFile) {
19801986
Base.showWarning(tr("Error"), tr("Could not create the sketch."), e);
19811987
return false;
19821988
}
1989+
createTabs();
19831990

1984-
header.rebuild();
1985-
updateTitle();
19861991
// Disable untitled setting from previous document, if any
19871992
untitled = false;
19881993

app/src/processing/app/EditorTab.java

+87-15
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import javax.swing.text.Element;
4646
import javax.swing.text.PlainDocument;
4747
import javax.swing.undo.UndoManager;
48+
import javax.swing.text.DefaultCaret;
4849

4950
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
5051
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
@@ -70,6 +71,8 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage {
7071
protected RTextScrollPane scrollPane;
7172
protected SketchCode code;
7273
protected boolean modified;
74+
/** Is external editing mode currently enabled? */
75+
protected boolean external;
7376

7477
/**
7578
* Create a new EditorTab
@@ -101,13 +104,13 @@ public EditorTab(Editor editor, SketchCode code, String contents)
101104
RSyntaxDocument document = createDocument(contents);
102105
this.textarea = createTextArea(document);
103106
this.scrollPane = createScrollPane(this.textarea);
107+
code.setStorage(this);
104108
applyPreferences();
105109
add(this.scrollPane, BorderLayout.CENTER);
106110

107111
RUndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor);
108112
document.addUndoableEditListener(undo);
109113
textarea.setUndoManager(undo);
110-
code.setStorage(this);
111114
}
112115

113116
private RSyntaxDocument createDocument(String contents) {
@@ -279,19 +282,29 @@ public void applyPreferences() {
279282
scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding"));
280283
scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers"));
281284

282-
// apply the setting for 'use external editor'
283-
if (PreferencesData.getBoolean("editor.external")) {
284-
// disable line highlight and turn off the caret when disabling
285-
textarea.setBackground(Theme.getColor("editor.external.bgcolor"));
286-
textarea.setHighlightCurrentLine(false);
287-
textarea.setEditable(false);
288-
289-
} else {
290-
textarea.setBackground(Theme.getColor("editor.bgcolor"));
291-
textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight"));
292-
textarea.setEditable(true);
285+
// apply the setting for 'use external editor', but only if it changed
286+
if (external != PreferencesData.getBoolean("editor.external")) {
287+
external = !external;
288+
if (external) {
289+
// disable line highlight and turn off the caret when disabling
290+
textarea.setBackground(Theme.getColor("editor.external.bgcolor"));
291+
textarea.setHighlightCurrentLine(false);
292+
textarea.setEditable(false);
293+
// Detach from the code, since we are no longer the authoritative source
294+
// for file contents.
295+
code.setStorage(null);
296+
// Reload, in case the file contents already changed.
297+
reload();
298+
} else {
299+
textarea.setBackground(Theme.getColor("editor.bgcolor"));
300+
textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight"));
301+
textarea.setEditable(true);
302+
code.setStorage(this);
303+
// Reload once just before disabling external mode, to ensure we have
304+
// the latest contents.
305+
reload();
306+
}
293307
}
294-
295308
// apply changes to the font size for the editor
296309
Font editorFont = scale(PreferencesData.getFont("editor.font"));
297310
textarea.setFont(editorFont);
@@ -306,7 +319,34 @@ public void updateKeywords(PdeKeywords keywords) {
306319
document.setTokenMakerFactory(new ArduinoTokenMakerFactory(keywords));
307320
document.setSyntaxStyle(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS);
308321
}
309-
322+
323+
/**
324+
* Called when this tab is made the current one, or when it is the current one
325+
* and the window is activated.
326+
*/
327+
public void activated() {
328+
// When external editing is enabled, reload the text whenever we get activated.
329+
if (external) {
330+
reload();
331+
}
332+
}
333+
334+
/**
335+
* Reload the contents of our file.
336+
*/
337+
private void reload() {
338+
String text;
339+
try {
340+
text = code.load();
341+
} catch (IOException e) {
342+
System.err.println(I18n.format("Warning: Failed to reload file: \"{0}\"",
343+
code.getFileName()));
344+
return;
345+
}
346+
setText(text);
347+
setModified(false);
348+
}
349+
310350
/**
311351
* Get the TextArea object for use (not recommended). This should only
312352
* be used in obscure cases that really need to hack the internals of the
@@ -343,7 +383,39 @@ public String getText() {
343383
* Replace the entire contents of this tab.
344384
*/
345385
public void setText(String what) {
346-
textarea.setText(what);
386+
// Set the caret update policy to NEVER_UPDATE while completely replacing
387+
// the current text. Normally, the caret tracks inserts and deletions, but
388+
// replacing the entire text will always make the caret end up at the end,
389+
// which isn't really useful. With NEVER_UPDATE, the caret will just keep
390+
// its absolute position (number of characters from the start), which isn't
391+
// always perfect, but the best we can do without making a diff of the old
392+
// and new text and some guesswork.
393+
// Note that we cannot use textarea.setText() here, since that first removes
394+
// text and then inserts the new text. Even with NEVER_UPDATE, the caret
395+
// always makes sure to stay valid, so first removing all text makes it
396+
// reset to 0. Also note that simply saving and restoring the caret position
397+
// will work, but then the scroll position might change in response to the
398+
// caret position.
399+
DefaultCaret caret = (DefaultCaret) textarea.getCaret();
400+
int policy = caret.getUpdatePolicy();
401+
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
402+
try {
403+
RSyntaxDocument doc = (RSyntaxDocument)textarea.getDocument();
404+
int oldLength = doc.getLength();
405+
// The undo manager already seems to group the insert and remove together
406+
// automatically, but better be explicit about it.
407+
textarea.getUndoManager().beginInternalAtomicEdit();
408+
try {
409+
doc.insertString(oldLength, what, null);
410+
doc.remove(0, oldLength);
411+
} catch (BadLocationException e) {
412+
System.err.println("Unexpected failure replacing text");
413+
} finally {
414+
textarea.getUndoManager().endInternalAtomicEdit();
415+
}
416+
} finally {
417+
caret.setUpdatePolicy(policy);
418+
}
347419
}
348420

349421
/**

app/src/processing/app/Sketch.java

+3-52
Original file line numberDiff line numberDiff line change
@@ -65,42 +65,8 @@ public class Sketch {
6565
public Sketch(Editor _editor, File file) throws IOException {
6666
editor = _editor;
6767
data = new SketchData(file);
68-
load();
6968
}
7069

71-
72-
/**
73-
* Build the list of files.
74-
* <P>
75-
* Generally this is only done once, rather than
76-
* each time a change is made, because otherwise it gets to be
77-
* a nightmare to keep track of what files went where, because
78-
* not all the data will be saved to disk.
79-
* <P>
80-
* This also gets called when the main sketch file is renamed,
81-
* because the sketch has to be reloaded from a different folder.
82-
* <P>
83-
* Another exception is when an external editor is in use,
84-
* in which case the load happens each time "run" is hit.
85-
*/
86-
private void load() throws IOException {
87-
load(false);
88-
}
89-
90-
protected void load(boolean forceUpdate) throws IOException {
91-
data.load();
92-
93-
// set the main file to be the current tab
94-
if (editor != null) {
95-
int current = editor.getCurrentTabIndex();
96-
if (current < 0)
97-
current = 0;
98-
editor.sketchLoaded(this);
99-
editor.selectTab(current);
100-
}
101-
}
102-
103-
10470
private boolean renamingCode;
10571

10672
/**
@@ -943,24 +909,6 @@ public void importLibrary(UserLibrary lib) throws IOException {
943909
public void prepare() throws IOException {
944910
// make sure the user didn't hide the sketch folder
945911
ensureExistence();
946-
947-
// TODO record history here
948-
//current.history.record(program, SketchHistory.RUN);
949-
950-
// if an external editor is being used, need to grab the
951-
// latest version of the code from the file.
952-
if (PreferencesData.getBoolean("editor.external")) {
953-
// history gets screwed by the open..
954-
//String historySaved = history.lastRecorded;
955-
//handleOpen(sketch);
956-
//history.lastRecorded = historySaved;
957-
958-
// nuke previous files and settings, just get things loaded
959-
load(true);
960-
}
961-
962-
// // handle preprocessing the main file's code
963-
// return build(tempBuildFolder.getAbsolutePath());
964912
}
965913

966914
/**
@@ -1288,6 +1236,9 @@ public boolean isUntitled() {
12881236
return editor.untitled;
12891237
}
12901238

1239+
public boolean reload() throws IOException {
1240+
return data.reload();
1241+
}
12911242

12921243
// .................................................................
12931244

arduino-core/src/processing/app/BaseNoGui.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -502,14 +502,13 @@ static public void init(String[] args) throws Exception {
502502
boolean success = false;
503503
try {
504504
// Editor constructor loads the sketch with handleOpenInternal() that
505-
// creates a new Sketch that, in trun, calls load() inside its constructor
505+
// creates a new Sketch that, in turn, builds a SketchData
506+
// inside its constructor.
506507
// This translates here as:
507508
// SketchData data = new SketchData(file);
508509
// File tempBuildFolder = getBuildFolder();
509-
// data.load();
510510
SketchData data = new SketchData(absoluteFile(parser.getFilenames().get(0)));
511511
File tempBuildFolder = getBuildFolder(data);
512-
data.load();
513512

514513
// Sketch.exportApplet()
515514
// - calls Sketch.prepare() that calls Sketch.ensureExistence()
@@ -556,7 +555,6 @@ static public void init(String[] args) throws Exception {
556555
// data.load();
557556
SketchData data = new SketchData(absoluteFile(path));
558557
File tempBuildFolder = getBuildFolder(data);
559-
data.load();
560558

561559
// Sketch.prepare() calls Sketch.ensureExistence()
562560
// Sketch.build(verbose) calls Sketch.ensureExistence() and set progressListener and, finally, calls Compiler.build()

arduino-core/src/processing/app/SketchCode.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ public SketchCode(File file, boolean primary) {
9191

9292
/**
9393
* Set an in-memory storage for this file's text, that will be queried
94-
* on compile, save, and whenever the text is needed.
94+
* on compile, save, and whenever the text is needed. null can be
95+
* passed to detach any attached storage.
9596
*/
9697
public void setStorage(TextStorage text) {
9798
this.storage = text;
@@ -201,6 +202,9 @@ public boolean isModified() {
201202
return false;
202203
}
203204

205+
public boolean equals(Object o) {
206+
return (o instanceof SketchCode) && file.equals(((SketchCode) o).file);
207+
}
204208

205209
/**
206210
* Load this piece of code from a file and return the contents. This

0 commit comments

Comments
 (0)