Skip to content

Commit e51ea86

Browse files
Clean up sketch loading
Previously, the Sketch constructor called its `load()` function, which called the `SketchData.load()` function and `Editor.sketchLoaded()` to initialize the GUI with the loaded files. When external editing was enabled, `Sketch.load()` was called 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 `EditorTab.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 thie list of files in the sketch. If it changed, all tabs are re-created. If not, 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. 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 users 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. 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 dee5884 commit e51ea86

File tree

7 files changed

+131
-109
lines changed

7 files changed

+131
-109
lines changed

app/src/processing/app/Base.java

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

app/src/processing/app/Editor.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,7 @@ public void selectTab(final int index) {
16041604
redoAction.updateRedoState();
16051605
updateTitle();
16061606
header.rebuild();
1607+
getCurrentTab().activated();
16071608

16081609
// This must be run in the GUI thread
16091610
SwingUtilities.invokeLater(() -> {
@@ -1655,7 +1656,11 @@ public int findTabIndex(final String name) {
16551656
return -1;
16561657
}
16571658

1658-
public void sketchLoaded(Sketch sketch) {
1659+
/**
1660+
* Create tabs for each of the current sketch's files, removing any existing
1661+
* tabs.
1662+
*/
1663+
public void createTabs() {
16591664
tabs.clear();
16601665
currentTabIndex = -1;
16611666
tabs.ensureCapacity(sketch.getCodeCount());
@@ -1667,6 +1672,7 @@ public void sketchLoaded(Sketch sketch) {
16671672
System.err.println(e);
16681673
}
16691674
}
1675+
selectTab(0);
16701676
}
16711677

16721678
/**
@@ -1981,9 +1987,8 @@ protected boolean handleOpenInternal(File sketchFile) {
19811987
Base.showWarning(tr("Error"), tr("Could not create the sketch."), e);
19821988
return false;
19831989
}
1990+
createTabs();
19841991

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

app/src/processing/app/EditorTab.java

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import javax.swing.text.BadLocationException;
4747
import javax.swing.text.PlainDocument;
4848
import javax.swing.undo.UndoManager;
49+
import javax.swing.text.DefaultCaret;
4950

5051
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
5152
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
@@ -68,6 +69,8 @@ public class EditorTab extends JPanel implements SketchCode.TextStorage {
6869
protected RTextScrollPane scrollPane;
6970
protected SketchCode code;
7071
protected boolean modified;
72+
/** Is external editing mode currently enabled? */
73+
protected boolean external;
7174

7275
/**
7376
* Create a new EditorTab
@@ -99,12 +102,12 @@ public EditorTab(Editor editor, SketchCode code, String contents)
99102
RSyntaxDocument document = createDocument(contents);
100103
this.textarea = createTextArea(document);
101104
this.scrollPane = createScrollPane(this.textarea);
105+
code.setStorage(this);
102106
applyPreferences();
103107
add(this.scrollPane, BorderLayout.CENTER);
104108

105109
UndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor);
106110
((RSyntaxDocument)textarea.getDocument()).addUndoableEditListener(undo);
107-
code.setStorage(this);
108111
}
109112

110113
private RSyntaxDocument createDocument(String contents) {
@@ -277,19 +280,29 @@ public void applyPreferences() {
277280
scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding"));
278281
scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers"));
279282

280-
// apply the setting for 'use external editor'
281-
if (PreferencesData.getBoolean("editor.external")) {
282-
// disable line highlight and turn off the caret when disabling
283-
textarea.setBackground(Theme.getColor("editor.external.bgcolor"));
284-
textarea.setHighlightCurrentLine(false);
285-
textarea.setEditable(false);
286-
287-
} else {
288-
textarea.setBackground(Theme.getColor("editor.bgcolor"));
289-
textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight"));
290-
textarea.setEditable(true);
283+
// apply the setting for 'use external editor', but only if it changed
284+
if (external != PreferencesData.getBoolean("editor.external")) {
285+
external = !external;
286+
if (external) {
287+
// disable line highlight and turn off the caret when disabling
288+
textarea.setBackground(Theme.getColor("editor.external.bgcolor"));
289+
textarea.setHighlightCurrentLine(false);
290+
textarea.setEditable(false);
291+
// Detach from the code, since we are no longer the authoritative source
292+
// for file contents.
293+
code.setStorage(null);
294+
// Reload, in case the file contents already changed.
295+
reload();
296+
} else {
297+
textarea.setBackground(Theme.getColor("editor.bgcolor"));
298+
textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight"));
299+
textarea.setEditable(true);
300+
code.setStorage(this);
301+
// Reload once just before disabling external mode, to ensure we have
302+
// the latest contents.
303+
reload();
304+
}
291305
}
292-
293306
// apply changes to the font size for the editor
294307
textarea.setFont(PreferencesData.getFont("editor.font"));
295308
}
@@ -302,7 +315,34 @@ public void updateKeywords(PdeKeywords keywords) {
302315
document.setTokenMakerFactory(new ArduinoTokenMakerFactory(keywords));
303316
document.setSyntaxStyle(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS);
304317
}
305-
318+
319+
/**
320+
* Called when this tab is made the current one, or when it is the current one
321+
* and the window is activated.
322+
*/
323+
public void activated() {
324+
// When external editing is enabled, reload the text whenever we get activated.
325+
if (external) {
326+
reload();
327+
}
328+
}
329+
330+
/**
331+
* Reload the contents of our file.
332+
*/
333+
private void reload() {
334+
String text;
335+
try {
336+
text = code.load();
337+
} catch (IOException e) {
338+
System.err.println(I18n.format("Warning: Failed to reload file: \"{0}\"",
339+
code.getFileName()));
340+
return;
341+
}
342+
setText(text);
343+
setModified(false);
344+
}
345+
306346
/**
307347
* Get the TextArea object for use (not recommended). This should only
308348
* be used in obscure cases that really need to hack the internals of the
@@ -339,7 +379,18 @@ public String getText() {
339379
* Replace the entire contents of this tab.
340380
*/
341381
public void setText(String what) {
382+
// Set the caret update policy to NEVER_UPDATE while completely replacing
383+
// the current text. Normally, the caret tracks inserts and deletions, but
384+
// replacing the entire text will always make the caret end up at the end,
385+
// which isn't really useful. With NEVER_UPDATE, the caret will just keep
386+
// its absolute position (number of characters from the start), which isn't
387+
// always perfect, but the best we can do without making a diff of the old
388+
// and new text and some guesswork.
389+
DefaultCaret caret = (DefaultCaret) textarea.getCaret();
390+
int policy = caret.getUpdatePolicy();
391+
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
342392
textarea.setText(what);
393+
caret.setUpdatePolicy(policy);
343394
}
344395

345396
/**

app/src/processing/app/Sketch.java

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -67,42 +67,8 @@ public class Sketch {
6767
public Sketch(Editor _editor, File file) throws IOException {
6868
editor = _editor;
6969
data = new SketchData(file);
70-
load();
7170
}
7271

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

10874
/**
@@ -948,24 +914,6 @@ private void importLibrary(File jarPath) throws IOException {
948914
public void prepare() throws IOException {
949915
// make sure the user didn't hide the sketch folder
950916
ensureExistence();
951-
952-
// TODO record history here
953-
//current.history.record(program, SketchHistory.RUN);
954-
955-
// if an external editor is being used, need to grab the
956-
// latest version of the code from the file.
957-
if (PreferencesData.getBoolean("editor.external")) {
958-
// history gets screwed by the open..
959-
//String historySaved = history.lastRecorded;
960-
//handleOpen(sketch);
961-
//history.lastRecorded = historySaved;
962-
963-
// nuke previous files and settings, just get things loaded
964-
load(true);
965-
}
966-
967-
// // handle preprocessing the main file's code
968-
// return build(tempBuildFolder.getAbsolutePath());
969917
}
970918

971919
/**
@@ -1274,6 +1222,9 @@ public boolean isUntitled() {
12741222
return editor.untitled;
12751223
}
12761224

1225+
public boolean reload() throws IOException {
1226+
return data.reload();
1227+
}
12771228

12781229
// .................................................................
12791230

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -466,14 +466,13 @@ static public void init(String[] args) throws Exception {
466466
boolean success = false;
467467
try {
468468
// Editor constructor loads the sketch with handleOpenInternal() that
469-
// creates a new Sketch that, in trun, calls load() inside its constructor
469+
// creates a new Sketch that, in turn, builds a SketchData
470+
// inside its constructor.
470471
// This translates here as:
471472
// SketchData data = new SketchData(file);
472473
// File tempBuildFolder = getBuildFolder();
473-
// data.load();
474474
SketchData data = new SketchData(absoluteFile(parser.getFilenames().get(0)));
475475
File tempBuildFolder = getBuildFolder(data);
476-
data.load();
477476

478477
// Sketch.exportApplet()
479478
// - calls Sketch.prepare() that calls Sketch.ensureExistence()
@@ -520,7 +519,6 @@ static public void init(String[] args) throws Exception {
520519
// data.load();
521520
SketchData data = new SketchData(absoluteFile(path));
522521
File tempBuildFolder = getBuildFolder(data);
523-
data.load();
524522

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

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

Lines changed: 5 additions & 1 deletion
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;
@@ -199,6 +200,9 @@ public boolean isModified() {
199200
return false;
200201
}
201202

203+
public boolean equals(Object o) {
204+
return (o instanceof SketchCode) && file.equals(((SketchCode) o).file);
205+
}
202206

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

0 commit comments

Comments
 (0)